JWT(5)JWT+spring security

多来源 token (前端、各个三方)的信任配置、统一验证、身份映射,以及 Spring Security 如何衔接授权检查。

HTTP 请求

→ Servlet 容器

→ DelegatingFilterProxy

→ FilterChainProxy:选择匹配的 SecurityFilterChain

→ BearerTokenAuthenticationFilter:提取 Bearer Token

→ AuthenticationManagerResolver:选择可信认证策略

→ AuthenticationManager(ProviderManager)

→ JwtAuthenticationProvider

→ JwtDecoder:验签并校验声明

→ 自定义身份转换器:状态检查、身份映射、权限计算

← 返回已认证的 Authentication

→ 将 Authentication 写入 SecurityContextHolder

→ AuthorizationFilter

→ AuthorizationManager:检查认证状态及接口权限

→ DispatcherServlet

→ Controller

→ Service:加载真实资源并检查租户、归属等权限

→ 业务处理

请求结束 → 框架清理 SecurityContext

spring security:

复制代码
@Bean
SecurityFilterChain securityFilterChain(
        HttpSecurity http,
        AuthenticationManagerResolver<HttpServletRequest> resolver,
        AuthorizationManager<RequestAuthorizationContext> authorizationManager) {

    // 1、注册安全过滤器链,框架自动添加 Bearer Token 认证过滤器和授权过滤器
    http.sessionManagement(session ->
        session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

    http.oauth2ResourceServer(resourceServer ->
        resourceServer.authenticationManagerResolver(resolver));

    http.authorizeHttpRequests(authorize ->
        authorize.anyRequest().access(authorizationManager));

    return http.build();
}

@Bean
AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver() {
    Map<String, AuthenticationManager> managers = new HashMap<>();

    // 启动时为已登记的可信策略创建认证管理器
    for (TrustPolicy policy : trustedPolicies.loadAll()) {
        managers.put(policy.id(), buildAuthenticationManager(policy));
    }

    return request -> {
        // 2、认证过滤器调用此处,未验签信息仅用于选择已登记的可信策略
        String token = bearerTokenResolver.resolve(request);
        TokenHint hint = tokenReader.peekBounded(token);
        TrustPolicy policy = trustedPolicies.requireMatch(request, hint);
        return managers.get(policy.id());
    };
}

AuthenticationManager buildAuthenticationManager(TrustPolicy policy) {
    // 3、Provider 调用 Decoder,使用可信密钥验签并校验算法、iss、aud、类型、必要声明、exp 和 nbf
    JwtDecoder decoder = token -> jwtVerifier.verify(token, policy);
    JwtAuthenticationProvider provider = new JwtAuthenticationProvider(decoder);

    provider.setJwtAuthenticationConverter(jwt -> {
        // 4、验签通过后按策略检查撤销状态,凭证失效时抛出认证异常
        tokenStatusValidator.validate(jwt, policy);

        // 5、将已验证声明映射为统一身份,保留用户或服务类型、签发方、主体、租户及委托关系
        Identity identity = identityMapper.mapVerified(jwt, policy);
        identityValidator.validate(identity, policy);

        // 6、计算有效权限,同时满足 Token 授权范围与主体当前权限约束
        Set<GrantedAuthority> authorities = permissionResolver.intersect(
            policy.mapGrantedAuthorities(jwt),
            permissionService.loadAllowedAuthorities(identity));

        // 7、返回已认证结果,由框架过滤器写入 SecurityContextHolder
        return identityTokens.authenticated(identity, authorities);
    });

    return new ProviderManager(provider);
}

@Bean
AuthorizationManager<RequestAuthorizationContext> authorizationManager() {
    return (authenticationSupplier, context) -> {
        // 8、授权过滤器调用此处,从安全上下文取得身份并检查接口权限,匿名身份及未配置操作默认拒绝
        Authentication authentication = authenticationSupplier.get();
        boolean allowed = authorizationService.allowEndpoint(
            authentication, context.getRequest());

        return new AuthorizationDecision(allowed);
    };
}

securityFilterChain() 在启动时完成注册;请求到达后,由框架过滤器调用 Resolver,再调用 Manager、Provider、Decoder 和身份转换器,Controller 无需手动启动这条链路。

认证失败由框架转换为 401,已认证但权限不足返回 403。依赖真实业务对象的资源权限,在 Service 加载资源后继续检查。token验证+身份解析:

复制代码
void handleRequest(Request request, Response response, FilterChain chain) {
    try {
        // 第一步:从 Authorization: Bearer <token> 提取凭证
        // 同时检查格式和长度,缺失或非法时抛出认证异常
        String token = tokenExtractor.requireBearerToken(request);

        // 第二步:从预先登记的可信配置中选择验证策略
        // 未验签的 issuer、tokenType 仅用于策略查找,不能作为身份依据
        TokenHint hint = tokenReader.peek(token);
        TrustPolicy policy = trustedPolicies.requireMatch(request, hint);

        // 第三步:验证签名及声明,全部通过后才能信任 Token 内容
        // 包括:允许的算法、可信密钥、issuer、audience、类型、exp、nbf
        VerifiedClaims claims = tokenVerifier.verify(token, policy);

        // 第四步:按策略检查 Token 撤销状态
        // 必需的状态检查无法完成时,终止请求
        tokenStatusValidator.validate(claims, policy);

        // 第五步:将不同 Token 的声明映射为统一身份,并检查主体状态
        // 保留主体类型、issuer + subject、调用应用、租户和委托关系
        Identity identity = identityMapper.map(claims, policy);
        identityValidator.validate(identity, policy);

        // 第六步:计算有效权限,同时满足 Token 授权与主体权限约束
        Set<Permission> permissions = permissionResolver.intersect(
            policy.mapGrantedPermissions(claims),
            permissionService.loadAllowedPermissions(identity)
        );

        // 第七步:创建认证结果,写入请求级安全上下文
        Authentication authentication =
            authenticationFactory.authenticated(identity, permissions);

        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        SecurityContextHolder.setContext(context);

        // 第八步:执行授权检查,通过后进入业务处理
        // 检查接口操作权限、资源归属和租户边界
        authorizationService.check(identity, permissions, request);
        audit.record("AUTHENTICATION_SUCCEEDED");
        chain.doFilter(request, response);

    } catch (AuthenticationException e) {
        audit.record("AUTHENTICATION_FAILED");
        response.setHeader("WWW-Authenticate", "Bearer");
        response.setStatus(401);

    } catch (AccessDeniedException e) {
        audit.record("AUTHORIZATION_DENIED");
        response.setStatus(403);

    } finally {
        // 请求结束后清理身份上下文,避免线程复用导致身份串用
        SecurityContextHolder.clearContext();
    }
}
相关推荐
Rain的Java大神之路37 分钟前
LeetCode 21 合并两个有序链表:一道 Easy 题,凭什么刷掉一半候选人?
java·后端·算法
小白的码BUG之路41 分钟前
K8S -- 集群搭建环境基础
java·服务器·kubernetes
wenyq71 小时前
LeetCode 152. Maximum Product Subarray
java·算法·leetcode
余额瞒着我当琳1 小时前
C++异常,异常的概念,异常抛出与捕获,RAII 和智能指针的设计思路,C++标准库一些指针的使用
java·开发语言·c++·算法
花间相见1 小时前
【macOS开发环境:Java配置】—— Mac上的Java版本管理-SDKMAN安装、多版本切换与项目实战
java·架构·个人开发
骑着蜗牛撵大象3271 小时前
大模型 SDK 编译就崩?用 CMakeLists 把 httplib、JSON 与 OpenSSL 装配成可复用的 ChatServer
android·java·c++·http·json·cmake·大模型sdk
念何架构之路1 小时前
OpenTelemetry核心概念
后端·目标跟踪·golang·c#·linq