同一服务器上Spring Authorization Server与资源服务器的不透明令牌对接问题
问题解答
1. 自行实现OpaqueTokenIntrospector的可行性
完全可行,且在授权服务与业务端点同服务器部署的场景下,这种方式比调用远程/oauth2/introspect接口更高效,省去了内部HTTP请求的开销。
核心思路是借助OAuth2AuthorizationService直接查询令牌状态与信息,示例实现如下:
@Component public class LocalOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private final OAuth2AuthorizationService authorizationService; public LocalOpaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) { this.authorizationService = authorizationService; } @Override public OAuth2AuthenticatedPrincipal introspect(String token) { // 查询令牌对应的授权记录 OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); if (authorization == null) { throw new OAuth2IntrospectionException("无效的访问令牌"); } // 验证令牌有效性 OAuth2AccessToken accessToken = authorization.getAccessToken(); if (accessToken.isExpired() || !authorization.isActive()) { throw new OAuth2IntrospectionException("令牌已过期或已失效"); } // 构建认证主体,包含用户信息与权限 Map<String, Object> attributes = new HashMap<>(); attributes.putAll(authorization.getAttributes()); attributes.put(OAuth2IntrospectionClaimNames.ACTIVE, true); attributes.put(OAuth2IntrospectionClaimNames.SCOPE, accessToken.getScopes()); attributes.put(OAuth2IntrospectionClaimNames.USERNAME, authorization.getPrincipalName()); return new DefaultOAuth2AuthenticatedPrincipal( authorization.getPrincipalName(), attributes, AuthorityUtils.createAuthorityList(accessToken.getScopes().stream() .map(scope -> "SCOPE_" + scope) .toArray(String[]::new)) ); } }
2. 更简洁的替代方案
方案一:拆分SecurityFilterChain配置
你当前启动失败的根源之一是将授权服务器配置与资源服务器配置混在了同一个SecurityFilterChain中。正确做法是拆分两个独立的FilterChain:
- 高优先级FilterChain专门处理授权服务器端点(如
/oauth2/authorize、/oauth2/token等) - 低优先级FilterChain处理业务端点,配置资源服务器的不透明令牌验证
示例配置:
// 授权服务器FilterChain @Bean @Order(Ordered.HIGHEST_PRECEDENCE + 1) public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); authorizationServerConfigurer.oidc(Customizer.withDefaults()); RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); http .securityMatcher(endpointsMatcher) .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)) .exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) ) .apply(authorizationServerConfigurer); return http.build(); } // 业务端点(资源服务器)FilterChain @Bean @Order(Ordered.HIGHEST_PRECEDENCE + 2) public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2 .opaqueToken(Customizer.withDefaults()) // 自动使用自定义的LocalOpaqueTokenIntrospector ); return http.build(); }
方案二:使用NimbusOpaqueTokenIntrospector调用本地 introspect 接口
如果不介意内部HTTP请求开销,可直接配置Spring Security提供的NimbusOpaqueTokenIntrospector,无需自定义实现:
@Bean public OpaqueTokenIntrospector opaqueTokenIntrospector() { return new NimbusOpaqueTokenIntrospector( "http://localhost:8080/oauth2/introspect", // 本地introspect接口地址 "your-client-id", // 调用接口的客户端ID "your-client-secret" // 客户端密钥 ); }
3. 关键注意事项
- 生产环境需将默认的
InMemoryOAuth2AuthorizationService替换为数据库实现的服务 - 令牌有效性验证需覆盖过期、撤销、状态异常等所有维度
- 配置多个FilterChain时,授权服务器的优先级必须高于资源服务器
内容的提问来源于stack exchange,提问作者SuperMario
相关产品推荐
相关产品推荐

