【Spring Security】授权(一)
授权(Authorization)
Authorization 基础与访问控制模型
基本概念
认证 vs 授权
概念 | 英文 | 核心问题 | 举例 |
---|---|---|---|
认证 | Authentication | 你是谁? | 用户登录验证用户名密码 |
授权 | Authorization | 你能做什么? | 用户是否有访问 /admin 的权限 |
总结一句话:
认证是“身份证”,授权是“通行证”。
授权在 Spring Security 中的位置
前面学的过滤链执行顺序的最后几步,其实就是授权阶段:
WebAsyncManagerIntegrationFilter
SecurityContextHolderFilter
HeaderWriterFilter
...
UsernamePasswordAuthenticationFilter
AnonymousAuthenticationFilter
ExceptionTranslationFilter
FilterSecurityInterceptor (授权核心)⬅️
也就是说:
授权控制的真正执行者是
FilterSecurityInterceptor
。
授权的执行流程(整体逻辑)
核心参与组件
组件 | 职责 | 说明 |
---|---|---|
FilterSecurityInterceptor | 授权过滤器 | 过滤链最后一层,执行权限判断 |
AccessDecisionManager | 权限决策管理器 | 根据多个投票结果决定是否放行 |
AccessDecisionVoter | 投票器 | 逐个投票决定是否允许访问 |
SecurityMetadataSource | 元数据源 | 提供当前资源所需权限(角色) |
Authentication | 当前用户凭证 | 来自认证阶段,包含用户权限 |
ConfigAttribute | 权限要求对象 | 描述访问某资源所需的权限 |
在 Spring Security 中,AccessDecisionManager
(访问决策管理器)是权限控制的核心组件,负责根据用户的权限、请求的资源以及配置的规则,最终决定是否允许用户访问某个资源(如接口、方法)。
权限决策管理器
AccessDecisionManager
的核心作用
当用户发起请求时,Spring Security 的过滤器链会拦截请求并提取用户信息(权限 / 角色)、请求信息(如 URL、方法)。之后,AccessDecisionManager
会接收这些信息,并根据预设的规则判断用户是否有权限访问该资源,最终返回 “允许访问” 或 “拒绝访问” 的结果。
简单说,它是权限校验的 “最终裁判者”。
核心组成与工作流程
-
依赖的核心组件
AccessDecisionManager
的决策过程依赖以下关键信息:Authentication
:用户认证信息,包含用户拥有的权限(GrantedAuthority
,如角色ROLE_ADMIN
、权限user:list
)。Object
:被访问的资源对象,通常是FilterInvocation
(对应 URL 资源)或MethodInvocation
(对应方法资源)。Collection<ConfigAttribute>
:资源对应的权限要求(即访问该资源需要的权限,如hasRole('ADMIN')
或hasAuthority('user:delete')
)。
-
工作流程
- 获取信息:从请求中提取用户权限(
Authentication
)、资源所需权限(ConfigAttribute
)。 - 决策判断:
AccessDecisionManager
根据内置规则或自定义逻辑,对比用户权限与资源所需权限。 - 返回结果:
- 允许访问:请求继续执行。
- 拒绝访问:抛出
AccessDeniedException
(403 错误)。
- 获取信息:从请求中提取用户权限(
默认授权决策模型(投票机制)
Spring Security 默认采用 投票制模型(Voting-based Model)。
三种主要投票策略:
决策策略类 | 策略描述 | 举例 |
---|---|---|
AffirmativeBased | 只要有一个投票通过即放行 | 默认策略 |
ConsensusBased | 赞成票多于反对票即放行 | 多数表决制 |
UnanimousBased | 所有投票器都同意才放行 | 全票制 |
默认实现 AffirmativeBased
源码解析
AffirmativeBased
的核心逻辑是:只要有一个投票器(AccessDecisionVoter
)投 “同意票”,就允许访问;如果所有投票器都弃权,且存在未认证用户的权限需求,则可能抛出 “认证不足” 异常。
核心源码(简化版):
public class AffirmativeBased extends AbstractAccessDecisionManager {// 构造方法:接收投票器列表public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {super(decisionVoters);}/*** 核心决策方法* @param authentication 用户认证信息(含权限)* @param object 被访问的资源(如 FilterInvocation、MethodInvocation)* @param configAttributes 资源所需的权限要求(如 "ROLE_ADMIN"、"user:delete")*/@Overridepublic void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException {int grant = 0; // 同意票数int deny = 0; // 拒绝票数// 遍历所有投票器,收集投票结果for (AccessDecisionVoter<?> voter : getDecisionVoters()) {// 每个投票器投票(返回 1:同意,-1:拒绝,0:弃权)int result = voter.vote(authentication, object, configAttributes);switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:grant++;// 只要有一个同意,直接返回(一票通过)return;case AccessDecisionVoter.ACCESS_DENIED:deny++;break;default: // ACCESS_ABSTAIN(弃权)break;}}// 如果有拒绝票,直接抛出 403if (deny > 0) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}// 如果所有投票器都弃权,检查是否需要认证checkAllowIfAllAbstainDecisions();}
}
关键逻辑说明:
- 一票通过:循环所有投票器,只要有一个返回
ACCESS_GRANTED
(1),立即允许访问(直接return
)。 - 拒绝优先:如果没有同意票,但存在拒绝票(
deny > 0
),则抛出AccessDeniedException
(403 错误)。 - 全弃权处理:若所有投票器都弃权(
grant=0 && deny=0
),则调用checkAllowIfAllAbstainDecisions()
:- 默认为
false
:抛出AccessDeniedException
(拒绝访问)。 - 可通过
setAllowIfAllAbstainDecisions(true)
配置为 “允许访问”(不推荐,不安全)。
- 默认为
ConsensusBased
(少数服从多数)
核心逻辑:统计同意票和拒绝票,同意票多于拒绝票则允许访问;票数相等时可配置默认策略。
public class ConsensusBased extends AbstractAccessDecisionManager {private boolean allowIfEqualGrantedDeniedDecisions = true; // 票数相等时默认允许@Overridepublic void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException {int grant = 0;int deny = 0;for (AccessDecisionVoter<?> voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);if (result == AccessDecisionVoter.ACCESS_GRANTED) {grant++;} else if (result == AccessDecisionVoter.ACCESS_DENIED) {deny++;}}// 同意票 > 拒绝票:允许访问if (grant > deny) {return;}// 拒绝票 > 同意票:拒绝访问if (deny > grant) {throw new AccessDeniedException("Access is denied");}// 票数相等:根据配置决定if (grant == deny) {if (this.allowIfEqualGrantedDeniedDecisions) {return; // 允许} else {throw new AccessDeniedException("Access is denied"); // 拒绝}}// 全弃权处理(同 AffirmativeBased)checkAllowIfAllAbstainDecisions();}
}
UnanimousBased
(一票否决)
核心逻辑:所有投票器必须同意(或弃权),只要有一个拒绝票就拒绝访问。
public class UnanimousBased extends AbstractAccessDecisionManager {@Overridepublic void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException {int grant = 0;List<ConfigAttribute> deniedAttributes = new ArrayList<>();for (AccessDecisionVoter<?> voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);if (result == AccessDecisionVoter.ACCESS_DENIED) {// 只要有一个拒绝票,记录拒绝的权限要求deniedAttributes.addAll(filterConfigAttributes(configAttributes, voter));} else if (result == AccessDecisionVoter.ACCESS_GRANTED) {grant++;}}// 存在拒绝票:抛出异常(包含拒绝原因)if (!deniedAttributes.isEmpty()) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}// 所有投票器都弃权:检查是否允许if (grant == 0) {checkAllowIfAllAbstainDecisions();}// 走到这里表示所有投票器要么同意,要么弃权:允许访问}
}
父类 AbstractAccessDecisionManager
的核心逻辑
上述三个实现都继承自 AbstractAccessDecisionManager
,它提供了基础能力:
- 管理投票器列表(
decisionVoters
)。 - 处理 “全弃权” 场景(
checkAllowIfAllAbstainDecisions()
)。 - 校验投票器是否支持资源类型(
supports()
方法)。
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager {protected final List<AccessDecisionVoter<?>> decisionVoters;private boolean allowIfAllAbstainDecisions = false; // 全弃权时默认拒绝public AbstractAccessDecisionManager(List<AccessDecisionVoter<?>> decisionVoters) {Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");this.decisionVoters = decisionVoters;}// 全弃权时的处理:如果不允许,则抛出异常protected void checkAllowIfAllAbstainDecisions() throws AccessDeniedException {if (!this.allowIfAllAbstainDecisions) {throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}}// 其他方法(supports 等)...
}
默认配置的生效方式
在 Spring Security 中,AffirmativeBased
是默认的 AccessDecisionManager
,其投票器列表默认包含:
WebExpressionVoter
:处理 SpEL 表达式(如hasRole()
、hasAuthority()
)。RoleVoter
:处理角色权限(如ROLE_ADMIN
)。AuthenticatedVoter
:处理认证状态(如isAuthenticated()
)。
这些默认配置在 HttpSecurity
自动配置时生效,无需手动声明。
投票器
投票器(AccessDecisionVoter
):决策的 “参与者”
AccessDecisionManager
本身并不直接判断权限,而是通过投票器(AccessDecisionVoter
) 收集 “投票结果”,再根据自身策略(如一票通过、全票通过)做出最终决策。
-
核心投票器实现
Spring Security 内置了多个投票器,最常用的是:
-
RoleVoter
:处理角色权限(如ROLE_ADMIN
),判断用户是否拥有资源所需的角色。public class RoleVoter implements AccessDecisionVoter<Object> {// 角色前缀(可自定义,默认 "ROLE_")private String rolePrefix = "ROLE_";@Overridepublic int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {// 1. 如果用户未认证,直接弃权if (authentication == null) {return ACCESS_ABSTAIN;}int result = ACCESS_ABSTAIN;// 2. 获取用户拥有的所有权限(GrantedAuthority)Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);// 3. 遍历资源所需的权限要求(attributes)for (ConfigAttribute attribute : attributes) {// 3.1 如果投票器不支持该权限要求(非角色类型),直接弃权if (!supports(attribute)) {continue;}result = ACCESS_DENIED; // 先默认拒绝// 3.2 检查用户是否拥有该角色权限for (GrantedAuthority authority : authorities) {if (attribute.getAttribute().equals(authority.getAuthority())) {return ACCESS_GRANTED; // 匹配成功,投同意票}}}return result;}// 判断权限要求是否为角色类型(是否以 rolePrefix 开头)@Overridepublic boolean supports(ConfigAttribute attribute) {String attr = attribute.getAttribute();return (attr != null) && attr.startsWith(rolePrefix);}// 支持所有资源类型@Overridepublic boolean supports(Class<?> clazz) {return true;}// 提取用户权限(默认从 Authentication 中获取)protected Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {return authentication.getAuthorities();} }
-
工作流程:
- 仅处理以
ROLE_
为前缀的权限要求(如ROLE_ADMIN
、ROLE_USER
)。 - 遍历资源所需的角色权限,若用户拥有其中任意一个,返回
ACCESS_GRANTED
(同意)。 - 若用户没有所需角色,返回
ACCESS_DENIED
(拒绝);若权限要求不是角色类型,返回ACCESS_ABSTAIN
(弃权)。
示例:
- 资源所需权限:
ROLE_ADMIN
。 - 用户拥有权限:
ROLE_ADMIN
→ 投票结果:ACCESS_GRANTED
。 - 用户拥有权限:
ROLE_USER
→ 投票结果:ACCESS_DENIED
。 - 资源所需权限:
user:delete
(非角色类型)→ 投票结果:ACCESS_ABSTAIN
。
- 仅处理以
-
-
AuthenticatedVoter
:处理认证状态相关的规则(如isAuthenticated()
、isAnonymous()
)。public class AuthenticatedVoter implements AccessDecisionVoter<Object> {// 预定义的认证状态权限要求public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY"; // 完全认证(非记住我)public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED"; // 记住我认证public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY"; // 匿名@Overridepublic int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {int result = ACCESS_ABSTAIN;// 遍历资源所需的权限要求for (ConfigAttribute attribute : attributes) {if (!supports(attribute)) {continue;}result = ACCESS_DENIED; // 先默认拒绝// 检查用户认证状态是否符合要求if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) {// 要求完全认证(非记住我)if (isFullyAuthenticated(authentication)) {return ACCESS_GRANTED;}} else if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) {// 要求记住我认证(或完全认证)if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {return ACCESS_GRANTED;}} else if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) {// 允许匿名访问(未认证或记住我都算)return ACCESS_GRANTED;}}return result;}// 判断权限要求是否为认证状态类型@Overridepublic boolean supports(ConfigAttribute attribute) {String attr = attribute.getAttribute();return (attr != null) && (attr.equals(IS_AUTHENTICATED_FULLY) || attr.equals(IS_AUTHENTICATED_REMEMBERED) || attr.equals(IS_AUTHENTICATED_ANONYMOUSLY));}// 检查是否为完全认证(非匿名、非记住我)private boolean isFullyAuthenticated(Authentication authentication) {return authentication != null && !authenticationTrustResolver.isAnonymous(authentication)&& !authenticationTrustResolver.isRememberMe(authentication);} }
-
工作流程:
- 仅处理三个预定义的认证状态权限要求(
IS_AUTHENTICATED_*
)。 - 根据用户的认证状态(匿名、记住我、完全认证)与权限要求对比,返回投票结果。
示例:
- 资源所需权限:
IS_AUTHENTICATED_FULLY
→ 仅完全认证用户(输入账号密码登录)可通过。 - 资源所需权限:
IS_AUTHENTICATED_ANONYMOUSLY
→ 匿名用户(未登录)可通过。
- 仅处理三个预定义的认证状态权限要求(
-
-
WebExpressionVoter
:处理 SpEL 表达式规则(如hasRole('ADMIN')
、hasAuthority('user:list')
),是注解@PreAuthorize
和 URL 权限配置的底层实现。public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {private final ExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();@Overridepublic int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {// 1. 提取 SpEL 表达式(资源所需权限,如 "hasRole('ADMIN')")String expression = attributes.iterator().next().getAttribute();// 2. 构建表达式上下文(包含用户认证信息、请求对象等)EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, fi);ctx.setVariable("request", fi.getRequest()); // 暴露 request 变量// 3. 解析并执行表达式Expression expr = expressionHandler.getExpressionParser().parseExpression(expression);boolean result = expr.getValue(ctx, Boolean.class);// 4. 根据表达式结果返回投票结果return result ? ACCESS_GRANTED : ACCESS_DENIED;}@Overridepublic boolean supports(ConfigAttribute attribute) {// 支持所有以 SpEL 表达式定义的权限要求return attribute.getAttribute() != null;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);} }
-
工作流程:
- 解析资源所需的 SpEL 表达式(如
hasRole('ADMIN')
、hasIpAddress('192.168.1.0/24')
)。 - 构建表达式上下文,包含用户认证信息(
authentication
)、请求对象(request
)等变量。 - 执行表达式,若结果为
true
则返回ACCESS_GRANTED
(同意),否则返回ACCESS_DENIED
(拒绝)。
示例:
- 资源所需权限:
hasAuthority('user:delete')
→ 用户拥有user:delete
权限时,表达式结果为true
,投票同意。 - 资源所需权限:
hasRole('ADMIN') and hasIpAddress('127.0.0.1')
→ 仅当用户是ADMIN
且 IP 为本地时,投票同意。
- 解析资源所需的 SpEL 表达式(如
-
-
-
投票器的工作原理
每个投票器的
vote()
方法返回以下结果之一:- 1(ACCESS_GRANTED):同意访问。
- -1(ACCESS_DENIED):拒绝访问。
- 0(ACCESS_ABSTAIN):弃权(不参与此次决策)。
AccessDecisionManager
收集所有投票器的结果后,按自身策略(如AffirmativeBased
的 “一票通过”)决定最终结果。
AccessDecisionVoter
核心接口
所有投票器都实现 AccessDecisionVoter
接口,其核心方法如下:
public interface AccessDecisionVoter<S> {// 投票结果常量:同意(1)、拒绝(-1)、弃权(0)int ACCESS_GRANTED = 1;int ACCESS_DENIED = -1;int ACCESS_ABSTAIN = 0;// 核心投票方法:根据用户认证信息、资源、所需权限,返回投票结果int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);// 判断投票器是否支持某种权限要求(ConfigAttribute)boolean supports(ConfigAttribute attribute);// 判断投票器是否支持某种资源类型(如 FilterInvocation、MethodInvocation)boolean supports(Class<?> clazz);
}
投票器的核心逻辑集中在 vote()
方法:它接收用户的认证信息(Authentication
,含用户权限)、被访问的资源(object
)、资源所需的权限要求(attributes
),然后返回投票结果。
自定义投票器
如果内置投票器无法满足需求(如基于 “用户部门” 的权限校验),可自定义投票器。
场景:仅允许 “技术部” 用户访问 /api/tech/**
接口
@Component
public class DepartmentVoter implements AccessDecisionVoter<Object> {// 部门权限前缀(自定义)private static final String DEPARTMENT_PREFIX = "DEPARTMENT_";@Overridepublic int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {// 1. 未认证用户直接弃权if (authentication == null) {return ACCESS_ABSTAIN;}int result = ACCESS_ABSTAIN;// 2. 获取用户所属部门(假设用户详情中包含部门信息)CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();String userDept = userDetails.getDepartment(); // 如 "技术部"// 3. 遍历资源所需的部门权限要求(如 "DEPARTMENT_技术部")for (ConfigAttribute attribute : attributes) {if (!supports(attribute)) {continue;}result = ACCESS_DENIED; // 先默认拒绝// 4. 检查用户部门是否匹配String requiredDept = attribute.getAttribute().replace(DEPARTMENT_PREFIX, "");if (requiredDept.equals(userDept)) {return ACCESS_GRANTED; // 部门匹配,投同意票}}return result;}// 仅支持部门类型的权限要求(以 DEPARTMENT_ 开头)@Overridepublic boolean supports(ConfigAttribute attribute) {String attr = attribute.getAttribute();return attr != null && attr.startsWith(DEPARTMENT_PREFIX);}@Overridepublic boolean supports(Class<?> clazz) {return true; // 支持所有资源类型}
}
配置自定义投票器:
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Autowiredprivate DepartmentVoter departmentVoter;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 1. 构建投票器列表(包含内置投票器 + 自定义投票器)List<AccessDecisionVoter<?>> voters = new ArrayList<>();voters.add(new WebExpressionVoter()); // 保留 SpEL 支持voters.add(new RoleVoter());voters.add(departmentVoter); // 添加自定义投票器// 2. 配置决策管理器使用自定义投票器列表http.authorizeHttpRequests(auth -> auth.requestMatchers("/api/tech/**").access((auth, context) -> {// 资源所需权限:DEPARTMENT_技术部ConfigAttribute deptAttr = new SecurityConfig("DEPARTMENT_技术部");// 调用决策管理器判断AffirmativeBased decisionManager = new AffirmativeBased(voters);try {decisionManager.decide(auth.get(), context.getRequest(), Collections.singleton(deptAttr));return new AuthorizationDecision(true);} catch (AccessDeniedException e) {return new AuthorizationDecision(false);}}).anyRequest().authenticated());return http.build();}
}
元数据源
在 Spring Security 中,元数据源(SecurityMetadataSource
) 是权限控制的 “信息提供者”,负责收集被访问资源所需的权限要求(ConfigAttribute
),为后续的投票器(AccessDecisionVoter
)和决策管理器(AccessDecisionManager
)提供判断依据。
简单说,元数据源的核心作用是:告诉系统 “访问这个资源(如接口、方法)需要哪些权限”。
SecurityMetadataSource
核心接口
所有元数据源都实现 SecurityMetadataSource
接口,其定义如下:
public interface SecurityMetadataSource extends AopInfrastructureBean {// 核心方法:根据资源对象(如 URL、方法)获取其所需的权限要求Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException;// 获取所有支持的权限要求(通常返回空集合,意义不大)Collection<ConfigAttribute> getAllConfigAttributes();// 判断是否支持某种资源类型(如 FilterInvocation、MethodInvocation)boolean supports(Class<?> clazz);
}
getAttributes(Object object)
:最核心的方法。参数object
是被访问的资源(如FilterInvocation
对应 URL 资源,MethodInvocation
对应方法资源),返回值是该资源所需的权限要求(ConfigAttribute
集合,如ROLE_ADMIN
、user:delete
)。supports(Class<?> clazz)
:判断当前元数据源是否支持对某种资源类型的解析。
Spring Security 内置的元数据源实现
Spring Security 针对不同的资源类型(URL 资源、方法资源)提供了对应的元数据源实现:
-
FilterInvocationSecurityMetadataSource
(URL 资源元数据源)专门用于解析URL 接口资源的权限要求,通常与
FilterSecurityInterceptor
配合使用(处理 HTTP 请求级别的权限)。核心实现:
DefaultFilterInvocationSecurityMetadataSource
这是最常用的 URL 元数据源,它基于请求匹配规则(如 Ant 路径匹配)映射 URL 与权限要求。
public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {// 存储 URL 模式与权限要求的映射(如 "/api/admin/**" → ["ROLE_ADMIN"])private final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap;public DefaultFilterInvocationSecurityMetadataSource(LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {this.requestMap = requestMap;}@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) {// 1. 转换资源对象为 FilterInvocation(包含 HTTP 请求信息)FilterInvocation fi = (FilterInvocation) object;HttpServletRequest request = fi.getRequest();// 2. 遍历 URL 匹配规则,找到与当前请求匹配的规则for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {if (entry.getKey().matches(request)) {// 3. 返回匹配到的权限要求return entry.getValue();}}// 4. 无匹配规则时返回空(表示不需要特定权限)return null;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);} }
工作流程:
- 初始化时,通过
requestMap
传入 URL 模式与权限的映射(如"/api/admin/**"
对应["ROLE_ADMIN"]
)。 - 当用户访问某个 URL 时,
getAttributes()
方法根据请求信息匹配requestMap
中的规则,返回该 URL 所需的权限要求。
示例:
在 Security 配置中定义 URL 权限规则时,底层会构建
requestMap
并传入DefaultFilterInvocationSecurityMetadataSource
:http.authorizeHttpRequests(auth -> auth.requestMatchers("/api/admin/**").hasRole("ADMIN") // 映射 "/api/admin/**" → ["ROLE_ADMIN"].requestMatchers("/api/users").hasAuthority("user:list") // 映射 "/api/users" → ["user:list"] );
- 初始化时,通过
-
MethodSecurityMetadataSource
(方法资源元数据源)专门用于解析方法资源的权限要求,与
MethodSecurityInterceptor
配合使用(处理方法级别的权限,如@PreAuthorize
注解)。核心实现:
PrePostAnnotationSecurityMetadataSource
负责解析方法上的
@PreAuthorize
、@PostAuthorize
、@PreFilter
、@PostFilter
等注解,提取其中的权限表达式作为权限要求。public class PrePostAnnotationSecurityMetadataSource implements MethodSecurityMetadataSource {private final PrePostAnnotationParser prePostAnnotationParser = new PrePostAnnotationParser();@Overridepublic Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {// 1. 合并方法与类上的注解(方法注解优先级更高)Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);// 2. 解析 @PreAuthorize 等注解中的表达式(如 @PreAuthorize("hasRole('ADMIN')"))Collection<ConfigAttribute> attributes = prePostAnnotationParser.parseAnnotations(specificMethod, targetClass);return attributes;}@Overridepublic boolean supports(Class<?> clazz) {return Method.class.isAssignableFrom(clazz);} }
工作流程:
- 当调用带有
@PreAuthorize
等注解的方法时,getAttributes()
方法解析注解中的 SpEL 表达式(如"hasRole('ADMIN')"
)。 - 将表达式封装为
ConfigAttribute
并返回,作为该方法所需的权限要求。
示例:
方法上的注解会被解析为对应的权限要求:
@PreAuthorize("hasAuthority('user:delete')") // 解析为权限要求:"hasAuthority('user:delete')" public void deleteUser(Long id) { ... }
- 当调用带有
元数据源在权限流程中的作用
元数据源是 Spring Security 权限校验流程的关键中间环节,其工作流程嵌入在整个权限判断过程中:
- 用户发起请求:访问某个 URL 或调用某个方法。
- 拦截请求:
FilterSecurityInterceptor
(URL 级)或MethodSecurityInterceptor
(方法级)拦截请求。 - 获取资源权限要求:拦截器调用元数据源的
getAttributes()
方法,获取当前资源所需的权限(ConfigAttribute
)。 - 获取用户权限:从
SecurityContext
中提取用户的认证信息(Authentication
,含用户拥有的权限)。 - 权限判断:决策管理器(
AccessDecisionManager
)结合用户权限和资源权限要求,通过投票器做出最终决策。
简言之:
元数据源提供 “需要什么权限”,用户认证信息提供 “有什么权限”,决策管理器负责 “比对判断”。
自定义元数据源
当内置元数据源无法满足需求(如从数据库动态加载 URL 与权限的映射)时,可自定义元数据源。
场景:从数据库动态加载 URL 权限规则
假设数据库中存储了 URL 与权限的映射关系(如 sys_url_permission
表),需在系统启动时加载,并支持动态更新。
-
自定义 URL 元数据源
@Component public class DynamicUrlSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {// 存储 URL 与权限的映射(从数据库加载)private Map<RequestMatcher, Collection<ConfigAttribute>> urlPermissionMap;@Autowiredprivate UrlPermissionService urlPermissionService; // 数据库服务// 初始化:从数据库加载 URL 权限规则@PostConstructpublic void init() {loadUrlPermissions();}// 从数据库加载并构建映射关系private void loadUrlPermissions() {urlPermissionMap = new LinkedHashMap<>();// 1. 查询数据库:获取所有 URL 及其对应的权限(如 "/api/users" → ["user:list"])List<UrlPermission> permissions = urlPermissionService.listAll();for (UrlPermission perm : permissions) {// 2. 创建 URL 匹配器(支持 Ant 风格路径,如 "/api/**")RequestMatcher requestMatcher = new AntPathRequestMatcher(perm.getUrl());// 3. 封装权限要求(ConfigAttribute)List<ConfigAttribute> attributes = new ArrayList<>();for (String auth : perm.getAuthorities()) {attributes.add(new SecurityConfig(auth));}// 4. 存入映射表urlPermissionMap.put(requestMatcher, attributes);}}// 核心方法:根据请求获取所需权限@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) {FilterInvocation fi = (FilterInvocation) object;HttpServletRequest request = fi.getRequest();// 遍历映射表,匹配当前请求的 URLfor (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : urlPermissionMap.entrySet()) {if (entry.getKey().matches(request)) {return entry.getValue();}}// 未匹配到的 URL:默认拒绝(或返回空表示允许,根据业务调整)return Collections.singletonList(new SecurityConfig("ROLE_DENY"));}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}// 提供动态刷新方法(如权限更新后调用)public void refresh() {loadUrlPermissions();} }
-
配置自定义元数据源
@Configuration @EnableWebSecurity public class SecurityConfig {@Autowiredprivate DynamicUrlSecurityMetadataSource dynamicMetadataSource;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth// 禁用默认的 URL 权限配置,使用自定义元数据源.anyRequest().authenticated())// 替换默认的 FilterSecurityInterceptor,使用自定义元数据源.with(new Customizer<HttpSecurity>() {@Overridepublic void customize(HttpSecurity http) {FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();interceptor.setSecurityMetadataSource(dynamicMetadataSource);// 设置决策管理器(使用默认的 AffirmativeBased)interceptor.setAccessDecisionManager(http.getSharedObject(AccessDecisionManager.class));interceptor.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));http.addFilterBefore(interceptor, FilterSecurityInterceptor.class);}});return http.build();} }
注意事项
- 资源类型匹配:元数据源必须与资源类型对应(如
FilterInvocationSecurityMetadataSource
对应 URL 资源,MethodSecurityMetadataSource
对应方法资源)。 - 性能优化:动态元数据源(如从数据库加载)建议加缓存,避免频繁查询数据库;可提供
refresh()
方法支持权限动态更新。 - 默认行为:若
getAttributes()
返回null
,表示该资源不需要特定权限(默认允许访问,除非被其他规则拦截)。 - 与注解的配合:方法级注解(如
@PreAuthorize
)的权限解析依赖MethodSecurityMetadataSource
,需通过@EnableMethodSecurity
开启。
权限控制的三层结构模型
Spring Security 的授权体系是分层的:
层级 | 控制方式 | 示例 | 应用场景 |
---|---|---|---|
1. Web 层 | 基于 URL | /admin/** 只能 ADMIN 访问 | 页面、接口权限 |
2. 方法层 | 基于注解 | @PreAuthorize("hasRole('ADMIN')") | Service 层逻辑保护 |
3. 数据层 | 基于业务规则 | 用户只能修改自己的资源 | 精细化控制(ABAC) |