Spring Security 角色权限&资源权限配置 学习笔记

Spring Security 角色权限&资源权限配置 学习笔记

一、学习核心目标

基于RBAC(基于角色的访问控制)模型,分别实现角色维度资源维度的权限控制,理解两种权限控制的差异与适用场景,掌握Spring Security中权限配置的核心流程。

二、基础环境说明

  • 框架:Spring Boot + Spring Security + MyBatis
  • 数据库:MySQL(sy_db),涉及表:用户表(user)、角色表(t_role)、权限表(t_permission)、角色-权限关联表(t_role_permission)、用户-角色关联表(t_user_role)
  • 核心依赖(隐含):spring-boot-starter-security、spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java等

三、角色权限配置(注释部分,历史实现)

1. 核心原理

基于角色(Role) 控制接口访问,Spring Security中角色权限默认带ROLE_前缀,通过hasRole()/hasAnyRole()注解判断角色是否匹配。

2. 关键代码解析(注释部分)

(1)实体类 TUser 中角色权限处理
java 复制代码
// TUser.java 中注释的角色权限相关代码
// private List<TRole> tRoleList; // 角色列表

/**
 * 获取用户权限  目前暂无权限
 * @return
 */
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    Collection<SimpleGrantedAuthority> Collection=new ArrayList<>();
    // 【角色权限逻辑(注释)】遍历角色列表,封装角色权限(需加ROLE_前缀)
//    for (TRole tRole : tRoleList) {
//        SimpleGrantedAuthority simpleGrantedAuthority=new SimpleGrantedAuthority("ROLE_"+tRole.getRole());
//        Collection.add(simpleGrantedAuthority);
//    }
    // ... 资源权限逻辑(后续核心)
    return Collection;
}
  • 核心逻辑:从数据库查询用户关联的角色列表,为每个角色拼接ROLE_前缀(Spring Security默认要求),封装为SimpleGrantedAuthority对象。
(2)Controller 角色权限注解
java 复制代码
// ProductController.java 角色权限控制
@GetMapping("/list")
@PreAuthorize("hasRole('saler')") // 要求用户拥有saler角色(底层自动拼接ROLE_)
public String list(){
   return "hello product list...";
}

@GetMapping("/update")
@PreAuthorize("hasAnyRole('saler','manager')") // 多角色匹配
public String update(){
   return "hello product update...";
}
  • @PreAuthorize:方法级权限注解,在方法执行前校验权限;
  • hasRole('saler'):等价于hasAuthority('ROLE_saler'),底层会自动拼接ROLE_前缀。
(3)角色权限查询(Mapper层)
xml 复制代码
<!-- RoleMapper.xml 按用户ID查询角色 -->
<select id="findRoleByUserId" resultType="com.sy.pojo.TRole" parameterType="integer">
  select r.id, r.role, r.role_name
  from t_role r
         left join t_user_role ur on r.id = ur.role_id
  where ur.user_id = #{userId}
</select>
  • 通过用户-角色关联表(t_user_role)查询用户绑定的所有角色。

3. 角色权限优缺点

优点 缺点
配置简单,适合粗粒度权限控制(如:管理员/普通用户) 权限颗粒度粗,无法控制到具体资源操作(如:商品的查看/新增/编辑)
符合RBAC基础模型,易理解 角色变更时需修改代码(如新增角色需改注解)

四、资源权限配置(核心实现)

1. 核心原理

基于资源权限标识符(Permission Code) 控制接口访问,权限颗粒度更细(可控制到"某个资源的某个操作"),通过hasAuthority()注解判断权限编码是否匹配。

2. 数据库设计(核心表)

表名 核心字段 作用
t_permission id、name(权限名称)、code(权限编码) 存储资源权限(如:product:list、product:add)
t_role_permission role_id、permission_id 角色-权限关联(一个角色绑定多个权限)
t_user_role user_id、role_id 用户-角色关联(一个用户绑定多个角色)

3. 关键代码解析

(1)实体类:权限表(TPermission)+ 用户类(TUser)关联权限
java 复制代码
// TPermission.java 权限实体
@Data
public class TPermission implements Serializable {
    private Integer id;
    private String name; // 权限名称(如:商品列表)
    private String code; // 权限编码(如:product:list)
}

// TUser.java 关联权限列表
private List<TPermission> tPermissions; // 资源权限列表(替代注释的角色列表)

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    Collection<SimpleGrantedAuthority> Collection=new ArrayList<>();
    // 【资源权限核心逻辑】遍历权限编码列表,封装为Authority
    for (TPermission tPermission : tPermissions) {
        SimpleGrantedAuthority simpleGrantedAuthority=new SimpleGrantedAuthority(tPermission.getCode());
        Collection.add(simpleGrantedAuthority);
    }
    return Collection;
}
  • 核心:将数据库中查询到的"权限编码"直接封装为SimpleGrantedAuthority(无需加前缀),作为用户的权限标识。
(2)Mapper层:查询用户关联的权限
xml 复制代码
<!-- PermissionMapper.xml 按用户ID查询权限 -->
<select id="findPermissionByUid" parameterType="integer" resultType="com.sy.pojo.TPermission">
  SELECT DISTINCT
    tp.id,
    tp.name,
    tp.code
  FROM t_permission tp
         LEFT JOIN t_role_permission trp ON tp.id = trp.permission_id
         LEFT JOIN t_role tr ON trp.role_id = tr.id
         LEFT JOIN t_user_role tur ON tr.id = tur.role_id
  WHERE tur.user_id = #{userId}
</select>
  • 关联查询逻辑:用户ID → 角色(t_user_role)→ 权限(t_role_permission)→ 权限编码(t_permission),最终拿到用户的所有资源权限编码。
