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) |
| 适用场景 | 简单的角色划分(管理员/普通用户) | 复杂的资源操作控制(增删改查) |
| 灵活性 | 低(角色变更需改代码) | 高(权限编码可配置化) |
六、关键注意事项
- 权限封装要求 :
- 角色权限:必须为角色名拼接
ROLE_前缀(如ROLE_saler),否则hasRole()校验失败; - 资源权限:权限编码直接封装,无需前缀,与
hasAuthority()中的字符串完全一致即可。
- 角色权限:必须为角色名拼接
- 注解生效前提 :
- 必须在Security配置类上添加
@EnableMethodSecurity(Spring Security 6.x版本,旧版本是@EnableGlobalMethodSecurity)。
- 必须在Security配置类上添加
- 数据库关联查询 :
- 资源权限需通过"用户-角色-权限"三层关联查询,注意加
DISTINCT去重(避免同一权限重复封装)。
- 资源权限需通过"用户-角色-权限"三层关联查询,注意加
- 密码加密 :
- Spring Security强制要求密码加密存储,需通过
BCryptPasswordEncoder加密,否则认证时会报错。
- Spring Security强制要求密码加密存储,需通过
- 403权限不足处理 :
- 项目中配置了
static/error/403.html,当权限校验失败时会跳转该页面,提升用户体验。
- 项目中配置了
七、核心流程总结
- 认证流程:用户登录 →
UserDetailsService查询用户+权限 → 封装UserDetails→ Spring Security完成认证; - 授权流程:访问接口 →
@PreAuthorize触发权限校验 → 对比用户getAuthorities()中的权限标识 → 匹配则放行,否则返回403。
八、拓展思考
- 可将权限编码配置到数据库,实现"权限动态配置"(无需改代码,仅改数据库即可调整权限);
- 结合自定义权限注解/权限拦截器,实现更灵活的权限控制;
- 补充权限缓存(如Redis),减少数据库关联查询的性能损耗。