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

如何将请求中当前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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:45:54