🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
最新Spring Security实战教程(十)权限表达式进阶 - 在SpEL在安全控制中的高阶魔法
回顾链接:
最新Spring Security实战教程(一)初识Spring Security安全框架
最新Spring Security实战教程(二)表单登录定制到处理逻辑的深度改造
最新Spring Security实战教程(三)Spring Security 的底层原理解析
最新Spring Security实战教程(四)基于内存的用户认证
最新Spring Security实战教程(五)基于数据库的动态用户认证传统RBAC角色模型实战开发
最新Spring Security实战教程(六)最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发
最新Spring Security实战教程(七)方法级安全控制@PreAuthorize注解的灵活运用
最新Spring Security实战教程(八)Remember-Me实现原理 - 持久化令牌与安全存储方案
最新Spring Security实战教程(九)前后端分离认证实战 - JWT+SpringSecurity无缝整合
专栏更新完毕后,博主将会上传所有章节代码到CSDN资源免费给大家下载,如你不想等后续章节代码需提前获取,可以私信或留言!
1. 前言
博主在持续更新 Spring Security
教程过程中,有一些小伙伴总会私信问到之前关于ABAC属性权限模型 那一个章节中自定义策略条件表达式的问题,如下图:
温馨提示:
没了解ABAC属性权限模型的可以访问:最新Spring Security实战教程(六)最新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', '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) { /*...*/ }
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实战教程(六)最新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;
}
6. 结语
SpEL
在 Spring Security
中的应用使得权限控制不仅仅局限于静态的角色和权限匹配,而可以根据请求参数、用户属性、业务逻辑等灵活判断访问权限。通过灵活组合内置功能与自定义扩展,开发者可以实现从简单角色校验到复杂业务规则的全场景覆盖。
希望这个章节的内容能够帮助小伙伴们更深入地理解 Spring Security
权限表达式的底层原理与应用技巧,在实际项目中设计出更灵活高效的安全策略。如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,并三连给博主一点点鼓励 !谢谢~
下一章节:最新Spring Security实战教程(十一)OAuth2.0精讲 - 四种授权模式与资源服务器搭建