1. 前言
博主在持续更新Spring Security教程过程中,有一些小伙伴总会私信问到之前关于ABAC属性权限模型那一个章节中自定义策略条件表达式的问题,如下图:

温馨提示:
没了解ABAC属性权限模型的可以访问:最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发进行了解。
大家最关心的问题就是:为什么在EvaluationContext上下文中传入了对应的自定义策略条件表达式,Spring Security就能进行更细粒度控制?
针对这些问题,博主觉得还是有必要再补充一下SpEL在Spring Security中的一些应用技巧。
2. SpEL在Spring Security中的基本应用
Spring Expression Language(SpEL)作为Spring生态的通用表达式引擎,在Spring Security中扮演着权限控制的灵魂角色。通过SpEL表达式让权限判断更灵活、更具有可配置性。我们不仅可以对用户角色进行细粒度控制,还可以在方法参数、请求信息等多维度上做出动态判断,满足复杂业务场景下的安全需求。
2.1 基础权限校验
在Spring Security的注解中,经常会看到@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter等注解,实际上这些注解内部都使用了SpEL来编写权限表达式:
1 |
|
在上面的例子中,
hasRole('ADMIN')就是基于SpEL的表达式。Spring Security内部会将该表达式编译并执行,根据认证对象中的角色信息决定是否允许访问。
2.2 复杂对象属性校验
下面举例:Project.owner(项目所有者必须是当前认证用户并且项目状态 = EDITABLE):
1 | public record Project(Long id, String owner, String status) {} |
2.3 方法参数动态处理
下面再看看方法上的动态处理:
1 | // 校验用户ID与当前认证用户匹配 |
通过上述的几个例子,就可以知道Spring Security常见的几个注解就是基于SpEL的表达式。
3. 常用的SpEL表达式及变量
在Spring Security中,SpEL表达式可以访问以下几个常见的内置变量及方法,从而实现灵活的权限控制:
❶ authentication
获取当前用户的身份认证信息,包含用户名、密码、授权信息等。
示例:
1 | // 判断传递用户名是否当前认证信息中的用户 |
❷ principal
当前经过认证的用户对象,通常是一个实现了UserDetails接口的对象。
示例:
1 | // 判断当前用户是否启用 |
❸ permitAll / denyAll
快速通过/拒绝所有访问。
示例:
1 | // 等价于 @PermitAll |
❹ #this
当前上下文中的对象,即当前方法调用对象(非静态方法)。
示例:
1 |
|
❺ #参数名
直接访问方法参数。
示例:
1 |
|
❻ returnObject
方法返回值(仅限@PostAuthorize和@PostFilter)。
示例:
1 | // 返回Document对象中owner = 当前用户名 才允许访问该接口 |
❼ target / targetObject
AOP代理的原始目标对象(目标方法所属的对象)。如:当前有一个adminService对象,adminService对象下有adminOperation方法,target = adminService。
示例:
1 |
|
❽ @beanName
访问Spring容器中的Bean,如Spring容器中有一个RiskEvaluator的bean,可以通过下面示例调用。
示例:
1 |
|
常用表达式速查表
| 表达式 | 说明 |
|---|---|
hasRole('ROLE') |
是否拥有指定角色 |
hasAnyRole('ROLE1','ROLE2') |
是否拥有任一指定角色 |
hasAuthority('AUTH') |
是否拥有指定权限 |
hasAnyAuthority('AUTH1','AUTH2') |
是否拥有任一指定权限 |
isAuthenticated() |
是否已认证 |
isAnonymous() |
是否为匿名用户 |
isRememberMe() |
是否通过RememberMe认证 |
fullyAuthenticated() |
是否为完整认证(非RememberMe) |
permitAll() |
始终允许 |
denyAll() |
始终拒绝 |
#paramName |
访问方法参数 |
@beanName.method() |
调用Spring Bean的方法 |
4. 自定义表达式实战
有时内置的SpEL表达式无法满足业务需要,我们可以扩展Spring Security,增加自定义的安全表达式。例如,我们可以定义一个检查用户是否在某一部门的表达式。
4.1 注册自定义权限校验器
回顾一下我们之前第6章节中ABAC属性权限模型,自定义注解authz,通过调用authz注解以实现我们自己所需业务:
1 |
|
调用示例:
1 |
|
代码解释:
通过调用@authz传递当前用户认证信息 + permission = “admin:menu”,查询出对应表达式(见文章开头的数据库截图),判断用户是否满足表达式条件。
4.2 自定义MethodSecurityExpressionHandler
在之前最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发的章节中已经演示过,这里不再赘述,感兴趣的小伙伴可以回顾之前的章节代码:
1 | // 调用示例 |
5. 高阶表达式技巧
下面演示几个高阶表达式技巧,小伙伴可以根据自己业务需求来调整使用。
5.1 多条件组合表达式
1 | // 复杂逻辑组合 |
5.2 时间维度控制
1 | // 限制工作时间访问 |
5.3 安全审计集成
AuditLogger是我们Spring容器中的bean,调用logExportAttempt传递相关参数判断是否有相应权限:
1 | // 权限校验与审计联动 |
5.4 参数过滤与返回结果过滤
Spring Security支持@PreFilter与@PostFilter注解,对集合类型的参数或返回值进行过滤。这样可以确保传入的集合中只包含当前用户可以操作的文档,返回的集合中也仅包含公开或本人拥有的文档:
1 |
|
5.5 动态资源权限控制
结合Spring Bean实现动态资源权限判断:
1 |
|
6. SpEL表达式执行原理简析
理解SpEL的执行原理有助于我们更好地运用它。SpEL表达式的执行主要包含以下几个步骤:
- 解析(Parsing):将表达式字符串解析为抽象语法树(AST)。
- 编译(Compilation):可选步骤,将表达式编译为字节码以提高性能。
- 评估(Evaluation):在给定的上下文中执行表达式,返回结果。
在Spring Security中,MethodSecurityExpressionHandler负责创建和管理表达式评估上下文,并将当前认证信息、方法参数等注入到上下文中,供表达式使用。
7. 性能优化建议
虽然SpEL非常强大,但过度使用或不当使用可能影响系统性能。以下是一些优化建议:
| 问题 | 建议 |
|---|---|
| 表达式重复解析 | 使用表达式缓存,避免重复解析相同表达式 |
| 复杂对象图导航 | 避免过深的对象属性访问链 |
| 频繁调用的方法 | 将复杂逻辑移到自定义Bean中,表达式只做简单调用 |
| 集合过滤 | 大数据量集合谨慎使用@PreFilter/@PostFilter |
8. 结语
SpEL在Spring Security中的应用使得权限控制不仅仅局限于静态的角色和权限匹配,而可以根据请求参数、用户属性、业务逻辑等灵活判断访问权限。通过灵活组合内置功能与自定义扩展,开发者可以实现从简单角色校验到复杂业务规则的全场景覆盖。
希望这个章节的内容能够帮助小伙伴们更深入地理解Spring Security权限表达式的底层原理与应用技巧,在实际项目中设计出更灵活高效的安全策略。如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,并三连给博主一点点鼓励!谢谢~