CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
API 设计实战入门/课时/授权模型
RawBlame
课时目录12 节

API 设计实战入门

  1. 01API 基础
  2. 02HTTP 协议层
  3. 03REST 资源设计
  4. 04错误处理
  5. 05认证
  6. 06授权模型
  7. 07API 安全
  8. 08API 风格
  9. 09架构与集成
  10. 10测试
  11. 11可观测与合规
  12. 12文档与工具

授权模型

RBAC、ABAC 与 ReBAC。

30 分钟

授权模型

这一课你将能做到

  1. 实现 RBAC(基于角色的访问控制),设计用户-角色-权限三层模型
  2. 理解 ABAC(基于属性的访问控制)和 ReBAC(基于关系的访问控制)的适用场景
  3. 在 Spring Security 中配置方法级和 URL 级权限控制

为什么要学

认证解决「你是谁」,授权解决「你能做什么」。一个系统里,管理员能删课程、讲师能改课程内容、学员只能看——这就是授权。授权模型选错,要么权限漏洞导致越权,要么设计过度复杂拖慢开发。

先建立图像

RBAC 模型(最常用):
┌────────┐      ┌────────┐      ┌────────┐
│  用户   │ ───► │  角色   │ ───► │  权限   │
│ User   │      │ Role   │      │Permission│
└────────┘      └────────┘      └────────┘
   张三            管理员            课程:删除
   李四            讲师             课程:编辑
   王五            学员             课程:查看

ABAC 模型(更灵活):
用户属性(部门=技术部, 级别=P7) + 资源属性(密级=机密) + 环境属性(时间=工作日)
    │
    ▼
策略引擎判断:技术部 P7 以上 + 工作日 → 允许访问机密文件

ReBAC 模型(社交关系):
用户A ──关注──► 用户B ──发布──► 文章X
    │
    └── 因为关注关系,A 可以查看 B 的文章

跟着做

步骤 1:数据库设计(RBAC)

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(100) NOT NULL,
    email VARCHAR(100)
);

-- 角色表
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(50) UNIQUE NOT NULL,  -- ADMIN, INSTRUCTOR, STUDENT
    name VARCHAR(100) NOT NULL
);

-- 权限表
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(100) UNIQUE NOT NULL,  -- lesson:read, lesson:write, lesson:delete
    name VARCHAR(100) NOT NULL,
    resource VARCHAR(50),               -- lesson, submission, user
    action VARCHAR(50)                  -- read, write, delete
);

-- 用户-角色关联
CREATE TABLE user_roles (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY (user_id, role_id)
);

-- 角色-权限关联
CREATE TABLE role_permissions (
    role_id BIGINT,
    permission_id BIGINT,
    PRIMARY KEY (role_id, permission_id)
);

步骤 2:实体类

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_roles",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();
}

@Entity
@Table(name = "roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String code;
    private String name;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "role_permissions",
            joinColumns = @JoinColumn(name = "role_id"),
            inverseJoinColumns = @JoinColumn(name = "permission_id"))
    private Set<Permission> permissions = new HashSet<>();
}

@Entity
@Table(name = "permissions")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String code;
    private String name;
    private String resource;
    private String action;
}

步骤 3:Spring Security 配置

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers(HttpMethod.GET, "/api/lessons/**").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/lessons").hasRole("ADMIN")
                .requestMatchers(HttpMethod.PUT, "/api/lessons/**").hasAnyRole("ADMIN", "INSTRUCTOR")
                .requestMatchers(HttpMethod.DELETE, "/api/lessons/**").hasRole("ADMIN")
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

步骤 4:方法级权限控制

@RestController
@RequestMapping("/api/lessons")
public class LessonController {

    @GetMapping
    public ApiResponse<List<Lesson>> list() {
        // 任何人都能看
        return ApiResponse.success(lessonService.findAll());
    }

    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    public ApiResponse<Lesson> create(@RequestBody @Valid LessonRequest request) {
        // 只有管理员能创建
        return ApiResponse.success(lessonService.create(request));
    }

    @PutMapping("/{id}")
    @PreAuthorize("hasAnyRole('ADMIN', 'INSTRUCTOR')")
    public ApiResponse<Lesson> update(@PathVariable Long id,
                                     @RequestBody @Valid LessonRequest request) {
        // 管理员和讲师都能改
        return ApiResponse.success(lessonService.update(id, request));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ApiResponse<Void> delete(@PathVariable Long id) {
        // 只有管理员能删
        lessonService.delete(id);
        return ApiResponse.success(null);
    }

    @GetMapping("/{id}/submissions")
    @PreAuthorize("hasAnyRole('ADMIN', 'INSTRUCTOR') or @submissionService.isOwner(#id, authentication.name)")
    public ApiResponse<List<Submission>> getSubmissions(@PathVariable Long id,
                                                       Authentication authentication) {
        // 管理员、讲师、或提交者本人能看
        return ApiResponse.success(submissionService.findByLessonId(id));
    }
}

步骤 5:自定义权限评估器(处理复杂逻辑)

@Component("lessonSecurity")
public class LessonSecurityService {

    public boolean canEdit(Long lessonId, Authentication authentication) {
        // 管理员直接放行
        if (authentication.getAuthorities().stream()
                .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
            return true;
        }
        // 讲师只能改自己负责的课程
        Lesson lesson = lessonRepository.findById(lessonId).orElse(null);
        if (lesson == null) {
            return false;
        }
        return lesson.getInstructorId().equals(getCurrentUserId(authentication));
    }

