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
相关产品推荐
相关产品推荐

