You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security多OAuth2流程适配:仅授权码流调用用户自省端点

解决方案:区分OAuth2流的令牌处理逻辑

核心思路

利用AWS Cognito令牌自带的token_use字段区分两种流:

  • Authorization Code流的access token,token_use值为access,需要调用自定义自省端点获取用户权限
  • Client Credentials流的access token,token_use值为client,直接基于令牌内的客户端权限构建认证对象,跳过自省请求

步骤1:自定义OpaqueTokenIntrospector

实现OpaqueTokenIntrospector接口,在内部判断令牌类型并分支处理:

import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;

import java.text.ParseException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class CognitoTokenIntrospector implements OpaqueTokenIntrospector {

    private final OpaqueTokenIntrospector userTokenIntrospector;
    private final String clientAuthorityPrefix;

    public CognitoTokenIntrospector(String introspectionUri, String clientId, String clientSecret, String authorityPrefix) {
        this.userTokenIntrospector = new NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret);
        this.clientAuthorityPrefix = authorityPrefix;
    }

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        try {
            // 解析Cognito access token的payload,提取token_use字段
            JWT jwt = JWTParser.parse(token);
            Map<String, Object> payload = jwt.getJWTClaimsSet().getClaims();
            String tokenUse = (String) payload.get("token_use");

            if ("client".equals(tokenUse)) {
                // Client Credentials流:直接从令牌提取客户端权限构建认证对象
                return new OAuth2AuthenticatedPrincipal() {
                    @Override
                    public Map<String, Object> getAttributes() {
                        return payload;
                    }

                    @Override
                    public Collection<? extends GrantedAuthority> getAuthorities() {
                        List<String> scopes = (List<String>) payload.get("scope");
                        return scopes.stream()
                                .map(scope -> new SimpleGrantedAuthority(clientAuthorityPrefix + scope))
                                .collect(Collectors.toList());
                    }

                    @Override
                    public String getName() {
                        return (String) payload.get("client_id");
                    }
                };
            } else if ("access".equals(tokenUse)) {
                // Authorization Code流:调用自定义自省端点获取用户权限
                return userTokenIntrospector.introspect(token);
            }
        } catch (ParseException e) {
            throw new OAuth2AuthenticationException("无效的令牌格式", e);
        }

        throw new OAuth2AuthenticationException("不支持的令牌类型");
    }
}

步骤2:配置SecurityFilterChain

在配置类中注册自定义的自省器,并替换默认实现:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CognitoConfig cognitoConfig; // 自定义配置类,存储Cognito的自省地址、客户端ID/密钥等参数

    public SecurityConfig(CognitoConfig cognitoConfig) {
        this.cognitoConfig = cognitoConfig;
    }

    @Bean
    public OpaqueTokenIntrospector cognitoTokenIntrospector() {
        return new CognitoTokenIntrospector(
                cognitoConfig.getIntrospectionUri(),
                cognitoConfig.getClientId(),
                cognitoConfig.getClientSecret(),
                "SCOPE_" // 客户端权限前缀,可根据实际需求调整
        );
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .opaqueToken(token -> token
                                .introspector(cognitoTokenIntrospector()) // 应用自定义自省器
                        )
                )
                .oauth2Client(); // 保留Client Credentials流的客户端配置

        return http.build();
    }
}

关键细节说明

  • 令牌解析优化:Cognito的access token是JWT格式,直接解析payload无需额外网络请求,避免不必要的性能损耗
  • 权限格式统一:Client Credentials流的权限来自令牌的scope字段,转换为GrantedAuthority时添加前缀,保持和Authorization Code流的权限格式一致
  • 异常处理:解析失败或未知令牌类型时抛出OAuth2AuthenticationException,Spring Security会自动返回401 Unauthorized响应

内容的提问来源于stack exchange,提问作者Leonardo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 01:37:32