Spring Boot OAuth2资源服务器:如何强制用GET调用令牌Introspection端点?
解决Spring Security资源服务器强制使用GET请求调用令牌自省(Introspection)端点的问题
问题背景
在基于Spring Boot开发集成外部授权服务器的OAuth2资源服务器时,配置令牌自省端点后遇到以下错误:
org.springframework.security.authentication.AuthenticationServiceException: Introspection endpoint responded with 201 CREATED
经排查,授权服务器的/auth/introspect端点同时支持GET和POST请求,但Spring Security默认使用POST请求调用该端点,导致返回异常状态码,需强制改为GET请求。
当前配置(application.properties):
spring.security.oauth2.resourceserver.opaque-token.introspection-uri=https://server-name/auth/introspect spring.security.oauth2.resourceserver.opaque-token.client-id=client-id spring.security.oauth2.resourceserver.opaque-token.client-secret=secret
依赖版本:
- Spring Boot 3.1.2
- Spring Security OAuth2 Resource Server 6.1.2
- oauth2-oidc-sdk 10.13.2
- spring-boot-starter-security
解决方案
要强制使用GET请求调用自省端点,需自定义OpaqueTokenIntrospector实现类,替换Spring Security默认的POST请求逻辑。
1. 自定义令牌自省器
创建自定义的OpaqueTokenIntrospector,通过RestTemplate发送GET请求,并传递必要参数:
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.web.client.RestTemplate; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.util.Map; public class GetOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private final RestTemplate restTemplate; private final String introspectionUri; private final String clientId; private final String clientSecret; public GetOpaqueTokenIntrospector(RestTemplate restTemplate, String introspectionUri, String clientId, String clientSecret) { this.restTemplate = restTemplate; this.introspectionUri = introspectionUri; this.clientId = clientId; this.clientSecret = clientSecret; } @Override public OAuth2AuthenticatedPrincipal introspect(String token) { // 构造GET请求参数 MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("token", token); params.add("client_id", clientId); params.add("client_secret", clientSecret); // 发送GET请求获取自省响应 Map<String, Object> introspectionResponse = restTemplate.getForObject(introspectionUri, Map.class, params); // 验证令牌是否有效 if (introspectionResponse == null || !Boolean.TRUE.equals(introspectionResponse.get("active"))) { throw new IllegalArgumentException("无效或已过期的令牌"); } return new OAuth2IntrospectionAuthenticatedPrincipal(introspectionResponse); } }
2. 配置Spring Security使用自定义自省器
修改Spring Security配置类,替换默认的OpaqueTokenIntrospector:
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.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.client.RestTemplate; @Configuration @EnableWebSecurity public class ResourceServerConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .opaqueToken(token -> token.introspector(opaqueTokenIntrospector())) ); return http.build(); } @Bean public OpaqueTokenIntrospector opaqueTokenIntrospector() { String introspectionUri = "https://server-name/auth/introspect"; String clientId = "client-id"; String clientSecret = "secret"; RestTemplate restTemplate = new RestTemplate(); // 如果授权服务器要求Basic认证,可添加拦截器(可选) // restTemplate.getInterceptors().add(new BasicAuthorizationInterceptor(clientId, clientSecret)); return new GetOpaqueTokenIntrospector(restTemplate, introspectionUri, clientId, clientSecret); } }
3. 可选调整:Basic认证方式
如果授权服务器要求客户端通过Basic Auth认证,而非将client_id和client_secret作为请求参数,可启用上述代码中的BasicAuthorizationInterceptor,并移除请求参数中的client_id和client_secret:
// 构造GET请求参数时仅传递token MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("token", token);
注意事项
- 确保授权服务器的GET自省端点能正确处理请求参数,并返回符合OAuth2规范的响应(必须包含
active字段)。 - 可根据实际需求对
RestTemplate进行自定义配置(如超时、SSL设置等)。
内容的提问来源于stack exchange,提问作者Fabiano Armando
相关产品推荐
相关产品推荐

