从
@EnableMethodSecurity注解到@PreAuthorize的 AOP 拦截,再到 SpEL 表达式解析------方法级授权的底层机制远比表面看到的复杂。本文还将介绍两种生产级自定义授权方案:SpEL 调用自定义 Bean 和自定义 AuthorizationManager。
前言
前一篇我们拆解了 URL 授权的全链路。但 URL 授权只能做到路径级别的粗粒度控制,真正的业务权限往往需要精确到方法级别------"这个用户能不能修改这条记录?"这类问题,URL 授权回答不了。
Spring Security 的方法安全授权就是为此而生。
一、@EnableMethodSecurity:方法安全的启用原理
1.1 启用注解
java
@Configuration
@EnableMethodSecurity // Spring Security 6.x 推荐方式
public class SecurityConfig {
// ...
}
注意:
@EnableGlobalMethodSecurity在 6.x 中已废弃,由@EnableMethodSecurity替代。
1.2 @EnableMethodSecurity 的内部机制
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MethodSecuritySelector.class) // 唯一的静态导入,其他配置由 Selector 动态决定
public @interface EnableMethodSecurity {
boolean prePostEnabled() default true; // 启用 @PreAuthorize / @PostAuthorize
boolean securedEnabled() default false; // 启用 @Secured
boolean jsr250Enabled() default false; // 启用 @RolesAllowed
boolean proxyTargetClass() default false; // CGLIB vs JDK 动态代理
AdviceMode mode() default AdviceMode.PROXY; // 代理模式:PROXY / ASPECTJ
int offset() default 0; // 拦截器顺序偏移量(6.3+)
}
1.3 核心组件链路
less
@EnableMethodSecurity @Import(MethodSecuritySelector.class)
│
└── MethodSecuritySelector.selectImports()
│
├── AutoProxyRegistrar → 注册 AnnotationAwareAspectJAutoProxyCreator
├── MethodSecurityAdvisorRegistrar → 注册切面 Advisor
│
├── [prePostEnabled=true] → PrePostMethodSecurityConfiguration
│ ├── PreAuthorizeAuthorizationManager
│ ├── PostAuthorizeAuthorizationManager
│ ├── PreFilterAuthorizationMethodInterceptor
│ └── PostFilterAuthorizationMethodInterceptor
│
├── [securedEnabled=true] → SecuredMethodSecurityConfiguration
├── [jsr250Enabled=true] → Jsr250MethodSecurityConfiguration
│
├── AuthorizationProxyConfiguration(处理 @AuthorizeReturnObject)
├── [条件] AuthorizationProxyDataConfiguration(Spring Data 环境)
├── [条件] AuthorizationProxyWebConfiguration(Spring Web 环境)
└── [条件] MethodObservationConfiguration(Micrometer 环境)
1.4 MethodSecuritySelector 完整源码:动态导入配置类
java
final class MethodSecuritySelector implements ImportSelector {
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
@Override
public String[] selectImports(AnnotationMetadata importMetadata) {
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())
&& !importMetadata.hasMetaAnnotation(EnableMethodSecurity.class.getName())) {
return new String[0];
}
EnableMethodSecurity annotation = importMetadata.getAnnotations()
.get(EnableMethodSecurity.class).synthesize();
List<String> imports = new ArrayList<>(
Arrays.asList(this.autoProxy.selectImports(importMetadata)));
// 根据注解属性,动态决定导入哪些配置类
if (annotation.prePostEnabled()) {
imports.add(PrePostMethodSecurityConfiguration.class.getName());
}
if (annotation.securedEnabled()) {
imports.add(SecuredMethodSecurityConfiguration.class.getName());
}
if (annotation.jsr250Enabled()) {
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
}
// AuthorizationProxyConfiguration:处理 @AuthorizeReturnObject(返回值授权)
imports.add(AuthorizationProxyConfiguration.class.getName());
return imports.toArray(new String[0]);
}
// 内部类:根据 AdviceMode 导入 AOP 代理注册器
private static final class AutoProxyRegistrarSelector
extends AdviceModeImportSelector<EnableMethodSecurity> {
private static final String[] IMPORTS = new String[] {
AutoProxyRegistrar.class.getName(), // 注册 AnnotationAwareAspectJAutoProxyCreator
MethodSecurityAdvisorRegistrar.class.getName() // 注册切面 Advisor
};
private static final String[] ASPECTJ_IMPORTS = new String[] {
MethodSecurityAspectJAutoProxyRegistrar.class.getName()
};
@Override
protected String[] selectImports(AdviceMode adviceMode) {
if (adviceMode == AdviceMode.PROXY) return IMPORTS;
if (adviceMode == AdviceMode.ASPECTJ) return ASPECTJ_IMPORTS;
throw new IllegalStateException("AdviceMode not supported");
}
}
}
1.5 导入的配置类职责速查
| 组件 | 类型 | 职责 |
|---|---|---|
MethodSecuritySelector |
ImportSelector |
根据 @EnableMethodSecurity 属性,动态决定导入哪些配置类 |
PrePostMethodSecurityConfiguration |
@Configuration |
创建 4 个核心拦截器:preAuthorize / postAuthorize / preFilter / postFilter |
AutoProxyRegistrar |
注册器 | 注册 AnnotationAwareAspectJAutoProxyCreator------AOP 代理创建的总指挥 |
MethodSecurityAdvisorRegistrar |
注册器 | 将方法级安全拦截器包装成 Spring AOP 能识别的 Advisor |
AnnotationAwareAspectJAutoProxyCreator |
BeanPostProcessor |
在 Bean 初始化后检查是否需要创建代理,匹配的 Bean 会被包装 |
AuthorizationProxyConfiguration |
@Configuration |
处理 @AuthorizeReturnObject------返回值对象的细粒度数据访问控制 |
1.6 AOP 代理创建的完整时序
- Spring 容器创建业务 Bean (如
OrderService) AnnotationAwareAspectJAutoProxyCreator(实现了BeanPostProcessor)在 Bean 初始化后介入- 拿出所有 Advisor,逐一比对
OrderService的方法 - 发现
deleteOrder(Long id)方法上有@PreAuthorize注解 → 匹配成功! - 使用
ProxyFactory创建代理对象(JDK 动态代理或 CGLIB) - 将
AuthorizationManagerBeforeMethodInterceptor织入代理对象的拦截器链 - Spring 容器中最终持有的
orderService引用指向这个代理对象

