Spring Security源码解析(七)授权机制:从AuthorizeHttpRequestsConfigurer到 AuthorizationFilter

13 阅读6分钟

http.authorizeHttpRequests() 一行配置,如何在运行时变成权限校验逻辑?本文深入拆解 AuthorizeHttpRequestsConfigurer 的配置收集、AuthorizationManager 的映射注册,以及 AuthorizationFilter 的运行时校验全流程。

前言

认证回答了"你是谁",授权回答了"你能做什么"。在 Spring Security 中,URL 授权是最基础也最常用的授权方式——通过配置 URL 路径与权限的映射关系,控制哪些请求需要认证、哪些需要特定角色。

本文将追踪 http.authorizeHttpRequests(auth -> auth.requestMatchers("/admin/**").hasRole("ADMIN")) 这一行配置,从配置阶段到运行时校验的完整链路。


一、配置阶段:AuthorizeHttpRequestsConfigurer

1.1 入口方法

// HttpSecurity.authorizeHttpRequests()
public HttpSecurity authorizeHttpRequests(
        Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry> authorizeHttpRequestsCustomizer)
        throws Exception {

    ApplicationContext context = getContext();
    // 获取或创建 AuthorizeHttpRequestsConfigurer,应用 Lambda 配置
    authorizeHttpRequestsCustomizer
        .customize(getOrApply(new AuthorizeHttpRequestsConfigurer<>(context)).getRegistry());

    return HttpSecurity.this;
}

1.2 AuthorizationManagerRequestMatcherRegistry:配置收集器

这是 AuthorizeHttpRequestsConfigurer 的内部类,负责收集 URL 路径与授权管理器的映射关系。它继承自 AbstractRequestMatcherRegistry(提供 requestMatchers() / anyRequest() 等路径匹配入口),通过内部持有的 RequestMatcherDelegatingAuthorizationManager.Builder 逐步收集映射:

public final class AuthorizationManagerRequestMatcherRegistry
        extends AbstractRequestMatcherRegistry<AuthorizedUrl> {

    // 核心:使用 Builder 模式收集所有映射
    private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder =
        RequestMatcherDelegatingAuthorizationManager.builder();

    // requestMatchers(String...) 由父类 AbstractRequestMatcherRegistry 实现
    // 它会创建 AntPathRequestMatcher/MvcRequestMatcher,最终回调本方法
    @Override
    protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
        return new AuthorizedUrl(requestMatchers);
    }

    // 添加映射(由 AuthorizedUrl.access() 最终调用)
    private void addMapping(RequestMatcher matcher,
            AuthorizationManager<RequestAuthorizationContext> manager) {
        this.managerBuilder.add(matcher, manager);
    }

    // 构建最终的 AuthorizationManager
    private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
        return (AuthorizationManager<HttpServletRequest>) this.managerBuilder.build();
    }
}

1.3 AuthorizedUrl:权限配置的链式 API

AuthorizedUrl 提供了各种权限配置方法:

public class AuthorizedUrl {
    private final List<? extends RequestMatcher> matchers;

    // 允许所有人访问 → SingleResultAuthorizationManager.permitAll()
    public AuthorizationManagerRequestMatcherRegistry permitAll() {
        return access(SingleResultAuthorizationManager.permitAll());
    }

    // 拒绝所有访问 → SingleResultAuthorizationManager.denyAll()
    public AuthorizationManagerRequestMatcherRegistry denyAll() {
        return access(SingleResultAuthorizationManager.denyAll());
    }

    // 需要认证(检查 Authentication 非 null 且已认证)
    public AuthorizationManagerRequestMatcherRegistry authenticated() {
        return access(AuthenticatedAuthorizationManager.authenticated());
    }

    // 需要特定角色(自动拼接 ROLE_ 前缀,支持 RoleHierarchy)
    public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
        return access(AuthorityAuthorizationManager.hasAnyRole(rolePrefix, new String[]{role}));
    }

    // 需要特定权限(不做前缀处理)
    public AuthorizationManagerRequestMatcherRegistry hasAuthority(String authority) {
        return access(AuthorityAuthorizationManager.hasAuthority(authority));
    }

    // 所有权限方法最终汇聚于此:遍历 matchers 逐个注册到 configurer
    public AuthorizationManagerRequestMatcherRegistry access(
            AuthorizationManager<RequestAuthorizationContext> manager) {
        return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
    }
}

1.4 配置示例解析

