
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页------Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
最新Spring Security实战教程(七)方法级安全控制@PreAuthorize注解的灵活运用
- [1. 前言](#1. 前言)
- [2. @PreAuthorize 注解简介](#2. @PreAuthorize 注解简介)
- [3. @PreAuthorize 核心原理解析](#3. @PreAuthorize 核心原理解析)
- 注解应用实战
-
- [❶ 基础权限校验](#❶ 基础权限校验)
- [❷ 参数级权限控制](#❷ 参数级权限控制)
- [❸ 动态业务规则集成](#❸ 动态业务规则集成)
- [❹ 复合条件表达式](#❹ 复合条件表达式)
- [❺ 返回值后校验](#❺ 返回值后校验)
- [❻ 参数预处理](#❻ 参数预处理)
- 结语
回顾链接:
最新Spring Security实战教程(一)初识Spring Security安全框架
最新Spring Security实战教程(二)表单登录定制到处理逻辑的深度改造
最新Spring Security实战教程(三)Spring Security 的底层原理解析
最新Spring Security实战教程(四)基于内存的用户认证
最新Spring Security实战教程(五)基于数据库的动态用户认证传统RBAC角色模型实战开发
最新Spring Security实战教程(六)最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发
专栏更新完毕后,博主将会上传所有章节代码到CSDN资源免费给大家下载,如你不想等后续章节代码需提前获取,可以私信或留言!
1. 前言
在实际项目中,安全控制不仅体现在 URL
拦截层面,方法级安全控制也越来越受到重视。Spring Security
提供了多种方式实现方法级安全,Spring Security
通过方法注解体系,这种细粒度控制使得我们能够在方法调用前、调用后,甚至返回值处理阶段实施安全检查,真正成为开发者保护服务接口的重要手段
相信小伙伴通过前面章节的学习,发现了博主在方法上进行角色和菜单资源验证的时候使用的一个注解:@PreAuthorize,那么本章节博主将带着大家剖析 @PreAuthorize 注解的核心原理、SpEL
表达式机制,并通过的示例代码演示如何在实际项目中灵活运用该注解实现细粒度的权限控制。
2. @PreAuthorize 注解简介
@PreAuthorize
注解可以在方法执行前对传入的参数、当前用户信息、认证状态等进行校验,从而决定是否允许方法执行。常见使用场景包括:
- 限制某个接口或方法只允许特定角色访问;
- 根据方法参数和认证信息动态判断权限;
- 调用自定义的权限判断逻辑(例如上一个章节中结合自定义 PermissionEvaluator);
Spring Security
内部通过 AOP
拦截被 @PreAuthorize
修饰的方法,并利用 Spring Expression Language(SpEL)
对注解中定义的表达式进行求值。只有当表达式求值结果为 true 时,方法才会执行,否则会抛出拒绝访问异常。
3. @PreAuthorize 核心原理解析
Spring Security
开启方法级安全控制实际上非常简单,只需要在 @Configuration
配置类中添加 @EnableMethodSecurity
java
@Configuration
//开启方法级的安全控制
@EnableMethodSecurity
public class AbacSecurityConfig {
//....
}
方法授权可以分为方法前授权 和方法后授权的组合,看下面的例子
java
@Service
public class MyCustomerService {
@PreAuthorize("hasAuthority('permission:read')")
@PostAuthorize("returnObject.owner == authentication.name")
public Customer readCustomer(String id) { ... }
}
当方法安全性被激活时,对 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
的,操作与上面相同,但是@PostAuthorizePostAuthorizeAuthorizationManager
- 如果评估通过(在这种情况下,返回值属于登录用户),则处理继续正常进行
- 如果不通过,拦截器将发布一个
AuthorizationDeniedEvent
并抛出一个AccessDeniedException
,然后捕获ExceptionTranslationFilter
并向响应返回403
状态代码
拦截与表达式求值
从上述官方介绍的工作流程来看,我们可以简单总结为:
Spring Security
在启用方法级安全时,会在应用上下文中配置一个 MethodSecurityInterceptor
(基于 AOP 实现)。当被 @PreAuthorize
修饰的方法被调用时:
- 拦截器捕获方法调用,并构造
EvaluationContext
,上下文中包含认证信息、方法参数等数据 - 使用
MethodSecurityExpressionHandler
将注解中的SpEL
表达式求值,判断是否满足访问条件 - 如果表达式结果为 false,则抛出
AccessDeniedException
否则放行执行方法

SpEL 表达式
@PreAuthorize
注解的值是一个 SpEL
表达式,可以引用以下内置变量:
- authentication:当前用户的认证对象。
- principal:当前认证用户的主体信息(通常为 UserDetails 对象)。
- #root:表达式根对象。
- 方法参数:可以通过 #paramName 或 #p0 访问方法参数。
如下代码
java
@PreAuthorize("hasRole('ADMIN') and #id > 10")
public void deleteUser(Long id) { ... }
表示只有当前用户拥有 ADMIN 角色且方法参数 id 大于 10 时,才能执行该方法。
自定义扩展
通过自定义 MethodSecurityExpressionHandler 和 PermissionEvaluator,可以扩展 @PreAuthorize 表达式功能。具体可以查阅上个章节ABAC属性权限模型实战开发
注解应用实战
下面通过一些简单示例,演示如何配置 Spring Security
方法级安全
❶ 基础权限校验
java
@PreAuthorize("hasRole('ADMIN')")
public void deleteResource(Long resourceId){
// 方法实现
}
@PreAuthorize("hasAuthority('RESOURCE_APPROVE')")
public void approveRequest(Request request){
// 审批逻辑
}
❷ 参数级权限控制
java
// 校验创建者匹配
@PreAuthorize("#article.createdBy == authentication.name")
public void updateArticle(Article article){
// 更新操作
}
// 参数过滤示例
@PreAuthorize("@permissionChecker.hasAccess(#userId, 'EDIT')")
public void editUserProfile(Long userId, Profile profile){
// 编辑逻辑
}
❸ 动态业务规则集成
如没有了解的小伙伴,建议查阅博主上一章节内容(这里仅演示如何配置@PreAuthorize):
最新Spring Security实战教程(六)最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发
java
public class DocumentPermissionEvaluator {
public boolean checkAccess(Long docId, String permission) {
// 自定义文档权限校验逻辑
}
}
// 在SpEL中调用自定义评估器
@PreAuthorize("@documentPermissionEvaluator.checkAccess(#docId, 'WRITE')")
public void updateDocument(Long docId, String content){
// 文档更新
}
❹ 复合条件表达式
java
// 组合多个条件
@PreAuthorize("hasRole('ADMIN') or (#user.department == authentication.user.department and hasAuthority('DEPT_ADMIN'))")
public void manageUser(User user){
// 用户管理逻辑
}
❺ 返回值后校验
java
@PostAuthorize("returnObject.owner == authentication.name")
public Document getConfidentialDocument(Long id){
// 获取文档逻辑
}
❻ 参数预处理
java
@PreFilter("filterObject.owner == authentication.name")
public void batchProcess(List<Document> documents){
// 仅处理当前用户拥有的文档
}
结语
通过本章节方法级安全控制的介绍,相信大家已经能通过灵活运用表达式语言和自定义扩展,让我们可以在保证系统安全性的同时,维持代码的优雅与可维护性!希望这章节文章对你在 Spring Security
方法级安全控制的实践中提供帮助和启发!
如果本本章内容对您有所帮助,希望 一键三连 给博主一点点鼓励,如果您有任何疑问或建议,请随时留言讨论!
下一章:最新Spring Security实战教程(八)Remember-Me实现原理 - 持久化令牌与安全存储方案