1.7 方法调用时的 AOP 拦截流程
当代理对象上的方法被实际调用时,AOP 的拦截逻辑开始运作:
scss
调用 orderService.getOrder(123)
│
▼
AOP 代理(AnnotationAwareAspectJAutoProxyCreator 创建)
│
▼
AuthorizationManagerBeforeMethodInterceptor.invoke() // 6.x 替代旧版 MethodSecurityInterceptor
│
├── 获取注解 → 匹配 Pointcut(注解匹配或表达式匹配)
├── 调用 AuthorizationManager.authorize() 进行授权检查
│ ├── GRANTED → 继续执行目标方法
│ └── DENIED → 抛出 AuthorizationDeniedException
│
▼
目标方法执行
二、PrePostMethodSecurityConfiguration:4 个拦截器的诞生地
PrePostMethodSecurityConfiguration 是方法安全的核心配置类,负责创建处理 @PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter 四个注解的 4 个核心拦截器。
核心构造函数:
java
PrePostMethodSecurityConfiguration(
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> preAuthorizeProcessor,
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>>> postAuthorizeProcessor) {
// ① 设置 ExpressionHandler(表达式解析引擎)
this.preFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.postAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.postFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
// ② 构建 preAuthorize 拦截器
AuthorizationManager<MethodInvocation> preAuthorize = preAuthorizeProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.preAuthorizeAuthorizationManager);
this.preAuthorizeMethodInterceptor =
AuthorizationManagerBeforeMethodInterceptor.preAuthorize(preAuthorize);
// ③ 构建 postAuthorize 拦截器
AuthorizationManager<MethodInvocationResult> postAuthorize = postAuthorizeProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.postAuthorizeAuthorizationManager);
this.postAuthorizeMethodInterceptor =
AuthorizationManagerAfterMethodInterceptor.postAuthorize(postAuthorize);
}
4 个拦截器的注册方式 ------注意它们都是通过 DeferringMethodInterceptor 延迟加载的:
java
@Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<PrePostMethodSecurityConfiguration> config) {
// DeferringMethodInterceptor:延迟加载,避免循环依赖
return new DeferringMethodInterceptor<>(preAuthorizePointcut,
() -> config.getObject().preAuthorizeMethodInterceptor);
}
为什么用 DeferringMethodInterceptor? 因为 PrePostMethodSecurityConfiguration 本身也是 Bean,直接返回拦截器实例容易导致循环依赖。DeferringMethodInterceptor 告诉 AOP 框架"这个切面的切点是 preAuthorizePointcut,具体的拦截逻辑请到时候再找我要"。
2.1 AuthorizationManagerBeforeMethodInterceptor.preAuthorize() 构建拦截器
java
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
AuthorizationManager<MethodInvocation> authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor =
new AuthorizationManagerBeforeMethodInterceptor(
// 切点:所有带 @PreAuthorize 注解的方法
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class),
authorizationManager); // PreAuthorizeAuthorizationManager
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
return interceptor;
}
2.2 拦截器的核心拦截逻辑:attemptAuthorization()
AuthorizationManagerBeforeMethodInterceptor 的核心在于 attemptAuthorization() 方法,它负责调用 AuthorizationManager 进行授权决策:
java
// invoke → 直接委托给 attemptAuthorization
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
return attemptAuthorization(mi);
}
// 核心授权逻辑
private Object attemptAuthorization(MethodInvocation mi) throws Throwable {
// ① 调用 AuthorizationManager.authorize() 进行授权决策
AuthorizationResult result;
try {
result = this.authorizationManager.authorize(this::getAuthentication, mi);
}
catch (AuthorizationDeniedException denied) {
return handle(mi, denied); // 授权管理器直接抛出拒绝异常
}
// ② 发布授权事件(可被监控系统采集)
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, mi, result);
// ③ 授权拒绝 → 委托给 MethodAuthorizationDeniedHandler
if (result != null && !result.isGranted()) {
return handle(mi, result);
}
// ④ 授权通过 → 继续执行目标方法
return proceed(mi);
}
// 拒绝处理:委托给 MethodAuthorizationDeniedHandler(默认抛 AuthorizationDeniedException)
private Object handle(MethodInvocation mi, AuthorizationResult result) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(mi, result);
}
return this.defaultHandler.handleDeniedInvocation(mi, result);
}
// 执行目标方法:若方法执行过程中抛出 AuthorizationDeniedException 也会被捕获处理
private Object proceed(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (AuthorizationDeniedException ex) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handleDeniedInvocation(mi, ex);
}
return this.defaultHandler.handleDeniedInvocation(mi, ex);
}
}
// 从 SecurityContextHolder 获取 Authentication
private Authentication getAuthentication() {
Authentication authentication = this.securityContextHolderStrategy.get()
.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
三、SpEL 表达式解析全链路:从 hasRole 到权限决策
3.1 PreAuthorizeAuthorizationManager:表达式执行引擎
java
public final class PreAuthorizeAuthorizationManager
implements AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
private PreAuthorizeExpressionAttributeRegistry registry =
new PreAuthorizeExpressionAttributeRegistry();
@Override
public AuthorizationDecision check(
Supplier<Authentication> authentication, MethodInvocation mi) {
// ① 获取方法上缓存的 SpEL 表达式
ExpressionAttribute attribute = this.registry.getAttribute(mi);
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { // NULL_ATTRIBUTE 常量
return null; // 方法上没有 @PreAuthorize 注解
}
// ② 创建 SpEL 评估上下文(包含 authentication + 方法参数 + BeanResolver)
EvaluationContext ctx = this.registry.getExpressionHandler()
.createEvaluationContext(authentication, mi);
// ③ 执行 SpEL 表达式,返回 AuthorizationDecision
return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
}
}
3.2 AbstractExpressionAttributeRegistry:表达式缓存机制
java
abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {
// ConcurrentHashMap 缓存:方法 → 已解析的表达式
private final Map<MethodClassKey, T> cachedAttributes = new ConcurrentHashMap<>();
private MethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
final T getAttribute(MethodInvocation mi) {
Method method = mi.getMethod();
Object target = mi.getThis();
Class<?> targetClass = (target != null) ? target.getClass() : null;
return getAttribute(method, targetClass);
}
final T getAttribute(Method method, Class<?> targetClass) {
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
// computeIfAbsent:第一次调用时解析并缓存,后续直接从缓存取
return this.cachedAttributes.computeIfAbsent(cacheKey,
(k) -> resolveAttribute(method, targetClass));
}
}
缓存设计 :同一方法的 SpEL 表达式只会被解析一次,后续调用直接从 ConcurrentHashMap 中获取,避免重复解析的性能开销。
3.3 DefaultMethodSecurityExpressionHandler:构建 SpEL 运行环境
java
public class DefaultMethodSecurityExpressionHandler
extends AbstractSecurityExpressionHandler<MethodInvocation> {
@Override
public EvaluationContext createEvaluationContext(
Supplier<? extends Authentication> authentication, MethodInvocation mi) {
// ① 创建根对象(SecurityExpressionRoot),注入 authentication
MethodSecurityExpressionOperations root =
createSecurityExpressionRoot(authentication, mi);
// ② 创建评估上下文,绑定方法参数为 SpEL 变量(支持 #id, #name 等)
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(
root, mi, getParameterNameDiscoverer());
// ③ 设置 BeanResolver(支持 @beanName 语法)
ctx.setBeanResolver(getBeanResolver());
return ctx;
}
}
3.4 SecurityExpressionRoot:hasRole 到底调用了什么?
这是理解 SpEL 表达式执行链最关键的一环。 当你写 hasRole('ADMIN') 时,SpEL 引擎最终会调用 SecurityExpressionRoot 上的方法(该方法由 MethodSecurityExpressionRoot 继承):
java
public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
private final Supplier<Authentication> authentication;
private RoleHierarchy roleHierarchy;
private Set<String> roles;
private String defaultRolePrefix = "ROLE_"; // 默认角色前缀
// hasRole('ADMIN') → hasAnyRole("ADMIN") → hasAnyAuthorityName("ROLE_", "ADMIN")
public final boolean hasRole(String role) {
return hasAnyRole(role);
}
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(this.defaultRolePrefix, roles);
}
public final boolean hasAuthority(String authority) {
return hasAnyAuthority(authority);
}
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
}
// 核心:从 Authentication 中提取权限集合,应用 RoleHierarchy,逐个比对
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = getAuthoritySet(); // ① 懒加载:从 Authentication 获取 authorities
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role); // ② ROLE_ + ADMIN
if (roleSet.contains(defaultedRole)) { // ③ 是否包含 ROLE_ADMIN
return true;
}
}
return false;
}
// 获取权限集合(懒加载 + RoleHierarchy 支持)
private Set<String> getAuthoritySet() {
if (this.roles == null) {
Collection<? extends GrantedAuthority> userAuthorities = getAuthentication().getAuthorities();
if (this.roleHierarchy != null) {
userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
}
this.roles = AuthorityUtils.authorityListToSet(userAuthorities);
}
return this.roles;
}
}
关键差异 :与 URL 授权不同,SpEL 中的
hasRole()不经过AuthorizationManager包装,而是直接在表达式中返回boolean值。表达式hasRole('ADMIN')的返回值直接被 SpEL 引擎解释为授权决策(true= 通过,false= 拒绝)。
3.5 表达式执行全链路总结
less
@PreAuthorize("hasRole('ADMIN')")
│
▼
PreAuthorizeAuthorizationManager.check()
│
├── registry.getAttribute(mi) → 从缓存获取 SpEL 表达式
├── expressionHandler.createEvaluationContext() → 构建上下文
│ ├── 创建 MethodSecurityExpressionRoot(继承 SecurityExpressionRoot)
│ ├── 绑定方法参数为 SpEL 变量(#id)
│ └── 设置 BeanResolver(支持 @beanName)
│
└── ExpressionUtils.evaluate()
│
└── SpEL 引擎执行 "hasRole('ADMIN')"
│
└── SecurityExpressionRoot.hasRole("ADMIN")
│
└── hasAnyRole("ADMIN")
│
└── hasAnyAuthorityName("ROLE_", "ADMIN")
│
└── getAuthoritySet() → roleSet.contains("ROLE_ADMIN")
│
├── true → 返回 true → GRANTED
└── false → 返回 false → DENIED → AuthorizationDeniedException

一句话总结整个链路:"表达式 → SpEL 根对象 → 权限集合比对 → 决策"
3.6 使用示例
java
@Service
public class OrderService {
@PreAuthorize("hasRole('ADMIN')")
public Order getOrder(Long id) {
// 只有 ADMIN 角色可以访问
}
@PreAuthorize("hasAuthority('order:delete')")
public void deleteOrder(Long id) {
// 需要 order:delete 权限
}
@PreAuthorize("@orderSecurity.canView(authentication, #id)")
public Order viewOrder(Long id) {
// 调用自定义 Bean 的方法进行细粒度校验
}
}
3.7 补充:SpEL 表达式扩展上下文
3.7.1 安全 SpEL 上下文
@PreAuthorize 中的 SpEL 表达式在一个特殊的上下文中执行,该上下文预定义了一些变量和函数:
| 变量/函数 | 说明 | 示例 |
|---|---|---|
authentication |
当前认证对象 | authentication.name |
principal |
当前用户主体 | principal.username |
hasRole(role) |
检查角色 | hasRole('ADMIN') |
hasAuthority(auth) |
检查权限 | hasAuthority('order:delete') |
hasAnyRole(...) |
检查任一角色 | hasAnyRole('USER', 'ADMIN') |
hasAnyAuthority(...) |
检查任一权限 | hasAnyAuthority('read', 'write') |
permitAll |
始终允许 | permitAll |
denyAll |
始终拒绝 | denyAll |
isAuthenticated() |
是否已认证 | isAuthenticated() |
isAnonymous() |
是否匿名 | isAnonymous() |
#变量名 |
方法参数引用 | #id |
3.7.2 SpEL 解析上下文构建
java
// DefaultMethodSecurityExpressionHandler 的核心实现(实际源码简化版)
public class DefaultMethodSecurityExpressionHandler
extends AbstractSecurityExpressionHandler<MethodInvocation>
implements MethodSecurityExpressionHandler {
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultSecurityParameterNameDiscoverer();
@Override
public EvaluationContext createEvaluationContext(
Supplier<Authentication> authentication, MethodInvocation mi) {
// 1. 创建 MethodSecurityExpressionRoot(继承 SecurityExpressionRoot)
MethodSecurityExpressionOperations root = createSecurityExpressionRoot(authentication, mi);
// 内部会设置:permissionEvaluator, trustResolver, roleHierarchy, defaultRolePrefix
// 2. 创建 MethodSecurityEvaluationContext(自动绑定方法参数)
MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(
root, mi, getParameterNameDiscoverer());
// MethodSecurityEvaluationContext 构造函数内部自动:
// - 遍历方法参数,通过 ParameterNameDiscoverer 获取参数名
// - 调用 ctx.setVariable(paramName, argValue) 注册为 SpEL 变量
// 3. 设置 BeanResolver(支持 @beanName 语法)
ctx.setBeanResolver(getBeanResolver());
return ctx;
}
}
3.7.3 @beanName.method() 语法的底层处理
当 SpEL 表达式中出现 @orderSecurity.canView(authentication, #id) 时:
@前缀表示引用 Spring BeanbeanResolver通过 BeanFactory 查找名为orderSecurity的 Bean- 调用该 Bean 的
canView方法 - 传入
authentication和#id(方法参数)
java
// BeanResolver 通过 Spring 的 BeanFactory 查找 Bean(Spring Framework 内置实现)
public class BeanFactoryResolver implements BeanResolver {
private final BeanFactory beanFactory;
@Override
public Object resolve(EvaluationContext context, String beanName) {
return this.beanFactory.getBean(beanName);
}
}
四、自定义授权方案一:SpEL 调用自定义 Bean
4.1 实现方式
java
@Component("orderSecurity")
public class OrderSecurity {
@Autowired
private OrderRepository orderRepository;
public boolean canView(Authentication authentication, Long orderId) {
// 1. 查询订单
Order order = orderRepository.findById(orderId).orElse(null);
if (order == null) return false;
// 2. 检查是否是订单所有者或管理员
return order.getOwner().equals(authentication.getName())
|| authentication.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
}
public boolean canModify(Authentication authentication, Long orderId) {
// 更复杂的业务规则
// ...
}
}
4.2 使用方式
java
@Service
public class OrderService {
@PreAuthorize("@orderSecurity.canView(authentication, #id)")
public Order viewOrder(Long id) { ... }
@PreAuthorize("@orderSecurity.canModify(authentication, #id)")
public Order updateOrder(Long id, OrderDTO dto) { ... }
}
4.3 优缺点
| 维度 | 优点 | 缺点 |
|---|---|---|
| 灵活性 | 极高,可编写任意业务逻辑 | SpEL 表达式较长 |
| 可测试性 | 可以单独测试 Security Bean | 需要额外 Bean |
| 性能 | 每次调用都执行 SpEL 解析 | SpEL 解析有一定开销 |
| 可维护性 | 集中管理权限逻辑 | SpEL 字符串不易重构 |
五、自定义授权方案二:自定义 AuthorizationManager
5.1 方案设计
这是 Spring Security 6.x 推荐的方式,通过自定义 AuthorizationManager<MethodInvocation> 实现更类型安全的授权逻辑。
Step 1:自定义注解
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireOwner {
String resourceParam() default "id";
}
注意:这里不需要加
@PreAuthorize("true"),因为我们会在 Step 3 中通过自定义 Advisor 直接拦截此注解,避免与默认的 PreAuthorize 拦截器产生双重拦截。
Step 2:自定义 AuthorizationManager
java
@Component
public class ResourceOwnerAuthorizationManager
implements AuthorizationManager<MethodInvocation> {
@Autowired
private OrderRepository orderRepository;
@Override
public AuthorizationDecision check(
Supplier<Authentication> authentication, MethodInvocation invocation) {
// 1. 获取方法上的注解
RequireOwner requireOwner = invocation.getMethod()
.getAnnotation(RequireOwner.class);
if (requireOwner == null) {
return new AuthorizationDecision(true);
}
// 2. 获取资源 ID 参数
String paramName = requireOwner.resourceParam();
Object resourceId = getParameterValue(invocation, paramName);
// 3. 查询资源
Order order = orderRepository.findById((Long) resourceId).orElse(null);
if (order == null) {
return new AuthorizationDecision(false);
}
// 4. 检查是否是资源所有者
Authentication auth = authentication.get();
boolean isOwner = order.getOwner().equals(auth.getName());
boolean isAdmin = auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
return new AuthorizationDecision(isOwner || isAdmin);
}
private Object getParameterValue(MethodInvocation invocation, String paramName) {
// 解析方法参数值
// 注意:JDK 默认不保留参数名,需要在编译时加 -parameters 参数,
// 或使用 Spring 的 ParameterNameDiscoverer(如 DefaultSecurityParameterNameDiscoverer)来获取参数名
Parameter[] parameters = invocation.getMethod().getParameters();
Object[] arguments = invocation.getArguments();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].getName().equals(paramName)) {
return arguments[i];
}
}
return null;
}
}
Step 3:注册拦截器
java
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
@Autowired
private ResourceOwnerAuthorizationManager resourceOwnerAuthManager;
@Bean
public Advisor resourceOwnerAdvisor() {
// 定义切点:匹配带有 @RequireOwner 注解的方法
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(
"@annotation(com.example.RequireOwner)");
// 创建拦截器(6.x 推荐 API,替代已废弃的 MethodSecurityInterceptor)
AuthorizationManagerBeforeMethodInterceptor interceptor =
new AuthorizationManagerBeforeMethodInterceptor(pointcut, resourceOwnerAuthManager);
return new DefaultPointcutAdvisor(pointcut, interceptor);
}
}
Step 4:使用
java
@Service
public class OrderService {
@RequireOwner(resourceParam = "id")
public Order updateOrder(Long id, OrderDTO dto) {
// 自动检查是否是资源所有者
}
}
5.2 两种自定义方案对比
| 维度 | SpEL 调用 Bean | 自定义 AuthorizationManager |
|---|---|---|
| 类型安全 | 弱(SpEL 字符串) | 强(Java 代码) |
| 编译期检查 | 无 | 有 |
| IDE 支持 | 有限(字符串无补全) | 完整(Java 代码补全) |
| 可测试性 | 一般 | 好 |
| 复杂度 | 低 | 中等 |
| Spring Security 推荐度 | 3.x/5.x 时代主流 | 6.x/7.x 推荐方式 |
| 适用场景 | 简单的权限校验 | 复杂的业务权限逻辑 |
六、@PreAuthorize 完整执行链路

