当前位置: 首页 > news >正文

【Spring Security】授权(一)

Spring Security

  • 授权(Authorization)
    • Authorization 基础与访问控制模型
      • 基本概念
      • 权限决策管理器
      • 投票器
      • 元数据源

授权(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 会接收这些信息,并根据预设的规则判断用户是否有权限访问该资源,最终返回 “允许访问” 或 “拒绝访问” 的结果。

简单说,它是权限校验的 “最终裁判者”。

核心组成与工作流程

  1. 依赖的核心组件

    • AccessDecisionManager 的决策过程依赖以下关键信息:
      • Authentication:用户认证信息,包含用户拥有的权限(GrantedAuthority,如角色 ROLE_ADMIN、权限 user:list)。
      • Object:被访问的资源对象,通常是 FilterInvocation(对应 URL 资源)或 MethodInvocation(对应方法资源)。
      • Collection<ConfigAttribute>:资源对应的权限要求(即访问该资源需要的权限,如 hasRole('ADMIN')hasAuthority('user:delete'))。
  2. 工作流程

    1. 获取信息:从请求中提取用户权限(Authentication)、资源所需权限(ConfigAttribute)。
    2. 决策判断AccessDecisionManager 根据内置规则或自定义逻辑,对比用户权限与资源所需权限。
    3. 返回结果
      • 允许访问:请求继续执行。
      • 拒绝访问:抛出 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();}
}

关键逻辑说明:

  1. 一票通过:循环所有投票器,只要有一个返回 ACCESS_GRANTED(1),立即允许访问(直接 return)。
  2. 拒绝优先:如果没有同意票,但存在拒绝票(deny > 0),则抛出 AccessDeniedException(403 错误)。
  3. 全弃权处理:若所有投票器都弃权(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,它提供了基础能力:

  1. 管理投票器列表(decisionVoters)。
  2. 处理 “全弃权” 场景(checkAllowIfAllAbstainDecisions())。
  3. 校验投票器是否支持资源类型(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 收集 “投票结果”,再根据自身策略(如一票通过、全票通过)做出最终决策。

  1. 核心投票器实现

    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_ADMINROLE_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 为本地时,投票同意。
  2. 投票器的工作原理

    每个投票器的 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_ADMINuser:delete)。
  • supports(Class<?> clazz):判断当前元数据源是否支持对某种资源类型的解析。

Spring Security 内置的元数据源实现

Spring Security 针对不同的资源类型(URL 资源、方法资源)提供了对应的元数据源实现:

  1. 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"]
    );
    
  2. 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 权限校验流程的关键中间环节,其工作流程嵌入在整个权限判断过程中:

  1. 用户发起请求:访问某个 URL 或调用某个方法。
  2. 拦截请求FilterSecurityInterceptor(URL 级)或 MethodSecurityInterceptor(方法级)拦截请求。
  3. 获取资源权限要求:拦截器调用元数据源的 getAttributes() 方法,获取当前资源所需的权限(ConfigAttribute)。
  4. 获取用户权限:从 SecurityContext 中提取用户的认证信息(Authentication,含用户拥有的权限)。
  5. 权限判断:决策管理器(AccessDecisionManager)结合用户权限和资源权限要求,通过投票器做出最终决策。

简言之:

元数据源提供 “需要什么权限”,用户认证信息提供 “有什么权限”,决策管理器负责 “比对判断”

自定义元数据源

当内置元数据源无法满足需求(如从数据库动态加载 URL 与权限的映射)时,可自定义元数据源。

场景:从数据库动态加载 URL 权限规则

假设数据库中存储了 URL 与权限的映射关系(如 sys_url_permission 表),需在系统启动时加载,并支持动态更新。

  1. 自定义 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();}
    }
    
  2. 配置自定义元数据源

    @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();}
    }
    

注意事项

  1. 资源类型匹配:元数据源必须与资源类型对应(如 FilterInvocationSecurityMetadataSource 对应 URL 资源,MethodSecurityMetadataSource 对应方法资源)。
  2. 性能优化:动态元数据源(如从数据库加载)建议加缓存,避免频繁查询数据库;可提供 refresh() 方法支持权限动态更新。
  3. 默认行为:若 getAttributes() 返回 null,表示该资源不需要特定权限(默认允许访问,除非被其他规则拦截)。
  4. 与注解的配合:方法级注解(如 @PreAuthorize)的权限解析依赖 MethodSecurityMetadataSource,需通过 @EnableMethodSecurity 开启。

权限控制的三层结构模型

Spring Security 的授权体系是分层的:

层级控制方式示例应用场景
1. Web 层基于 URL/admin/** 只能 ADMIN 访问页面、接口权限
2. 方法层基于注解@PreAuthorize("hasRole('ADMIN')")Service 层逻辑保护
3. 数据层基于业务规则用户只能修改自己的资源精细化控制(ABAC)

http://www.dtcms.com/a/512170.html

相关文章:

  • 数据结构八大排序:快速排序-挖坑法(递归与非递归)及其优化
  • Docker 中卷、容器、镜像的区别
  • 学习React-21-受控组件非受控组件
  • 银行测试学习计划
  • 电商自建站中企动力网站建设公司
  • 怎么搜 织梦的网站唐山海港经济开发区人才网
  • Qt打包工具Enigma Virtual Box
  • 【同步/异步 日志系统】--- 介绍
  • 【软考备考】 数据与文件的加解密种类详解和使用场景
  • GitLab 版本控制与管理指南
  • Python动态方法调用全解:从字符串到执行的艺术
  • Blender入门学习03
  • 网站建设龙兵科技嘉兴网站建设网站建设
  • html代码下载网站怎么优化关键词
  • Kafka面试精讲 Day 27:监控告警与故障排查
  • C++ ABI:编译报错之:gcc 4.8.1 切 gcc 6.1.0
  • OLED-on-silicon(OLEDoS)技术正成为VR/MR设备显示技术的未来大趋势
  • QML学习笔记(四十四)QML与C++交互:对QML对象设置objectName
  • 网站制作全包多少钱演出票务网站建设
  • 用 Go 手搓一个 NTP 服务:从“时间混乱“到“精准同步“的奇幻之旅
  • 如何设计一个高并发系统?
  • 仓颉语言核心技术全解析与实战教程
  • 【多维聚类算法】RQ-Kmeans 利用残差信息 捕捉细节特征
  • 【代码随想录算法训练营——Day44】动态规划——1143.最长公共子序列、1035.不相交的线、53.最大子序和、392.判断子序列
  • 北住房和城乡建设厅网站亦庄建设局网站
  • 做生鲜食品最好的网站深圳网站建设犀牛云
  • Spring—容器
  • 汉南公司网站建设山东定制版网站建设公司
  • .NET WinForms + WPF 综合学习路线:从传统到现代的.NET桌面开发
  • 怀柔做网站设计师网上接单被骗