如何将请求中当前Access Token添加到Feign微服务调用及处理过期问题
解决方案:Feign调用添加OAuth2 Access Token及定时任务Token过期处理
一、在Feign请求中添加当前用户的Access Token
1. 修正Feign拦截器,从Spring Security上下文获取Token
原有拦截器中Authentication头未填充,现代Spring Cloud OAuth2体系下,应通过SecurityContextHolder获取当前用户的OAuth2认证信息,生成标准的Authorization: Bearer {token}请求头:
import org.springframework.http.HttpHeaders; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Component; import feign.RequestInterceptor; import feign.RequestTemplate; @Component public class UserContextFeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // 传递自定义上下文头(原有逻辑保留) UserContext context = UserContextHolder.getContext(); template.header("tmx-correlation-id", context.getCorrelationId()); template.header("tmx-auth-token", context.getAuthToken()); template.header("tmx-user-id", context.getUserId()); template.header("tmx-organization-id", context.getOrganizationId()); // 添加OAuth2 Authorization头 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; OAuth2AuthorizedClient authorizedClient = oauthToken.getAuthorizedClient(); if (authorizedClient != null) { String accessToken = authorizedClient.getAccessToken().getTokenValue(); template.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); } } } }
2. 修正FeignClient配置
注意:@LoadBalanced注解不应直接标注在FeignClient的方法上(该注解用于RestTemplate/WebClient的负载均衡配置),需移除该注解:
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient(name = "organization-service", configuration = FeignConfiguration.class) public interface OrganizationFeignClient { @GetMapping("v1/organization/{organizationId}") Organization getOrganization(@PathVariable("organizationId") String organizationId); }
3. 确保Feign配置类注册拦截器
如果FeignConfiguration未显式注册拦截器,需补充:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfiguration { private final UserContextFeignInterceptor userContextFeignInterceptor; public FeignConfiguration(UserContextFeignInterceptor userContextFeignInterceptor) { this.userContextFeignInterceptor = userContextFeignInterceptor; } @Bean public RequestInterceptor requestInterceptor() { return userContextFeignInterceptor; } }
二、定时调用场景的Token过期处理
定时任务无用户上下文,无法从SecurityContext获取Token,需采用**客户端凭据模式(Client Credentials)**获取服务间调用的Token,并实现自动刷新逻辑:
1. 配置OAuth2客户端凭据
在application.yml中添加客户端凭据模式的OAuth2配置:
spring: security: oauth2: client: registration: organization-service-client: client-id: 你的服务客户端ID client-secret: 你的服务客户端密钥 authorization-grant-type: client_credentials provider: your-oauth-provider: token-uri: https://你的OAuth授权服务器地址/oauth2/token
2. 实现Token管理类(自动缓存与刷新)
创建专门的Token管理类,负责获取、缓存客户端凭据Token,并在即将过期时自动刷新:
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.stereotype.Component; import java.time.Instant; @Component public class OAuth2TokenManager { private final OAuth2AuthorizedClientService authorizedClientService; private final ClientRegistrationRepository clientRegistrationRepository; private String cachedAccessToken; private Instant tokenExpiryTime; private static final String CLIENT_REGISTRATION_ID = "organization-service-client"; public OAuth2TokenManager(OAuth2AuthorizedClientService authorizedClientService, ClientRegistrationRepository clientRegistrationRepository) { this.authorizedClientService = authorizedClientService; this.clientRegistrationRepository = clientRegistrationRepository; } public String getClientCredentialsToken() { // 提前60秒刷新Token,避免过期 if (cachedAccessToken == null || Instant.now().isAfter(tokenExpiryTime.minusSeconds(60))) { ClientRegistration registration = clientRegistrationRepository.findByRegistrationId(CLIENT_REGISTRATION_ID); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(registration.getRegistrationId()) .principal(registration.getClientId()) .build(); // 使用DefaultOAuth2AuthorizedClientManager自动处理Token获取与刷新 OAuth2AuthorizedClientManager clientManager = new org.springframework.security.oauth2.client.DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); OAuth2AuthorizedClient authorizedClient = clientManager.authorize(authorizeRequest); cachedAccessToken = authorizedClient.getAccessToken().getTokenValue(); tokenExpiryTime = authorizedClient.getAccessToken().getExpiresAt(); } return cachedAccessToken; } }
3. 修改Feign拦截器兼容定时任务场景
调整拦截器,当无用户上下文时自动使用客户端凭据Token:
@Component public class UserContextFeignInterceptor implements RequestInterceptor { private final OAuth2TokenManager tokenManager; public UserContextFeignInterceptor(OAuth2TokenManager tokenManager) { this.tokenManager = tokenManager; } @Override public void apply(RequestTemplate template) { // 传递自定义上下文头(原有逻辑保留) UserContext context = UserContextHolder.getContext(); template.header("tmx-correlation-id", context.getCorrelationId()); template.header("tmx-auth-token", context.getAuthToken()); template.header("tmx-user-id", context.getUserId()); template.header("tmx-organization-id", context.getOrganizationId()); // 根据场景选择Token来源 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String accessToken; if (authentication instanceof OAuth2AuthenticationToken) { // 用户请求场景:使用用户的Access Token OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; OAuth2AuthorizedClient authorizedClient = oauthToken.getAuthorizedClient(); accessToken = authorizedClient.getAccessToken().getTokenValue(); } else { // 定时任务场景:使用客户端凭据Token accessToken = tokenManager.getClientCredentialsToken(); } template.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken); } }
4. 定时任务中直接调用Feign接口
定时任务无需额外处理Token,拦截器会自动适配:
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class OrganizationScheduledTask { private final OrganizationFeignClient organizationFeignClient; public OrganizationScheduledTask(OrganizationFeignClient organizationFeignClient) { this.organizationFeignClient = organizationFeignClient; } @Scheduled(fixedRate = 3600000) // 每小时执行一次 public void syncOrganizationData() { Organization organization = organizationFeignClient.getOrganization("目标组织ID"); // 执行后续业务逻辑 } }
内容的提问来源于stack exchange,提问作者alpha
相关产品推荐
相关产品推荐

