继续接着上
授权管理实例
RBAC
在之前的设计中,我们严格按照了 RBAC 模型设计了三层关系
用户 User:多对多关联角色,也就是用户可以被赋予一个或多个角色
1
2
3
4
5
6
7
8
9
10
...
private Set<Role> roles = new HashSet<>();角色 Role:承上启下,连接用户和权限
1
2
3
4
5
6
7
8
9
10
11
12
13
...
private Set<User> users = new HashSet<>();
private Set<Permission> permissions = new HashSet<>();权限 Permission:真正的访问控制单元,实际上,一个权限不只是一个名字,而是 HTTP 动作 + 资源路径 的组合
1
2
3
4
5
6
7
8
9
private String resource; // 例如 /api/management
private String action; // GET / POST / PUT / DELETE
public String getAuthority() {
return action + ":" + resource; // 例如 GET:/api/management
}
那么串起来整体的授权链路
认证阶段,Spring Security 调用
CustomUserDetailsService加载用户和其GrantedAuthority。(Spring Security内置的权限类)1
2
3
4
5
6
7
8User user = userRepository.findByUsernameWithRolesAndPermissions(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
// 角色 -> ROLE_xxx
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
// 权限 -> ACTION:RESOURCE
String authority = permission.getAuthority(); // e.g. "GET:/api/users"
authorities.add(new SimpleGrantedAuthority(authority));用户登录后,Spring Security 里这个用户就带着两类权限:
角色型权限:ROLE_ADMIN, ROLE_USER …(你可以用来做 hasRole(“ADMIN”) 之类的判断)
资源权限:GET:/api/users、POST:/api/role-assignment/** …(用于精确控制 URL 访问)
决策前,元数据源
CustomFilterInvocationSecurityMetadataSource根据当前请求 URL + Method,从 DB 里查出访问这个 URL 需要哪些权限。1
2
3
4
5
6
7
8
9
10
11String requestUrl = filterInvocation.getRequestUrl();
String httpMethod = filterInvocation.getRequest().getMethod();
...
for (Permission permission : allPermissions) {
if (pathMatcher.match(permission.getResource(), requestUrl) &&
permission.getAction().equalsIgnoreCase(httpMethod)) {
String authority = permission.getAuthority(); // ACTION:RESOURCE
configAttributes.add(new SecurityConfig(authority));
}
}- 每次请求进来,这里都会遍历 DB 中的 Permission 表,看是否存在某行记录的 (action, resource) 能匹配当前请求。匹配成功就生成一个 ConfigAttribute,内容就是 GET:/api/users 这样的字符串。
- 如果完全没匹配到,返回 null,表示这个 URL 不需要特殊权限(没有配置则默认放行)。
决策器,AccessDecisionManager是
CustomAccessDecisionManager比较「用户拥有的权限」和「资源需要的权限」,决定放行/拒绝。1
2
3
4
5
6
7
8
9
10
11for (ConfigAttribute configAttribute : configAttributes) {
String needAuthority = configAttribute.getAttribute();
for (GrantedAuthority authority : authorities) {
if (needAuthority.equals(authority.getAuthority())) {
// 拥有需要的权限,放行
return;
}
}
}
throw new AccessDeniedException("访问被拒绝:权限不足");只要用户的 GrantedAuthority 集合里包含任意一个所需的 ConfigAttribute,就允许访问,否则抛 AccessDeniedException,返回 403。
那么即使这么写,也是需要进行配置类的配置的,也就是 URL 授权,因为大多数情况下是两者搭配使用
1 |
|
最外层规则:
/api/public/**、/login、/logout这种必须开放的出入口这种方法,必须谁都能用,直接permitAll()。其余所有请求
anyRequest().authenticated()—— 进入认证/授权流程。
我们注册了自定义的
FilterSecurityInterceptor,其内部就会调用:CustomFilterInvocationSecurityMetadataSource去要 这次请求需要什么权限然后拿到了需要什么,就轮到
CustomAccessDecisionManager做“有/没有”判断。
官方文档中,这一段对应的是配置 SecurityFilterChain 并插入自定义 FilterSecurityInterceptor 的部分,这种写法是 Spring Security 6.x 的推荐风格。
基于方法的授权
基于方法授权的四大注解
除了在请求级别建模授权之外,Spring Security 还支持在方法级别建模授权。
主要使用的注解就这四个, @PreAuthorize、@PostAuthorize、@PreFilter
和 @PostFilter,而且第一个最重要
@PreAuthorize:方法执行前授权- 在方法被调用之前进行权限验证,只有验证通过,方法才会执行;如果验证失败,直接抛出
AccessDeniedException(访问被拒绝),方法不会执行。它可以在方法执行前拦截非法请求,避免不必要的资源消耗。 @PreAuthorize的参数是Spring Security 表达式(SpEL)hasRole('ADMIN'):判断用户是否拥有指定角色(注意:Spring Security 会自动给角色加ROLE_前缀,比如hasRole('ADMIN')等价于判断是否有ROLE_ADMIN角色)。hasAuthority('file:upload'):判断用户是否拥有指定权限principal:代表当前登录用户的认证对象(Authentication),可以获取用户信息,比如principal.username获取用户名。#参数名:引用方法的参数,比如#userId引用方法的userId参数。和逻辑运算符:&&(且)、||(或)、!(非)。
- 在方法被调用之前进行权限验证,只有验证通过,方法才会执行;如果验证失败,直接抛出
@PostAuthorize:方法执行后授权- 先执行方法,再在方法执行完成后进行权限验证。如果验证失败,依然会抛出
AccessDeniedException,但此时方法已经执行完毕,有人说这个极少用,但是我比较爱用,但是请务必注意,这个注解不能阻止方法执行,只能在方法执行后判断是否允许返回结果,因此仅适用于无副作用的方法(比如查询方法),绝对不能用于有修改操作的方法(比如新增、删除、更新)。 @PostAuthorize中可以通过returnObject引用方法的返回值,这是它的核心特性。例如,想要用户查询自己的信息验证是否是自己的信息或自己是否是管理员,就可以这样写@PostAuthorize("hasRole('ADMIN') || returnObject.id == principal.id")
- 先执行方法,再在方法执行完成后进行权限验证。如果验证失败,依然会抛出
@PreFilter:方法执行前过滤集合参数在方法执行前,对方法的集合类型参数进行过滤,只保留符合条件的元素,再将过滤后的集合传入方法。这个我是真没咋用过
参数
value:过滤条件(SpEL 表达式),用filterObject表示集合中的每个元素。filterTarget:如果方法有多个集合参数,指定要过滤的参数名(必选)。1
2
3
4
5
6
7
8
9
10
11
12
13// 示例1:过滤单个集合参数(无需filterTarget)
// 场景:只保留金额大于100的订单(filterObject代表集合中的每个Order对象)
public void processOrders(List<Order> orders) {
orders.forEach(order -> System.out.println("处理订单:" + order.getId() + ",金额:" + order.getAmount()));
}
// 示例2:过滤多个集合参数(指定filterTarget)
// 场景:过滤userIds集合,只保留等于当前用户ID的元素
public void batchQuery(List<Long> userIds, List<String> orderNos) {
userIds.forEach(userId -> System.out.println("查询用户ID:" + userId));
}
@PostFilter:方法执行后过滤集合返回值这个比上面用的多,这个是方法执行完成后,对方法的集合类型返回值进行过滤,只返回符合条件的元素。
用
filterObject表示返回集合中的每个元素,通过 SpEL 表达式判断是否保留。1
2
3
4
5
6
7
8
9// 场景:查询所有产品后,只返回当前用户有权限查看的产品(filterObject代表每个Product对象)
public List<Product> listAllProducts() {
// 模拟从数据库查询所有产品(方法先执行)
List<Product> products = new ArrayList<>();
products.add(new Product(1L, "手机", 1L)); // 所有者ID:1
products.add(new Product(2L, "电脑", 2L)); // 所有者ID:2
return products;
}
别忘了在配置类上添加@EnableMethodSecurity,当然,旧版本的@EnableGlobalMethodSecurity也不是不行
1 | import org.springframework.context.annotation.Configuration; |
注意,这些注解可以用在类或方法上:
- 用在类上:对类中所有方法生效。
- 用在方法上:只对当前方法生效(优先级高于类上的注解)。
使用方法级别的权限控制
先开启方法安全注解
然后编写一个服务去控制和演示
1 | /** |
控制器就略了,然后编写一些测试用例
测试发现,只在方法上开启一些注解,完全的可以实现权限控制
所以实际上,这才是用到的最多的内容,那个错了的是正则匹配写错了)
授权管理的流程
授权管理前做了什么
回忆,当一个请求进入系统,它会经历以下流程:
- 身份确认
(Authentication):首先,系统通过过滤器链确认“你是谁”。默认生成的
Authentication对象处于未认证状态,登录时会交由AuthenticationManager负责进行认证。只有经过认证的Authentication对象才会被存入SecurityContextHolder后,授权检查才会开始。在用户有后续请求时,可从Authentication中检查权限。 - 请求拦截 (Interception):由
AuthorizationFilter或FilterSecurityInterceptor拦截请求。 - 决策判定 (Decision
Making):拦截器将当前的“用户信息”与“资源要求的权限”交给
AuthorizationManager。如果通过,则访问资源;否则抛出AccessDeniedException。
我们怎么知道这些过滤器在执行?其实我们只要开启 Spring Security 的debug调试模式,开发时就可以在控制台看到这些过滤器的执行顺序,如下:
1 | o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [ |
也就是
DisableEncodeUrlFilterWebAsyncManagerIntegrationFilterSecurityContextHolderFilter(Spring Security 6.x 新增,替代旧的SecurityContextPersistenceFilter)HeaderWriterFilterCorsFilter(跨域过滤器)LogoutFilter(登出过滤器)UsernamePasswordAuthenticationFilter(账号密码登录过滤器)BasicAuthenticationFilter(Basic 认证过滤器)RequestCacheAwareFilter(请求缓存过滤器)SecurityContextHolderAwareRequestFilter(请求包装过滤器)AnonymousAuthenticationFilter(匿名认证过滤器)ExceptionTranslationFilter(异常转换过滤器)FilterSecurityInterceptor(过滤器安全拦截器)AuthorizationFilter(授权过滤器,Spring Security 6.x 新增)
可以发现,过滤器的顺序是 Spring Security 根据功能优先级设计的,核心原则是:先处理基础上下文 / 异步,再处理跨域 / 头信息,再处理认证 / 登出,最后处理授权 / 异常。
其中,SecurityContextHolderFilter管理SecurityContext(用户认证信息的上下文)的创建和销毁,是整个
Security
的基础,它的执行时机非常早,当请求来临时它会从SecuritContextRepository中把SecurityContext对象取出来,然后放入SecurityContextHolder的
ThreadLocal 中。在所有拦截器都处理完成后,再把Security
Context存入SecurityContext Repository,并清除
SecurityContextHolder 内的 SecurityContext
引用。
和认证授权直接相关的过滤器是
AbstractAuthenticationProcessingFilter 和
UsernamePasswordAuthenticationFilter还有AuthorizationFilter
你可能又会问,怎么没看到AbstractAuthenticationProcessingFilter
这个过滤器呢?这是因为它是一个抽象的父类,其内部定义了认证处理的过程,UsernamePasswordAuthenticationFilter
就继承自
AbstractAuthenticationProcessingFilter,处理密码登录的认证。而
GenericFilterBean 是 Spring
框架中的过滤器类,最终的父接口是Filter
这是简单回忆了一下认证部分,因为这是授权前要做的事情
获取认证信息——从
SecurityContext 获取 Authentication
请求到达认证过滤器,准备调用管理器
上面说,AuthorizationFilter是 Spring Security
过滤器链中负责授权的关键过滤器,它的执行时机在
认证过滤器(如 UsernamePasswordAuthenticationFilter)
之后 ——
也就是说,只有用户完成认证(Authentication被存入SecurityContext),才会进入授权环节。
AuthorizationFilter实现了Filter接口,核心逻辑在doFilter(ServletRequest request, ServletResponse response, FilterChain chain)方法中
其中,先将将原生请求转换为Spring Security的包装请求,方便携带安全上下文,然后经过一些策略判断和错误跳过之后,开始调用核心授权逻辑
1 | else { |
this::getAuthentication是获取认证信息的关键入口
1 | private Authentication getAuthentication() { |
其中,每个不同的AuthorizationManager都有着不同的授权检查实现,但是逻辑大差不差,都是匹配当前请求的权限规则,从Authentication中获取用户的权限列表,然后对比用户权限与资源权限要求,返回AuthorizationDecision(isGranted=true/false)
获取认证信息
其中,SecurityContextHolder是获取SecurityContext的工具类,本身不存储数据,而是委托给SecurityContextHolderStrategy策略类处理。
SecurityContext存储在当前线程的ThreadLocal中,因此Authentication也是线程隔离的,这保证了多线程环境下用户信息不会混乱。基于ThreadLocal存储SecurityContext,就叫ThreadLocalSecurityContextHolderStrategy
SecurityContextHolder是如何获取SecurityContext的?而SecurityContext中又有什么内容是上述权限认证中必备的?
其中,SecurityContextHolder类加载时会执行initialize()方法
然后根据strategyName决定使用哪种SecurityContextHolderStrategy也就是存储SecurityContext的策略
1 | private static void initializeStrategy() { |
然后, 获取 SecurityContext
的核心方法就是,getContext()方法,当调用SecurityContextHolder.getContext()时,本质是调用策略实例的getContext()方法
类加载时初始化默认策略是ThreadLocalSecurityContextHolderStrategy。
其中,ThreadLocalSecurityContextHolderStrategy中的方法将SecurityContext包装成Supplier<SecurityContext>存储在ThreadLocal中,实现延迟初始化(只有真正调用get()时才会创建SecurityContext实例)。
1 | public Supplier<SecurityContext> getDeferredContext() { |
SecurityContext是一个接口,其唯一的核心实现类是SecurityContextImpl
可以看到SecurityContext最核心的内容就是Authentication对象,这个对象正是授权认证流程中必不可少的核心数据。
既然SecurityContext的核心是Authentication,那么Authentication中的哪些属性是授权认证必备的?我们看Authentication接口的核心方法:
1 | public interface Authentication extends Principal, Serializable { |
这部分也就是
- 请求到达授权入口,
AuthorizationFilter的doFilter方法被触发,再进入核心授权逻辑 - 认证信息获取方法
AuthorizationManager.check方法的第一个参数this::getAuthentication延迟获取认证信息 - 然后通过
SecurityContextHolder获取SecurityContext,再拿到其中的Authentication对象,这是授权认证的唯一必备数据 - 然后进入授权决策,所有
AuthorizationManager的实现逻辑大概一致,核心是权限匹配
委托决策并且处理结果
在 Web
环境下,通常会进入RequestMatcherDelegatingAuthorizationManager。
为什么捏?
1 | // HttpSecurity中的authorizeHttpRequests方法 |
然后你点进去AuthorizationManagerRequestMatcherRegistry就会发现这个传进来的时候已经是RequestMatcherDelegatingAuthorizationManager了
RequestMatcherDelegatingAuthorizationManager的核心属性是存储RequestMatcher与AuthorizationManager的映射关系
它会遍历配置好的规则,找到匹配当前 URL 的
AuthorizationManager(例如
AuthorityAuthorizationManager 用于检查
hasRole)。
首先,规则从哪来,通过
requestMatchers("/admin/**").hasRole("ADMIN")配置的规则,会被RequestMatcherDelegatingAuthorizationManager.Builder封装成RequestMatcherEntry(RequestMatcher+AuthorizationManager),存入mappings列表。就是上面红字标记的 List,具体是如何构建的就不讲了。然后,
RequestMatcherDelegatingAuthorizationManager的check方法遍历mappings,通过RequestMatcher匹配当前 URL,匹配成功后调用对应AuthorizationManager(如AuthorityAuthorizationManager)的check方法完成权限判断。```java public AuthorizationDecision check(Supplier
authentication, HttpServletRequest request) { // 日志:记录正在授权的请求(如GET /admin/user) if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format(“Authorizing %s”, request)); } // 核心:遍历所有配置的规则(mappings列表) for(RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) { // 步骤1:获取当前规则的RequestMatcher RequestMatcher matcher = mapping.getRequestMatcher(); // 步骤2:匹配当前请求(返回MatchResult,包含是否匹配、路径变量等) RequestMatcher.MatchResult matchResult = matcher.matcher(request); if (matchResult.isMatch()) { // 匹配成功 // 步骤3:获取对应的AuthorizationManager(如AuthorityAuthorizationManager) AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry(); if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager)); } // 步骤4:委托给该manager执行权限检查,返回决策结果 return manager.check(authentication, new RequestAuthorizationContext(request, matchResult.getVariables())); } } // 没有匹配到任何规则,返回拒绝授权(DENY = new AuthorizationDecision(false)) if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.of(() -> "Denying request since did not find matching RequestMatcher")); } return DENY;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
那么示例的`AuthorityAuthorizationManager`是如何实现的
当`RequestMatcherDelegatingAuthorizationManager`委托给`AuthorityAuthorizationManager`后,后者的`check`方法会完成具体的权限检查
- `hasRole`方法的预处理:添加`ROLE_`前缀
```java
public static <T> AuthorityAuthorizationManager<T> hasRole(String role) {
Assert.notNull(role, "role cannot be null");
// 校验:角色不能以ROLE_开头,否则抛出异常(因为框架会自动添加)
Assert.isTrue(!role.startsWith("ROLE_"), () -> role + " should not start with ROLE_ since ROLE_ is automatically prepended when using hasRole. Consider using hasAuthority instead.");
return hasAuthority("ROLE_" + role); // 自动添加ROLE_前缀
}
public static <T> AuthorityAuthorizationManager<T> hasAuthority(String authority) {
Assert.notNull(authority, "authority cannot be null");
return new AuthorityAuthorizationManager<T>(new String[]{authority});
}
check方法的权限判断1
2
3
4
5
6
7private final AuthoritiesAuthorizationManager delegate = new AuthoritiesAuthorizationManager();
private final Set<String> authorities; // 存储需要检查的权限/角色(如ROLE_ADMIN)
public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
// 委托给AuthoritiesAuthorizationManager的check方法,传入需要的权限列表
return this.delegate.check(authentication, this.authorities);
}AuthoritiesAuthorizationManager是真正的权限校验器,AuthorityAuthorizationManager只是一个封装层(门面)。核心逻辑就算比对用户权限和需要的权限,至少一个就通过- 入参:
authentication(用户认证信息)、authorities(需要的权限列表,如[ROLE_ADMIN])。
那么,AuthoritiesAuthorizationManager的权限检查就涉及到权限检查的最后部分了,授权决策结果的生成
首先,如果返回的 AuthorizationDecision 为
isGranted() == true,则继续执行
filterChain.doFilter。如果为 false,则由
AccessDeniedHandler 处理后续逻辑(如返回 403 页面)。
AuthoritiesAuthorizationManager是最终生成授权决策结果的核心类
1 | public AuthorityAuthorizationDecision check(Supplier<Authentication> authentication, Collection<String> authorities) { |
那么isGranted +
isAuthorized就算其中的核心,涉及到权限匹配的核心逻辑
1 | private boolean isGranted(Authentication authentication, Collection<String> authorities) { |
getGrantedAuthorities:通过RoleHierarchy处理角色层级(如配置ROLE_ADMIN > ROLE_USER,则拥有ROLE_ADMIN的用户也会被认为拥有ROLE_USER)。
当RequestMatcherDelegatingAuthorizationManager将AuthorizationDecision返回给AuthorizationFilter后,AuthorizationFilter的doFilter方法会处理这个结果,这个上面也说了
isGranted () = true→ 执行filterChain.doFilter,isGranted () = false→ 抛出异常并触发AccessDeniedHandler,决定授权失败之后应该如何做
这部分的时序图如下
sequenceDiagram
participant AuthFilter as AuthorizationFilter
participant DelegatingManager as RequestMatcherDelegatingAuthorizationManager
participant AuthoritiesManager as AuthoritiesAuthorizationManager
participant FilterChain as FilterChain
participant AccessDeniedHandler as AccessDeniedHandler
participant Controller as Controller
# 情况1:isGranted() = true
AuthFilter->>DelegatingManager: check(auth, request)
DelegatingManager->>AuthoritiesManager: check(auth, authorities)
AuthoritiesManager->>DelegatingManager: return isGranted=true
DelegatingManager->>AuthFilter: return AuthorizationDecision(true)
AuthFilter->>FilterChain: chain.doFilter(request, response)
FilterChain->>Controller: 执行目标资源逻辑
Controller->>Client: 返回响应
# 情况2:isGranted() = false
AuthFilter->>DelegatingManager: check(auth, request)
DelegatingManager->>AuthoritiesManager: check(auth, authorities)
AuthoritiesManager->>DelegatingManager: return isGranted=false
DelegatingManager->>AuthFilter: return AuthorizationDecision(false)
AuthFilter->>AuthFilter: throw AccessDeniedException
AuthFilter->>AccessDeniedHandler: 调用handle方法
AccessDeniedHandler->>Client: 返回403 Forbidden(或自定义页面/JSON)
方法级别安全
URL 权限控制是靠 Filter(过滤器) 在请求进入 Servlet 之前拦截;而方法级别安全是靠 AOP Proxy(代理对象) 在方法执行前后拦截。
当你在一个 Bean 的方法上加上 @PreAuthorize 时,Spring
会在启动时为该 Bean 生成一个代理对象。
- 非安全调用:
Controller -> Service.doSomething() - 安全调用:
Controller -> Proxy.doSomething() -> [授权检查] -> Service.doSomething()
方法拦截器
首先,方法拦截器主要用到的就是这两个,AuthorizationManagerBeforeMethodInterceptor和AuthorizationManagerAfterMethodInterceptor,一个前置,一个后置,是
AOP 的环绕通知实现
- 前置拦截(Before):方法执行前执行授权逻辑(如
@PreAuthorize、@Secured),授权失败则直接抛出异常,不执行方法。 - 后置拦截(After):方法执行后执行授权逻辑(如
@PostAuthorize),即使方法执行成功,授权失败仍会抛出异常。
他们都实现了MethodInterceptor也就是 AOP
联盟接口和PointcutAdvisor切面切点,能被 Spring AOP
自动识别并织入目标方法。
其中,前置拦截AuthorizationManagerBeforeMethodInterceptor对应@PreAuthorize,@Secured等,是最常用的方法授权方式
Spring 启动时,
AuthorizationManagerBeforeMethodInterceptor会作为PointcutAdvisor被 Spring AOP 识别,根据切点匹配目标方法,并织入拦截逻辑。然后,当目标方法被调用时,AOP 会触发拦截器的
invoke方法,分别调用下一步和下下步1
2
3
4
5
6public Object invoke(MethodInvocation mi) throws Throwable {
// 步骤3:执行授权逻辑
this.attemptAuthorization(mi);
// 步骤4:授权成功,执行目标方法
return mi.proceed();
}然后,
attemptAuthorization方法就是迁至拦截器的核心授权逻辑,至于这个就是复用上面的内容了1
2
3
4
5
6
7
8
9
10
11
12
13
14private void attemptAuthorization(MethodInvocation mi) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
// 调用AuthorizationManager的check方法,获取授权决策
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, mi);
// 发布授权事件(默认空实现)
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, mi, decision);
// 授权失败,抛出AccessDeniedException
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision));
throw new AccessDeniedException("Access Denied");
} else {
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
}
}之后,
getAuthentication方法 ,获取用户认证信息1
2
3
4
5
6
7
8
9
10private Authentication getAuthentication() {
// 从SecurityContext中获取Authentication(和Web授权逻辑一致)
Authentication authentication = ((SecurityContextHolderStrategy)this.securityContextHolderStrategy.get()).getContext().getAuthentication();
if (authentication == null) {
// 无认证信息,抛出401异常
throw new AuthenticationCredentialsNotFoundException("An Authentication object was not found in the SecurityContext");
} else {
return authentication;
}
}授权结果处理
- 授权成功:
mi.proceed()执行目标方法,返回方法结果。 - 授权失败:抛出
AccessDeniedException,被全局异常处理器捕获(返回 403 响应)。
- 授权成功:
对了,源码中拦截器的order属性决定了执行顺序:
@PreAuthorize:AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder()(优先级高)。@Secured:AuthorizationInterceptorsOrder.SECURED.getOrder()(优先级次之)。@Jsr250:AuthorizationInterceptorsOrder.JSR250.getOrder()(优先级最低)。@PostAuthorize:默认500(方法执行后,无冲突)。- 意义:如果一个方法同时带有多个注解,优先级高的拦截器先执行授权逻辑。
其中,后置拦截AuthorizationManagerAfterMethodInterceptor就算方法执行后授权,对应@PostAuthorize注解
首先,执行目标方法
1
2
3
4
5
6
7
8public Object invoke(MethodInvocation mi) throws Throwable {
// 步骤1:先执行目标方法,获取返回值
Object result = mi.proceed();
// 步骤2:执行授权逻辑(传入方法返回值)
this.attemptAuthorization(mi, result);
// 步骤3:返回方法结果
return result;
}然后
attemptAuthorization方法 —— 传入返回值授权1
2
3
4
5
6
7
8
9
10
11private void attemptAuthorization(MethodInvocation mi, Object result) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
// 封装方法调用和返回值为MethodInvocationResult
MethodInvocationResult object = new MethodInvocationResult(mi, result);
// 调用AuthorizationManager的check方法(可读取返回值)
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
}
AuthorizationManager决策者判断
AuthorizationManager是方法授权的统筹者,负责读取方法上的注解表达式、调用表达式处理器解析表达式、根据解析结果生成授权决策(AuthorizationDecision)。前置和后置分别是PreAuthorizeAuthorizationManager和PostAuthorizeAuthorizationManager,以PreAuthorizeAuthorizationManager为例
首先,将PreAuthorizeAuthorizationManager绑定到拦截器,只有先把决策者和拦截器绑定,执行时拦截器才能调用决策者。我们从AuthorizationManagerBeforeMethodInterceptor的静态工厂方法源码入手:
它会匹配带有@PreAuthorize注解的方法,然后创建拦截器实例,传入切点和PreAuthorizeAuthorizationManager,最后标注一下执行的顺序
之后,拦截器的构造方法中会存入决策者的实例
然后,你只需要在配置类上添加@EnableMethodSecurity,底层会自动执行上述静态方法,创建AuthorizationManagerBeforeMethodInterceptor(绑定PreAuthorizeAuthorizationManager)并注册到
Spring 容器
因为@EnableMethodSecurity导入了MethodSecurityConfiguration,该配置类会调用AuthorizationManagerBeforeMethodInterceptor.preAuthorize()等方法,创建并注册拦截器实例。
上面这些步骤完成了AuthorizationManager决策者与对应的拦截器绑定然后指定切点。之后Spring
自动注册拦截器为 AOP
的Advisor,使其能织入目标方法的调用流程。
之后就是方法调用触发拦截器的invoke方法,拦截器调用PreAuthorizeAuthorizationManager的check方法完成授权决策,形成
“拦截器→决策者” 的调用链路。
当你调用带有
@PreAuthorize的方法,对应的拦截器AuthorizationManagerBeforeMethodInterceptor调用invoke方法
invoke 调用
attemptAuthorization方法,它调用决策者的authorize方法,进行权限认证
最后,权限决策者调用
PreAuthorizeAuthorizationManager的authorize方法执行授权决策,这个就是权限决策者的核心权限验证方法
那么,注解上的 SpEL 表达式是如何被处理的呢?
首先,PreAuthorizeExpressionAttributeRegistry:是一个缓存注册表,作用是:
- 扫描方法上的
@PreAuthorize注解,提取其中的 SpEL 表达式(如"hasRole('ADMIN')"),封装成ExpressionAttribute对象。 - 缓存表达式结果,避免每次方法调用都重新扫描注解(提升性能)。
- 持有
MethodSecurityExpressionHandler实例,供后续解析表达式使用。
这之后就算涉及到表达式处理的内容了
表达式处理器
MethodSecurityExpressionHandler是方法授权中表达式的处理器,其中最主要的就算负责解析
SpEL 表达式,创建表达式执行的上下文,而且它支持方法参数 /
返回值的引用,是 SpEL 表达式能生效的核心。
MethodSecurityExpressionHandler是一个接口,继承自SecurityExpressionHandler,是处理方法级
SpEL 表达式的核心接口。其核心作用是为 SpEL
表达式提供执行环境和解析能力。
而DefaultMethodSecurityExpressionHandler是MethodSecurityExpressionHandler的默认实现,其核心能力包括:
创建
EvaluationContext上下文,包含:- 用户认证信息:
Authentication对象(可通过authentication引用)。 - 方法参数:方法的参数(可通过
#参数名引用,如#id)。 - 内置权限方法:如
hasRole()、hasAuthority()、isAuthenticated()等(这些方法由SecurityExpressionRoot提供)。
- 用户认证信息:
解析 SpEL 表达式
将
@PreAuthorize中的字符串表达式(如"hasRole('ADMIN') and #id == authentication.principal.id")解析为 Spring 的Expression对象,供后续执行。支持方法返回值和过滤
setReturnObject:将方法返回值放入上下文,支持returnObject引用。filter:处理@PreFilter/@PostFilter注解,过滤集合中的元素(如@PostFilter("filterObject.userId == authentication.principal.id"))。
这块有缘再见了