    public boolean canViewSubmissions(Long lessonId, Authentication authentication) {
        // 同 canEdit,或增加「已购买该课程」的判断
        return canEdit(lessonId, authentication);
    }
}
@PutMapping("/{id}")
@PreAuthorize("@lessonSecurity.canEdit(#id, authentication)")
public ApiResponse<Lesson> update(@PathVariable Long id,
                                 @RequestBody @Valid LessonRequest request) {
    return ApiResponse.success(lessonService.update(id, request));
}

步骤 6:数据级权限(行级安全)

// 只返回当前用户有权限的数据
@Query("SELECT l FROM Lesson l WHERE " +
       "(:isAdmin = true) OR " +
       "(l.visibility = 'PUBLIC') OR " +
       "(l.instructorId = :userId)")
List<Lesson> findVisibleLessons(@Param("userId") Long userId,
                                @Param("isAdmin") boolean isAdmin);

讲清楚要点

  1. RBAC 是默认选择:用户-角色-权限三层,覆盖 80% 的场景。角色是权限的集合,用户通过角色获得权限。好处是简单直观,好维护。
  2. ABAC 适合复杂策略:当权限取决于「时间、地点、数据属性、用户属性」的组合时,RBAC 力不从心。比如「工作日的 9-18 点,技术部 P7 以上可以访问生产环境日志」。ABAC 用策略语言(如 XACML)表达规则。
  3. ReBAC 适合社交网络:权限取决于「关系」,比如「好友可见」「关注后才能看」。微信、微博都用 ReBAC。
  4. 权限控制分三层:URL 级(粗粒度,如 /api/admin/** 需要 ADMIN)、方法级(中粒度,如 @PreAuthorize)、数据级(细粒度,如只返回自己的数据)。三层结合最安全。
  5. 最小权限原则:默认拒绝,按需开通。新用户注册后没有任何权限,由管理员分配角色。不要给「所有人都能看」的默认权限。

你可能会懵的地方

  • RBAC 角色和权限有什么区别? 角色是「职位」(如管理员、讲师),权限是「动作」(如删除课程、编辑课程)。角色是权限的集合。好处:给用户分配角色比逐个分配权限简单。

  • 什么时候用 ABAC? 当权限规则涉及「动态属性」时。比如「只能修改自己创建的课程」——RBAC 做不到(课程创建者不是固定角色),需要 ABAC(资源创建者 == 当前用户)。

  • Spring Security 的 hasRole 和 hasAuthority 区别? hasRole('ADMIN') 自动加 ROLE_ 前缀,匹配 ROLE_ADMIN;hasAuthority('ADMIN') 直接匹配 ADMIN。建议统一用 hasAuthority,更灵活。

  • 数据级权限怎么做? 方案一:SQL 里加条件(如上面的 @Query);方案二:MyBatis 拦截器自动拼接条件;方案三:数据库视图或行级安全策略(如 PostgreSQL RLS)。推荐方案一,简单可控。

  • 微服务架构下授权怎么做? 方案一:每个服务自己校验(去中心化,性能好但规则分散);方案二:统一授权服务(如 OPA、Casbin,集中管理但增加调用);方案三:网关层粗粒度 + 服务内细粒度。推荐方案三。

错法对照

错法问题正解
前端隐藏按钮就当权限控制了前端可被绕过,直接调 API后端必须校验权限
所有权限都写成硬编码 if-else新增权限要改代码,难维护数据库存权限,动态加载
用户直接关联权限,不通过角色权限分配太琐碎,难管理RBAC 三层模型
只控制 URL,不控制方法GET /api/lessons 公开,POST /api/lessons 也要公开?按 HTTP 方法区分权限
管理员权限给所有运营人员误操作风险大拆分角色:内容管理员、用户管理员、超级管理员

卡点急救

  1. @PreAuthorize 不生效:检查是否加了 @EnableMethodSecurity(Spring Boot 2.x 是 @EnableGlobalMethodSecurity),以及方法是否是 public。
  2. 权限不足返回 403 但不知道原因:开启 Spring Security 调试日志 logging.level.org.springframework.security=DEBUG。
  3. 多角色用户权限混乱:设计角色时考虑「权限并集」,一个用户有多个角色时,权限是叠加的(除非显式做互斥设计)。

面试常问

  • RBAC 和 ABAC 的区别? RBAC 基于角色(静态),ABAC 基于属性(动态)。RBAC 简单,适合组织架构稳定的系统;ABAC 灵活,适合规则复杂、属性多变的场景。能各举一个实际场景。

  • 怎么实现「只能修改自己创建的资源」? 方案一:Service 层判断 resource.creatorId == currentUserId;方案二:自定义 @PreAuthorize 表达式 + 权限评估器;方案三:数据库行级安全。能讲出优缺点。

  • Spring Security 的授权流程? Filter 链 → SecurityContextHolder 取 Authentication → AccessDecisionManager 决策 → 通过/拒绝。能讲出 FilterSecurityInterceptor、MethodSecurityInterceptor 的区别。

自测三问

  1. 设计一个「在线文档协作系统」的权限模型,要求支持「公开可读、邀请可编辑、仅自己可见」三种分享模式。
  2. 如果角色有 100 个,每个角色平均 20 个权限,数据库查询会有什么问题?怎么优化?
  3. 什么时候需要在 API 响应里做「字段级权限」(如普通用户看不到手机号,管理员能看到)?

小结一句话

授权的核心是「最小权限 + 分层控制」:RBAC 管角色,ABAC 管策略,ReBAC 管关系;URL 级、方法级、数据级三层防护缺一不可。

延伸

  • Spring Security 授权文档
  • OPA(Open Policy Agent)
  • 下一课:API 安全——OWASP API Top 10 与防护实战。
加载本节练习…
← 上一课认证下一课 →API 安全