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格式的请求,同时带上第三方要求的audience和User-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_id、client_secret、audience一定要和第三方提供的完全一致,别写错字母; - 查看错误响应:如果请求失败,抓包看第三方返回的错误信息(比如
invalid_client、missing_parameter),根据提示调整; - 日志排查:开启Spring Security的DEBUG日志,能看到请求和响应的详细过程,方便定位问题。
内容的提问来源于stack exchange,提问作者stumbler




