1. 前言
在实际项目中,安全控制不仅体现在URL拦截层面,方法级安全控制也越来越受到重视。Spring Security提供了多种方式实现方法级安全,通过方法注解体系,这种细粒度控制使得我们能够在方法调用前、调用后,甚至返回值处理阶段实施安全检查,真正成为开发者保护服务接口的重要手段。
相信小伙伴们通过前面章节的学习,发现了博主在方法上进行角色和菜单资源验证时使用的一个注解:@PreAuthorize。那么本章节博主将带着大家剖析**@PreAuthorize**注解的核心原理、SpEL表达式机制,并通过示例代码演示如何在实际项目中灵活运用该注解实现细粒度的权限控制。
2. @PreAuthorize注解简介
@PreAuthorize注解可以在方法执行前对传入的参数、当前用户信息、认证状态等进行校验,从而决定是否允许方法执行。常见使用场景包括:
- 限制某个接口或方法只允许特定角色访问;
- 根据方法参数和认证信息动态判断权限;
- 调用自定义的权限判断逻辑(例如上一章节中结合自定义PermissionEvaluator);
Spring Security内部通过AOP拦截被@PreAuthorize修饰的方法,并利用Spring Expression Language(SpEL)对注解中定义的表达式进行求值。只有当表达式求值结果为true时,方法才会执行,否则会抛出拒绝访问异常。
3. @PreAuthorize核心原理解析
Spring Security开启方法级安全控制实际上非常简单,只需要在@Configuration配置类中添加@EnableMethodSecurity:
1 |
|
方法授权可以分为方法前授权和方法后授权的组合,看下面的例子:
1 |
|
当方法安全性被激活时,对MyCustomerService#readCustomer的调用流程如下(官方流程图):

流程解析
Spring AOP为readCustomer调用其代理方法。它调用与AuthorizationManagerBeforeMethodInterceptor切入点匹配的@PreAuthorize。- 拦截器调用
PreAuthorizeAuthorizationManager#check。- 授权管理器使用
MethodSecurityExpressionHandler解析注解的SpEL表达式,并从包含EvaluationContext和MethodSecurityExpressionRoot的Supplier构造对应的MethodInvocation。- 拦截器使用此上下文来评估表达式,它从
Authentication读取Supplier,并检查其权限集合中是否有permission:read。- 如果评估通过,那么
Spring AOP继续调用该方法。- 如果不通过,拦截器发布一个
AuthorizationDeniedEvent并抛出一个AccessDeniedException,ExceptionTranslationFilter捕获并向响应返回一个403状态码。- 方法返回后,
Spring AOP调用与切入点匹配的AuthorizationManagerAfterMethodInterceptor,操作与上面相同,但是针对@PostAuthorize。- 如果评估通过(在这种情况下,返回值属于登录用户),则处理继续正常进行。
- 如果不通过,拦截器将发布一个
AuthorizationDeniedEvent并抛出一个AccessDeniedException,然后由ExceptionTranslationFilter捕获并向响应返回403状态代码。
拦截与表达式求值
从上述官方介绍的工作流程来看,我们可以简单总结为:
Spring Security在启用方法级安全时,会在应用上下文中配置一个MethodSecurityInterceptor(基于AOP实现)。当被@PreAuthorize修饰的方法被调用时:
- 拦截器捕获方法调用,并构造
EvaluationContext,上下文中包含认证信息、方法参数等数据; - 使用
MethodSecurityExpressionHandler将注解中的SpEL表达式求值,判断是否满足访问条件; - 如果表达式结果为false,则抛出
AccessDeniedException;否则放行执行方法。

SpEL表达式
@PreAuthorize注解的值是一个SpEL表达式,可以引用以下内置变量:
- authentication:当前用户的认证对象。
- principal:当前认证用户的主体信息(通常为UserDetails对象)。
- #root:表达式根对象。
- 方法参数:可以通过
#paramName或#p0访问方法参数。
如下代码:
1 |
|
表示只有当前用户拥有ADMIN角色且方法参数id大于10时,才能执行该方法。
自定义扩展
通过自定义MethodSecurityExpressionHandler和PermissionEvaluator,可以扩展@PreAuthorize表达式功能。具体可以查阅上一章节ABAC属性权限模型实战开发。
4. 注解应用实战
下面通过一些简单示例,演示如何配置Spring Security方法级安全。
❶ 基础权限校验
1 |
|
❷ 参数级权限控制
1 | // 校验创建者匹配 |
❸ 动态业务规则集成
如没有了解的小伙伴,建议查阅博主上一章节内容(这里仅演示如何配置@PreAuthorize):
最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发
1 | public class DocumentPermissionEvaluator { |
❹ 复合条件表达式
1 | // 组合多个条件 |
❺ 返回值后校验
1 |
|
❻ 参数预处理
1 |
|
❼ 结合Spring Bean的复杂校验
1 |
|
5. 常见表达式对照表
为了方便大家在实际开发中快速查阅,博主整理了常用的SpEL表达式对照表:
| 表达式示例 | 说明 |
|---|---|
hasRole('ADMIN') |
当前用户是否拥有ADMIN角色 |
hasAnyRole('ADMIN','USER') |
当前用户是否拥有任一指定角色 |
hasAuthority('sys:user:delete') |
当前用户是否拥有指定权限 |
hasAnyAuthority('sys:user:add','sys:user:edit') |
当前用户是否拥有任一指定权限 |
isAuthenticated() |
用户是否已认证 |
isAnonymous() |
用户是否为匿名用户 |
isRememberMe() |
用户是否通过RememberMe认证 |
fullyAuthenticated() |
用户是否为完整认证(非RememberMe) |
#id == 1 |
方法参数id是否等于1 |
#user.name == authentication.name |
参数user的name属性是否等于当前用户名 |
@bean.check(#param) |
调用Spring Bean的自定义方法 |
returnObject != null |
返回值不能为null(用于后处理) |
6. 常见问题与解决方案
问题1:@PreAuthorize不生效
可能原因:
- 忘记添加
@EnableMethodSecurity注解 - 方法不是由Spring容器管理的Bean
- 在同一个类中调用被@PreAuthorize修饰的方法(AOP内部调用失效)
解决方案:
1 | // 确保配置类添加注解 |
问题2:方法参数名称无法识别
原因: Java编译时未保留参数名称信息
解决方案: 在pom.xml中添加编译参数
1 | <plugin> |
或者使用参数索引:
1 | // 使用参数索引 |
问题3:复杂表达式导致代码可读性差
解决方案: 将复杂逻辑提取到自定义Bean中
1 |
|
结语
通过本章节方法级安全控制的介绍,相信大家已经能通过灵活运用表达式语言和自定义扩展,让我们可以在保证系统安全性的同时,维持代码的优雅与可维护性!希望这章文章对你在Spring Security方法级安全控制的实践中提供帮助和启发!
在后续的章节中,我们将继续深入探讨Spring Security的其他高级特性,敬请期待!