七、专栏总结:Spring Security 核心知识图谱
从第一篇的"过滤器链全景图"到这最后一篇的"方法安全 AOP 全链路",我们完成了一条完整的学习路径:
java
★ 第一层次:宏观架构
├── 01 过滤器链全景 → FilterOrderRegistration: 30+ 过滤器的"宪法"
├── 02 构建过程 → HttpSecurity Builder 模式: doBuild 三步曲
└── 03 FilterChainProxy → 总入口: 路由 + 防火墙 + 虚拟链驱动
★ 第二层次:认证体系
├── 04 认证器创建 → AuthenticationConfiguration → ProviderManager
├── 05 表单登录流程 → UsernamePasswordAuthenticationFilter 全链路
└── 06 会话与JWT → Session vs JWT 选型 + 自定义过滤器实战
★ 第三层次:授权体系
├── 07 URL 授权 → AuthorizeHttpRequestsConfigurer → AuthorizationFilter
└── 08 方法安全 → @EnableMethodSecurity → AOP → SpEL 全链路
核心组件职责速查
| 组件 | 层次 | 一句话职责 |
|---|---|---|
FilterOrderRegistration |
定义层 | 30+ 过滤器的执行顺序"宪法" |
HttpSecurity |
构建层 | 收集 Configurer,通过 doBuild 构建过滤器链 |
FilterChainProxy |
入口层 | 多链路由 + 防火墙 + 虚拟链驱动 |
ProviderManager |
认证层 | 遍历 AuthenticationProvider,委托认证 |
DaoAuthenticationProvider |
认证层 | UserDetailsService 查用户 + PasswordEncoder 验密码 |
AuthorizationFilter |
授权层(URL) | 根据 authorizeHttpRequests 规则校验 URL 权限 |
PreAuthorizeAuthorizationManager |
授权层(方法) | 解析 @PreAuthorize 的 SpEL 表达式并执行权限决策 |
SecurityExpressionRoot |
授权层(方法) | hasRole/hasAuthority 等 SpEL 函数的底层实现 |
自定义扩展路径图
java
简单场景:自定义 UserDetailsService → 实现 loadUserByUsername
↓
中等场景:自定义过滤器 → 继承 OncePerRequestFilter → addFilterBefore
↓
复杂场景:@PreAuthorize("@bean.method(authentication, #param)") → SpEL 调用 Bean
↓
生产级场景:自定义注解 + 自定义 AuthorizationManager → 注册 Advisor
专栏完结:从过滤器链全景到方法级授权,本专栏完整覆盖了 Spring Security 6.x 的核心源码与实战配置。掌握这 8 篇文章的内容,你不仅能在面试中对 Spring Security 的内部机制对答如流,更能在生产环境中从容应对各种安全需求------从传统的表单登录到前后端分离的 JWT 方案,从 URL 级别的粗粒度授权到方法级别的细粒度业务权限控制。