(3)Service层:加载用户权限
java 复制代码
// UserServiceImpl.java
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 1. 查询用户基础信息
    TUser tUser = userMapper.findUserByUsername(username);
    if (tUser == null) {
        throw new UsernameNotFoundException("用户不存在");
    }
    // 2. 查询用户关联的资源权限(替代注释的角色查询)
    List<TPermission> tPermissionList =permissionMapper.findPermissionByUid(tUser.getId());
    tUser.setTPermissions(tPermissionList);
    return tUser;
}
  • 核心:Spring Security认证时,通过loadUserByUsername加载用户信息+权限信息,供后续权限校验使用。
(4)Controller层:资源权限注解
java 复制代码
// GoodsController.java 资源权限控制
@GetMapping("/list")
@PreAuthorize("hasAuthority('product:list')") // 校验是否有该权限编码
public String list(){
    return "hello Goods list...";
}

@GetMapping("/add")
@PreAuthorize("hasAuthority('product:add')")
public String add(){
    return "hello Goods add...";
}
  • hasAuthority('product:list'):直接校验用户是否拥有该权限编码,颗粒度精准到"资源+操作"。
(5)Security核心配置
java 复制代码
// SecurityConfig.java
@Configuration
@EnableMethodSecurity // 开启方法级权限注解(@PreAuthorize)
public class SecurityConfig {

    // 密码加密器(Spring Security要求密码必须加密存储)
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    // 安全过滤器链配置
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                // 自定义登录页面配置
                .formLogin(formLogin -> formLogin
                        .loginProcessingUrl("/login") // 登录接口(前端表单提交地址)
                        .loginPage("/toLogin") // 自定义登录页跳转地址
                        .successForwardUrl("/index5") // 登录成功后跳转地址
                )
                // 接口访问权限控制
                .authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
                        .requestMatchers("/toLogin").permitAll() // 登录页无需认证
                        .anyRequest().authenticated() // 其他所有请求需认证
                )
                .build();
    }
}
  • @EnableMethodSecurity:必须开启,否则@PreAuthorize注解失效;
  • 登录配置:自定义登录页替代Spring Security默认登录页,表单提交地址需与loginProcessingUrl一致。

五、角色权限 vs 资源权限 对比

维度 角色权限(Role) 资源权限(Authority)
控制粒度 粗粒度(按角色分组) 细粒度(按资源+操作)
注解 hasRole()/hasAnyRole() hasAuthority()/hasAnyAuthority()
权限标识 需加ROLE_前缀(框架默认) 自定义编码(如product:list)
适用场景 简单的角色划分(管理员/普通用户) 复杂的资源操作控制(增删改查)
灵活性 低(角色变更需改代码) 高(权限编码可配置化)

六、关键注意事项

  1. 权限封装要求
    • 角色权限:必须为角色名拼接ROLE_前缀(如ROLE_saler),否则hasRole()校验失败;
    • 资源权限:权限编码直接封装,无需前缀,与hasAuthority()中的字符串完全一致即可。
  2. 注解生效前提
    • 必须在Security配置类上添加@EnableMethodSecurity(Spring Security 6.x版本,旧版本是@EnableGlobalMethodSecurity)。
  3. 数据库关联查询
    • 资源权限需通过"用户-角色-权限"三层关联查询,注意加DISTINCT去重(避免同一权限重复封装)。
  4. 密码加密
    • Spring Security强制要求密码加密存储,需通过BCryptPasswordEncoder加密,否则认证时会报错。
  5. 403权限不足处理
    • 项目中配置了static/error/403.html,当权限校验失败时会跳转该页面,提升用户体验。

七、核心流程总结

  1. 认证流程:用户登录 → UserDetailsService查询用户+权限 → 封装UserDetails → Spring Security完成认证;
  2. 授权流程:访问接口 → @PreAuthorize触发权限校验 → 对比用户getAuthorities()中的权限标识 → 匹配则放行,否则返回403。

八、拓展思考

  1. 可将权限编码配置到数据库,实现"权限动态配置"(无需改代码,仅改数据库即可调整权限);
  2. 结合自定义权限注解/权限拦截器,实现更灵活的权限控制;
  3. 补充权限缓存(如Redis),减少数据库关联查询的性能损耗。
相关推荐
我的xiaodoujiao4 小时前
API 接口自动化测试详细图文教程学习系列10--Requests模块2--举例说明
python·学习·测试工具·pytest
CHU7290354 小时前
在线教学课堂APP功能版块设计方案:重构学习场景的交互逻辑
java·学习·小程序·重构
汤愈韬4 小时前
网络安全之网络基础知识
服务器·网络协议·网络安全·security
我叫张土豆4 小时前
Spring AI 集成 MCP 服务踩坑实录:SSE 与 Streamable HTTP 协议的兼容性深度剖析
人工智能·spring·http
一定要AK4 小时前
HTML5 入门到精通全章节学习笔记
笔记·学习·html5
程序员zgh4 小时前
C/C++ 单元测试系统 构建
c语言·开发语言·c++·学习·单元测试
Chef_Chen4 小时前
Agent学习-RAG--上下文压缩与知识库的更新
人工智能·学习·自然语言处理
计算机学姐4 小时前
基于SpringBoot的在线学习网站平台【个性化推荐+数据可视化+课程章节学习】
java·vue.js·spring boot·后端·学习·mysql·信息可视化
Engineer邓祥浩4 小时前
JVM学习笔记(7) 第三部分 虚拟机执行子系统 第6章 类文件结构
jvm·笔记·学习
中屹指纹浏览器4 小时前
2026指纹浏览器技术架构深度解析:从隔离原理到性能优化的全链路实践
经验分享·笔记