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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 22:35:01