title: (十)权限表达式进阶:SpEL在安全控制中的高阶魔法
categories: [程技]
tags: [Spring Security]
date: 2026-03-02
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来编写权限表达式:
@PreAuthorize("hasAuthority('USER_CREATE')")
public void createUser(User user) { /*...*/ }
@PreAuthorize("hasRole('ADMIN') or hasRole('AUDITOR')")
public void auditSystem() { /*...*/ }在上面的例子中,hasRole('ADMIN')就是基于SpEL的表达式。Spring Security内部会将该表达式编译并执行,根据认证对象中的角色信息决定是否允许访问。2.2 复杂对象属性校验
下面举例:Project.owner(项目所有者必须是当前认证用户并且项目状态 = EDITABLE):
public record Project(Long id, String owner, String status) {}
// 校验项目所有者且项目状态为可编辑
@PreAuthorize("#project.owner == authentication.name and #project.status == 'EDITABLE'")
public void updateProject(Project project) {
// 更新逻辑
}2.3 方法参数动态处理
下面再看看方法上的动态处理:
// 校验用户ID与当前认证用户匹配
@PreAuthorize("#userId == authentication.principal.id")
public User getUserProfile(Long userId) {
return userService.findById(userId);
}
// 集合参数过滤
@PostFilter("filterObject.department == authentication.principal.department")
public List<Document> getAllDocuments() {
return documentRepository.findAll();
}通过上述的几个例子,就可以知道Spring Security常见的几个注解就是基于SpEL的表达式。
3. 常用的SpEL表达式及变量
在Spring Security中,SpEL表达式可以访问以下几个常见的内置变量及方法,从而实现灵活的权限控制:
❶ authentication
获取当前用户的身份认证信息,包含用户名、密码、授权信息等。
示例:
// 判断传递用户名是否当前认证信息中的用户
@PreAuthorize("authentication.principal.username == #username")
public void getUserDetails(String username) { /*...*/ }❷ principal
当前经过认证的用户对象,通常是一个实现了UserDetails接口的对象。
示例:
// 判断当前用户是否启用
@PreAuthorize("principal.enabled")
public void sensitiveOperation() { /*...*/ }❸ permitAll / denyAll
快速通过/拒绝所有访问。
示例:
@PreAuthorize("permitAll") // 等价于 @PermitAll
public String publicApi() { /*...*/ }❹ #this
当前上下文中的对象,即当前方法调用对象(非静态方法)。
示例:
@PreAuthorize("#this.isFeatureEnabled()")
public void usePremiumFeature() { /*...*/ }❺ #参数名
直接访问方法参数。
示例:
@PreAuthorize("#user.id == principal.id")
public void updateUser(User user) { /*...*/ }❻ returnObject
方法返回值(仅限@PostAuthorize和@PostFilter)。
示例:
// 返回Document对象中owner = 当前用户名 才允许访问该接口
@PostAuthorize("returnObject.owner == principal.username")
public Document getConfidentialDoc() { /*...*/ }❼ target / targetObject
AOP代理的原始目标对象(目标方法所属的对象)。如:当前有一个adminService对象,adminService对象下有adminOperation方法,target = adminService。
示例:
@PreAuthorize("target.isFeatureEnabled('ADVANCED_MODE')")
public void adminOperation() { /*...*/ }❽ @beanName
访问Spring容器中的Bean,如Spring容器中有一个RiskEvaluator的bean,可以通过下面示例调用。
示例:
@PreAuthorize("@riskEvaluator.isLowRisk(#request)")
public void processRequest(Request request) { /*...*/ }常用表达式速查表
| 表达式 | 说明 |
|---|---|
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注解以实现我们自己所需业务:
@Component("authz")
@RequiredArgsConstructor
public class AuthorizationLogic {
private final SpelExpressionParser parser = new SpelExpressionParser();
private final SysPolicyMapper sysPolicyMapper;
public boolean check(MethodSecurityExpressionOperations operations, String permission) {
SysUser userDetails = (SysUser) operations.getAuthentication().getPrincipal();
// 加载策略集
LambdaQueryWrapper<SysPolicy> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysPolicy::getTargetResource, permission);
List<SysPolicy> policies = sysPolicyMapper.selectList(queryWrapper);
if (policies.isEmpty()) {
return false;
}
// 构建评估上下文
EvaluationContext context = new StandardEvaluationContext();
// 将用户传入表达式上下文 如:#user.attrs['department'] == 'IT'
// 其中user前缀就是我们传入的user
context.setVariable("user", userDetails);
return policies.stream().allMatch(policy ->
parser.parseExpression(policy.getConditionExpression()).getValue(context, Boolean.class)
);
}
}调用示例:
@PreAuthorize("hasRole('ADMIN') and @authz.check(authentication, 'admin:menu')")
@GetMapping("/admin/test")
public ResponseEntity<?> test() {
return ResponseEntity.ok("RBAC角色 + ABAC属性的混合校验");
}代码解释:
通过调用@authz传递当前用户认证信息 + permission = "admin:menu",查询出对应表达式(见文章开头的数据库截图),判断用户是否满足表达式条件。
4.2 自定义MethodSecurityExpressionHandler
在之前最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发的章节中已经演示过,这里不再赘述,感兴趣的小伙伴可以回顾之前的章节代码:
// 调用示例
@PreAuthorize("hasRole('ADMIN') and @abacDecisionEngine.check(authentication, 'admin:menu')")
@GetMapping("/admin/test")
public ResponseEntity<?> test() {
return ResponseEntity.ok("RBAC角色 + ABAC属性的混合校验");
}5. 高阶表达式技巧
下面演示几个高阶表达式技巧,小伙伴可以根据自己业务需求来调整使用。
5.1 多条件组合表达式
// 复杂逻辑组合
@PreAuthorize("(hasRole('ADMIN') and @env.isProduction()) or #forceOverride")
public void systemReboot(boolean forceOverride) {
// 系统重启操作
}5.2 时间维度控制
// 限制工作时间访问
@PreAuthorize("@timeValidator.isWorkHour()")
public void accessFinancialSystem() {
// 财务系统操作
}
// 定时权限校验
public class TimeValidator {
public boolean isWorkHour() {
LocalTime now = LocalTime.now();
return now.isAfter(LocalTime.of(9, 0))
&& now.isBefore(LocalTime.of(18, 0));
}
}5.3 安全审计集成
AuditLogger是我们Spring容器中的bean,调用logExportAttempt传递相关参数判断是否有相应权限:
// 权限校验与审计联动
@PreAuthorize("hasAuthority('DATA_EXPORT') and @auditLogger.logExportAttempt(#request)")
public void exportData(ExportRequest request) {
// 数据导出逻辑
}5.4 参数过滤与返回结果过滤
Spring Security支持@PreFilter与@PostFilter注解,对集合类型的参数或返回值进行过滤。这样可以确保传入的集合中只包含当前用户可以操作的文档,返回的集合中也仅包含公开或本人拥有的文档:
@PreFilter("filterObject.owner == authentication.principal.username")
@PostFilter("filterObject.visibility == 'PUBLIC' or filterObject.owner == authentication.principal.username")
@GetMapping("/documents")
public List<Document> getDocuments(@RequestBody List<Document> docs) {
return docs;
}5.5 动态资源权限控制
结合Spring Bean实现动态资源权限判断:
@Component("resourceChecker")
public class ResourceChecker {
public boolean canAccess(Long resourceId, String operation) {
// 从数据库查询资源权限配置
// 根据资源ID、操作类型、当前用户等动态判断
return true; // 示例返回
}
}
// 使用示例
@PreAuthorize("@resourceChecker.canAccess(#resourceId, 'EDIT')")
public void editResource(Long resourceId) {
// 编辑资源逻辑
}6. SpEL表达式执行原理简析
理解SpEL的执行原理有助于我们更好地运用它。SpEL表达式的执行主要包含以下几个步骤:
- 解析(Parsing):将表达式字符串解析为抽象语法树(AST)。
- 编译(Compilation):可选步骤,将表达式编译为字节码以提高性能。
- 评估(Evaluation):在给定的上下文中执行表达式,返回结果。
在Spring Security中,MethodSecurityExpressionHandler负责创建和管理表达式评估上下文,并将当前认证信息、方法参数等注入到上下文中,供表达式使用。
7. 性能优化建议
虽然SpEL非常强大,但过度使用或不当使用可能影响系统性能。以下是一些优化建议:
| 问题 | 建议 |
|---|---|
| 表达式重复解析 | 使用表达式缓存,避免重复解析相同表达式 |
| 复杂对象图导航 | 避免过深的对象属性访问链 |
| 频繁调用的方法 | 将复杂逻辑移到自定义Bean中,表达式只做简单调用 |
| 集合过滤 | 大数据量集合谨慎使用@PreFilter/@PostFilter |
8. 结语
SpEL在Spring Security中的应用使得权限控制不仅仅局限于静态的角色和权限匹配,而可以根据请求参数、用户属性、业务逻辑等灵活判断访问权限。通过灵活组合内置功能与自定义扩展,开发者可以实现从简单角色校验到复杂业务规则的全场景覆盖。
希望这个章节的内容能够帮助小伙伴们更深入地理解Spring Security权限表达式的底层原理与应用技巧,在实际项目中设计出更灵活高效的安全策略。如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,并三连给博主一点点鼓励!谢谢~