http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/public/**").permitAll()        // 映射1
    .requestMatchers("/admin/**").hasRole("ADMIN")    // 映射2
    .requestMatchers("/api/**").authenticated()       // 映射3
    .anyRequest().denyAll()                           // 映射4
);

配置收集结果

映射RequestMatcherAuthorizationManager
1/public/**permitAll → 始终授权
2/admin/**hasRole("ADMIN") → 检查 ROLE_ADMIN
3/api/**authenticated → 检查是否已认证
4anyRequestdenyAll → 始终拒绝

image.png

配置收集流程requestMatchers() → 创建 RequestMatcher → AuthorizedUrl 链式调用 → access()addMapping()managerBuilder.add()。最终所有映射存储在 RequestMatcherDelegatingAuthorizationManager.Builder 中。


二、构建阶段:注册 AuthorizationFilter

2.1 AuthorizeHttpRequestsConfigurer.configure()

@Override
public void configure(H http) {
    // 1. 创建 AuthorizationManager
    AuthorizationManager<HttpServletRequest> authorizationManager = 
        this.registry.createAuthorizationManager();
    
    // 2. 创建 AuthorizationFilter
    AuthorizationFilter authorizationFilter = 
        new AuthorizationFilter(authorizationManager);
    
    // 3. 设置事件发布器
    authorizationFilter.setAuthorizationEventPublisher(this.publisher);
    
    // 4. 添加到 HttpSecurity
    http.addFilter(postProcess(authorizationFilter));
}

2.2 createAuthorizationManager:合并映射

// AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
    // 确保没有未完成的映射(例如只写了 requestMatchers 但忘了写权限规则)
    Assert.state(this.unmappedMatchers == null,
            "An incomplete mapping was found...");
    // 确保至少有一条映射
    Assert.state(this.mappingCount > 0,
            "At least one mapping is required...");
    // 通过 Builder 构建最终的 RequestMatcherDelegatingAuthorizationManager
    return (AuthorizationManager<HttpServletRequest>) this.managerBuilder.build();
}

RequestMatcherDelegatingAuthorizationManager 的核心逻辑:

public class RequestMatcherDelegatingAuthorizationManager 
        implements AuthorizationManager<HttpServletRequest> {
    
    private final List<RequestMatcherEntry<AuthorizationManager<HttpServletRequest>>> mappings;
    
    @Override
    public AuthorizationDecision check(
            Supplier<Authentication> authentication, HttpServletRequest request) {
        
        // 遍历所有映射
        for (RequestMatcherEntry<AuthorizationManager<HttpServletRequest>> entry : this.mappings) {
            
            // 1. 检查请求是否匹配
            if (entry.getRequestMatcher().matches(request)) {
                // 2. 委托给对应的 AuthorizationManager
                return entry.getEntry().check(authentication, request);
            }
        }
        
        // 没有匹配的映射,默认拒绝
        return new AuthorizationDecision(false);
    }
}
7.1.drawio.png

三、运行时阶段:AuthorizationFilter 校验

3.1 AuthorizationFilter.doFilter() 完整源码

public class AuthorizationFilter extends GenericFilterBean {
    private SecurityContextHolderStrategy securityContextHolderStrategy = 
        SecurityContextHolder.getContextHolderStrategy();
    private final AuthorizationManager<HttpServletRequest> authorizationManager;
    private AuthorizationEventPublisher eventPublisher;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
            FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // ① 防止同一请求被重复过滤
        if (this.observeOncePerRequest && isApplied(request)) {
            chain.doFilter(request, response);
            return;
        }

        // ② 跳过某些特殊 dispatcher 类型(如 ERROR dispatch)
        if (skipDispatch(request)) {
            chain.doFilter(request, response);
            return;
        }

        // ③ 标记已过滤
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

        try {
            // ④ 核心:调用授权管理器进行校验
            AuthorizationResult result = this.authorizationManager.authorize(
                this::getAuthentication, request);
            // ⑤ 发布授权事件(可被监控系统采集)
            this.eventPublisher.publishAuthorizationEvent(
                this::getAuthentication, request, result);
            // ⑥ 授权不通过 → 抛出异常,由 ExceptionTranslationFilter 捕获
            if (result != null && !result.isGranted()) {
                throw new AuthorizationDeniedException("Access Denied", result);
            }
            // ⑦ 授权通过 → 继续过滤器链
            chain.doFilter(request, response);
        } finally {
            // ⑧ 清除标记(避免同一线程复用时误判)
            request.removeAttribute(alreadyFilteredAttributeName);
        }
    }

    private Authentication getAuthentication() {
        Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
        if (authentication == null) {
            throw new AuthenticationCredentialsNotFoundException(
                    "An Authentication object was not found in the SecurityContext");
        }
        return authentication;
    }
}

关键点:注意第⑥步,AuthorizationFilter 不直接返回 403,而是抛出 AuthorizationDeniedException(继承自 AccessDeniedException)。这个异常会被后面的 ExceptionTranslationFilter 捕获,并根据用户是否已登录来决定返回 401(未登录)还是 403(已登录但无权限)。

3.2 校验流程图

7.2.drawio.png

四、AuthorizationManager 体系完整拆解

4.1 RequestMatcherDelegatingAuthorizationManager:按 URL 匹配委托

这是 URL 授权的核心路由器,内部维护了一个 mappings 列表,按顺序匹配 URL:

public final class RequestMatcherDelegatingAuthorizationManager 
        implements AuthorizationManager<HttpServletRequest> {

    private static final AuthorizationDecision DENY = new AuthorizationDecision(false);
    private final List<RequestMatcherEntry<AuthorizationManager<? super RequestAuthorizationContext>>> mappings;

    @Override
    public AuthorizationResult authorize(
            Supplier<? extends Authentication> authentication, 
            HttpServletRequest request) {
        // 遍历所有映射
        for (RequestMatcherEntry<AuthorizationManager<? super RequestAuthorizationContext>> mapping : this.mappings) {
            RequestMatcher matcher = mapping.getRequestMatcher();
            MatchResult matchResult = matcher.matcher(request);
            if (matchResult.isMatch()) {
                // 找到匹配的 URL 规则 → 委托给对应的 AuthorizationManager
                AuthorizationManager<? super RequestAuthorizationContext> manager = mapping.getEntry();
                return manager.authorize(authentication, 
                    new RequestAuthorizationContext(request, matchResult.getVariables()));
            }
        }
        // ★ 没有任何规则匹配 → 默认拒绝(Security by Default)
        return DENY;
    }
}
7.3.drawio.png

关键设计mappings 的顺序就是你在 authorizeHttpRequests() 中配置的顺序——先配置的规则优先匹配。

4.2 SingleResultAuthorizationManager:最简授权管理器

permitAll()denyAll() 背后的实现非常简单——直接返回构造时传入的结果:

public final class SingleResultAuthorizationManager<C> implements AuthorizationManager<C> {
    private static final SingleResultAuthorizationManager<?> DENY_MANAGER = 
        new SingleResultAuthorizationManager<>(new AuthorizationDecision(false));
    private static final SingleResultAuthorizationManager<?> PERMIT_MANAGER = 
        new SingleResultAuthorizationManager<>(new AuthorizationDecision(true));

    private final AuthorizationResult result;

    @Override
    public AuthorizationResult authorize(
            Supplier<? extends Authentication> authentication, C object) {
        return this.result;  // 不检查任何东西,直接返回预设结果
    }
}

4.3 常用 AuthorizationManager 对照表

Manager对应配置核心逻辑
SingleResultAuthorizationManagerpermitAll() / denyAll()直接返回预设结果,不做任何检查
AuthenticatedAuthorizationManagerauthenticated()检查 Authentication 是否非 null 且已认证
AuthorityAuthorizationManagerhasRole() / hasAuthority()检查用户权限列表中是否包含指定权限
RequestMatcherDelegatingAuthorizationManager多条规则URL 匹配 + 委托给对应的 Manager

五、URL 授权 vs 方法授权

维度URL 授权方法授权
配置方式http.authorizeHttpRequests()@PreAuthorize / @Secured
粒度URL 路径级别方法级别
灵活性较低(仅 URL)高(SpEL 表达式)
执行时机AuthorizationFilterAOP 代理
适用场景粗粒度访问控制细粒度业务权限

推荐实践:URL 授权做粗粒度控制(哪些路径需要认证),方法授权做细粒度控制(具体业务操作权限)。


六、实战:自定义 AuthorizationManager

// 自定义 IP 白名单授权管理器
// 注意:access() 接受的参数类型是 AuthorizationManager<RequestAuthorizationContext>
public class IpWhitelistAuthorizationManager
        implements AuthorizationManager<RequestAuthorizationContext> {

    private final List<String> allowedIps;

    public IpWhitelistAuthorizationManager(List<String> allowedIps) {
        this.allowedIps = allowedIps;
    }

    @Override
    public AuthorizationResult authorize(
            Supplier<Authentication> authentication, RequestAuthorizationContext context) {

        String remoteAddr = context.getRequest().getRemoteAddr();
        boolean allowed = allowedIps.contains(remoteAddr);

        return new AuthorizationDecision(allowed);
    }

    // 兼容旧版 API(6.4 前),同时覆盖 check()
    @Override
    @Deprecated
    public AuthorizationDecision check(
            Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        return (AuthorizationDecision) authorize(authentication, context);
    }
}

// 使用
http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/internal/**")
        .access(new IpWhitelistAuthorizationManager(
            List.of("192.168.1.0/24", "10.0.0.0/8")))
);

七、总结

阶段组件核心操作
配置阶段AuthorizeHttpRequestsConfigurer收集 URL → AuthorizationManager 映射
构建阶段AuthorizeHttpRequestsConfigurer.configure()创建 AuthorizationFilter 并注册
运行时AuthorizationFilter.doFilter()遍历映射,委托给 AuthorizationManager 校验
校验失败ExceptionTranslationFilter将 AuthorizationDeniedException 转为 401/403
AuthorizationManager配置方法校验逻辑
SingleResultAuthorizationManagerpermitAll() / denyAll()始终授权 / 始终拒绝
AuthenticatedAuthorizationManagerauthenticated()检查已认证
AuthorityAuthorizationManagerhasRole() / hasAuthority()检查权限

下一篇预告:《Spring Security 方法安全授权与自定义扩展:@EnableMethodSecurity、AOP、SpEL 全链路》将深入拆解方法级授权的启用原理、@PreAuthorize 的 AOP 拦截机制,以及基于 SpEL 和自定义 AuthorizationManager 的两种生产级扩展方案。