【Spring Security】授权与高级功能:权限控制、CSRF 与异常处理
🔑 关键词:Java、Spring Security、授权、@PreAuthorize、CSRF、Remember-Me、异常处理
一、URL 级别权限控制
1.1 基于角色
java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // 公开
.requestMatchers("/user/**").hasRole("USER") // 需要 USER 角色
.requestMatchers("/admin/**").hasRole("ADMIN") // 需要 ADMIN 角色
.requestMatchers("/admin/user/**").hasAnyRole("ADMIN", "SUPER_ADMIN") // 多角色
.anyRequest().authenticated() // 其他需认证
);
return http.build();
}
注意 :hasRole("ADMIN") 实际匹配的是 ROLE_ADMIN,Spring Security 自动添加 ROLE_ 前缀。
1.2 基于权限
java
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/add").hasAuthority("user:add")
.requestMatchers("/user/delete").hasAuthority("user:delete")
.requestMatchers("/order/**").hasAnyAuthority("order:read", "order:write")
.anyRequest().authenticated()
);
角色 vs 权限:
| 对比 | hasRole() |
hasAuthority() |
|---|---|---|
| 前缀 | 自动加 ROLE_ |
不加前缀,原样匹配 |
| 层级 | 角色有层级关系 | 权限是扁平的 |
| 适用场景 | 粗粒度(管理员/普通用户) | 细粒度(具体操作权限) |
二、方法级别权限控制
2.1 开启方法安全
java
@Configuration
@EnableMethodSecurity // Spring Security 6.x
public class SecurityConfig { }
2.2 @PreAuthorize(方法执行前校验)
java
@Service
public class UserService {
// 需要 user:read 权限才能调用
@PreAuthorize("hasAuthority('user:read')")
public List<User> findAll() {
return userMapper.selectAll();
}
// 需要 ADMIN 角色
@PreAuthorize("hasRole('ADMIN')")
public void deleteById(Long id) {
userMapper.deleteById(id);
}
// 只能查看自己的信息(动态参数校验)
@PreAuthorize("#id == authentication.principal.id")
public User findById(Long id) {
return userMapper.selectById(id);
}
// 自定义权限校验逻辑
@PreAuthorize("@permissionService.hasPermission(authentication, 'user:delete')")
public void delete(User user) { }
}
2.3 常用注解对比
| 注解 | 执行时机 | 说明 |
|---|---|---|
@PreAuthorize |
方法执行前 | ✅ 最常用,可访问方法参数 |
@PostAuthorize |
方法执行后 | 可访问方法返回值 |
@PreFilter |
方法执行前 | 过滤集合参数 |
@PostFilter |
方法执行后 | 过滤返回的集合 |
@Secured |
方法执行前 | 旧版注解,不支持 SpEL |
java
// @PostAuthorize 示例:校验返回值
@PostAuthorize("returnObject.owner == authentication.name")
public Order getOrder(Long id) {
return orderMapper.selectById(id);
}
三、CSRF 防护
3.1 什么是 CSRF?
CSRF(Cross-Site Request Forgery)跨站请求伪造:攻击者诱导用户访问恶意页面,利用用户已登录的身份发送恶意请求。
3.2 Spring Security 的 CSRF 防护
默认开启,要求每个 POST/PUT/DELETE 请求携带 CSRF Token。
表单场景(传统 Web):
html
<form action="/transfer" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<button type="submit">转账</button>
</form>
前后端分离场景:通常关闭 CSRF,改用 Token(JWT)认证。
java
http.csrf(csrf -> csrf.disable());
为什么前后端分离可以关闭 CSRF? JWT 放在请求头(Authorization)中,不会被浏览器自动携带,因此不受 CSRF 攻击影响。
四、Remember-Me(记住我)
4.1 配置
java
http.rememberMe(remember -> remember
.tokenValiditySeconds(86400 * 7) // 记住7天
.userDetailsService(myUserDetailsService)
);
4.2 登录表单
html
<form action="/doLogin" method="post">
<input name="username" placeholder="用户名"/>
<input name="password" type="password" placeholder="密码"/>
<label>
<input type="checkbox" name="remember-me"/> 记住我
</label>
<button type="submit">登录</button>
</form>
4.3 原理
勾选"记住我"后,服务端生成持久化 Token 写入 Cookie,下次访问自动恢复认证状态。Token 默认有效期 2 周。
五、自定义异常处理
5.1 认证异常(401)与授权异常(403)
java
http.exceptionHandling(ex -> ex
// 未认证:返回 401
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write(
"{\"code\":401,\"message\":\"未登录,请先登录\"}"
);
})
// 无权限:返回 403
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(403);
response.getWriter().write(
"{\"code\":403,\"message\":\"权限不足,无法访问\"}"
);
})
);
5.2 前后端分离的完整配置
java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/login", "/api/register").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form.disable()) // 关闭表单登录
.httpBasic(basic -> basic.disable()) // 关闭 Basic 认证
.csrf(csrf -> csrf.disable()) // 关闭 CSRF
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态
)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler())
);
return http.build();
}
六、RBAC 权限模型
6.1 数据库设计
用户表(sys_user) 角色表(sys_role) 权限表(sys_permission)
┌─────────┐ ┌─────────┐ ┌─────────────┐
│ id │ │ id │ │ id │
│ username│ │ name │ │ name │
│ password│ │ code │ │ code │
└─────────┘ └─────────┘ └─────────────┘
用户角色关联表(sys_user_role) 角色权限关联表(sys_role_permission)
┌───────────┬─────────┐ ┌───────────┬───────────────┐
│ user_id │ role_id │ │ role_id │ permission_id │
└───────────┴─────────┘ └───────────┴───────────────┘
6.2 查询用户权限
java
@Override
public UserDetails loadUserByUsername(String username) {
// 1. 查用户
User user = userMapper.selectByUsername(username);
// 2. 查角色
List<Role> roles = roleMapper.selectByUserId(user.getId());
// 3. 查权限
List<String> permissions = permissionMapper.selectByRoleIds(
roles.stream().map(Role::getId).collect(Collectors.toList())
);
// 4. 组装权限列表
List<GrantedAuthority> authorities = new ArrayList<>();
roles.forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_" + r.getCode())));
permissions.forEach(p -> authorities.add(new SimpleGrantedAuthority(p)));
return new LoginUser(user, authorities);
}
七、面试高频问题
Q1:Spring Security 中角色和权限的区别?
答 :角色(Role)是粗粒度的身份标识,如
ROLE_ADMIN、ROLE_USER;权限(Authority)是细粒度的操作权限,如user:add、user:delete。hasRole("ADMIN")匹配ROLE_ADMIN,hasAuthority("user:add")匹配user:add。
Q2:@PreAuthorize 和 @Secured 的区别?
答 :
@Secured是旧版注解,只支持角色判断,不支持 SpEL 表达式;@PreAuthorize支持 SpEL,可以在表达式中访问方法参数、认证对象,功能更强大。推荐用@PreAuthorize。
Q3:为什么前后端分离项目要关闭 CSRF?
答:CSRF 攻击依赖浏览器自动携带 Cookie 的机制。前后端分离通常使用 JWT Token 放在请求头中认证,不会被浏览器自动携带,因此不受 CSRF 攻击影响。关闭 CSRF 可以避免额外维护 Token 的开销。
八、总结
| 功能 | 实现方式 | 核心注解/配置 |
|---|---|---|
| URL 权限 | authorizeHttpRequests | hasRole / hasAuthority |
| 方法权限 | @EnableMethodSecurity | @PreAuthorize / @PostAuthorize |
| CSRF | csrf.disable() | 前后端分离关闭 |
| Remember-Me | rememberMe | tokenValiditySeconds |
| 异常处理 | exceptionHandling | authenticationEntryPoint / accessDeniedHandler |
| 权限模型 | RBAC | 用户-角色-权限 三表关联 |
📝 下一篇预告:《Spring Security + JWT 鉴权》,详解 JWT 原理、Token 生成验证、无状态认证完整流程。