You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot中基于第三方OAuth2认证生成Access Token及缓存Bearer Token问题求助

嘿,我来帮你解决这个第三方OAuth2认证的问题!看你已经搭了基础,但卡在生成Access Token上,结合你给的第三方接口细节,咱们一步步来搞定:

1. 先搞懂核心问题:第三方要求JSON格式请求

你提到的第三方认证接口要求用Content-Type: application/json发送请求体,但Spring OAuth2 Client默认用的是application/x-www-form-urlencoded,这大概率是你拿不到Token的核心原因!咱们先把这个问题解决。

2. 配置OAuth2客户端基础信息

首先在application.yml里配置第三方的客户端和认证服务信息:

spring:
  security:
    oauth2:
      client:
        registration:
          abc-auth: # 自定义的客户端标识,后面要用到
            client-id: CLIENT_ID_HERE
            client-secret: CLIENT_SECRET_HERE
            authorization-grant-type: client_credentials
        provider:
          abc-auth:
            token-uri: https://auth.abc.com/oauth/token # 第三方的Token接口地址
  # 缓存配置:用来存Token,避免频繁调用第三方接口
  cache:
    type: caffeine
    caffeine:
      spec: expireAfterWrite=86000s # 比第三方返回的expires_in少400秒,提前刷新Token
3. 自定义请求转换器(关键!)

写一个转换器,让Spring OAuth2 Client发送JSON格式的请求,同时带上第三方要求的audienceUser-Agent

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequestEntityConverter;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

public class CustomJsonClientCredentialsConverter extends OAuth2ClientCredentialsGrantRequestEntityConverter {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public RequestEntity<?> convert(OAuth2ClientCredentialsGrantRequest grantRequest) {
        URI tokenUri = grantRequest.getClientRegistration().getProviderDetails().getTokenUri();

        // 构造第三方要求的JSON请求体
        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put(OAuth2ParameterNames.CLIENT_ID, grantRequest.getClientRegistration().getClientId());
        requestBody.put(OAuth2ParameterNames.CLIENT_SECRET, grantRequest.getClientRegistration().getClientSecret());
        requestBody.put(OAuth2ParameterNames.GRANT_TYPE, grantRequest.getGrantType().getValue());
        requestBody.put("audience", "https://graphql.abc.com"); // 第三方要求的audience

        // 设置请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setUserAgent("YourPartnerService/1.2.3"); // 第三方要求的User-Agent

        try {
            return RequestEntity.post(tokenUri)
                    .headers(headers)
                    .body(objectMapper.writeValueAsString(requestBody));
        } catch (Exception e) {
            throw new RuntimeException("序列化请求体失败", e);
        }
    }
}

然后配置这个转换器到OAuth2客户端管理器:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;

@Configuration
public class OAuth2ClientConfig {

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepo,
            OAuth2AuthorizedClientRepository authorizedClientRepo) {

        DefaultClientCredentialsTokenResponseClient tokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
        // 绑定自定义的JSON请求转换器
        tokenResponseClient.setRequestEntityConverter(new CustomJsonClientCredentialsConverter());

        OAuth2AuthorizedClientProvider clientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials(c -> c.tokenResponseClient(tokenResponseClient))
                .build();

        DefaultOAuth2AuthorizedClientManager clientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepo, authorizedClientRepo);
        clientManager.setAuthorizedClientProvider(clientProvider);

        return clientManager;
    }
}
4. 实现Token获取与缓存

先在启动类上开启Spring缓存:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class YourMicroserviceApplication {
    public static void main(String[] args) {
        SpringApplication.run(YourMicroserviceApplication.class, args);
    }
}

然后写一个Token服务类,用@Cacheable缓存Token:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.request.OAuth2AuthorizeRequest;
import org.springframework.stereotype.Service;

@Service
public class TokenService {

    private final OAuth2AuthorizedClientManager clientManager;

    public TokenService(OAuth2AuthorizedClientManager clientManager) {
        this.clientManager = clientManager;
    }

    @Cacheable(value = "oauth2Tokens", key = "#clientRegistrationId")
    public String getAccessToken(String clientRegistrationId) {
        OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
                .build();
        OAuth2AuthorizedClient authorizedClient = clientManager.authorize(authorizeRequest);

        if (authorizedClient == null || authorizedClient.getAccessToken() == null) {
            throw new IllegalStateException("无法从第三方认证服务器获取Access Token");
        }
        return authorizedClient.getAccessToken().getTokenValue();
    }
}
5. 配置Spring Security强制所有请求认证

现在让你的微服务所有请求都必须经过认证,这里按你说的“所有请求必须经过认证,认证由第三方完成”,配置成资源服务器:

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;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().authenticated() // 所有请求都需要认证
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt
                                .jwkSetUri("https://auth.abc.com/.well-known/jwks.json") // 第三方的JWKS地址(如果Token是JWT)
                                // 如果是不透明Token,用 introspection 方式:
                                // .introspectionUri("https://auth.abc.com/oauth/introspect")
                                // .introspectionClientCredentials("CLIENT_ID_HERE", "CLIENT_SECRET_HERE")
                        )
                );
        return http.build();
    }
}
6. 测试Token获取

写一个简单的接口测试Token生成:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TokenTestController {

    private final TokenService tokenService;

    public TokenTestController(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @GetMapping("/test-token")
    public String getTestToken() {
        return tokenService.getAccessToken("abc-auth"); // 对应配置里的客户端标识
    }
}

访问这个接口,就能拿到第三方返回的Access Token,并且会自动缓存到配置的缓存中,下次调用会直接取缓存,直到缓存过期自动刷新。

排坑小贴士
  • 检查网络:确保你的服务能访问到第三方认证接口,没有防火墙、代理拦截;
  • 校验参数:client_idclient_secretaudience一定要和第三方提供的完全一致,别写错字母;
  • 查看错误响应:如果请求失败,抓包看第三方返回的错误信息(比如invalid_clientmissing_parameter),根据提示调整;
  • 日志排查:开启Spring Security的DEBUG日志,能看到请求和响应的详细过程,方便定位问题。

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

火山引擎 最新活动