启用Circuit Breaker时如何获取并设置调用的Authorization Token?
Resilience4j 熔断器下Spring Security上下文传递解决方案
核心问题原因
Resilience4j的熔断器/线程池隔离默认运行在独立子线程中,不会自动继承主线程的Spring Security上下文,导致子线程发起请求时无法获取Authorization Token。以下是两种可行的解决方案:
方案一:自定义ThreadLocal传递Token
通过手动在主线程存储Token,再在子线程中恢复,确保请求头携带认证信息。
- 自定义上下文Holder存储Token
public class SecurityTokenHolder { private static final ThreadLocal<String> AUTH_TOKEN = new ThreadLocal<>(); public static void setToken(String token) { AUTH_TOKEN.set(token); } public static String getToken() { return AUTH_TOKEN.get(); } public static void clear() { AUTH_TOKEN.remove(); } }
- 请求拦截器在主线程存入Token
@Component public class AuthTokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (token != null) { SecurityTokenHolder.setToken(token); } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { SecurityTokenHolder.clear(); } }
- 配置Resilience4j线程池传递上下文
通过装饰器将主线程的Token传递到子线程,执行完成后清理避免内存泄漏:
@Configuration public class Resilience4jThreadPoolConfig { @Bean public ThreadPoolBulkhead myThreadPoolBulkhead() { ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom() .coreThreadPoolSize(10) .maxThreadPoolSize(20) .queueCapacity(50) .build(); return ThreadPoolBulkhead.of("myServiceBulkhead", config, new TokenPropagatorDecorator()); } static class TokenPropagatorDecorator implements ThreadPoolBulkhead.DelegatingDecorator { @Override public Runnable decorateRunnable(Runnable runnable) { String token = SecurityTokenHolder.getToken(); return () -> { try { SecurityTokenHolder.setToken(token); runnable.run(); } finally { SecurityTokenHolder.clear(); } }; } @Override public <T> Callable<T> decorateCallable(Callable<T> callable) { String token = SecurityTokenHolder.getToken(); return () -> { try { SecurityTokenHolder.setToken(token); return callable.call(); } finally { SecurityTokenHolder.clear(); } }; } } }
- Feign客户端拦截器设置请求头
@Configuration public class FeignAuthConfig { @Bean public RequestInterceptor feignTokenInterceptor() { return template -> { String token = SecurityTokenHolder.getToken(); if (token != null) { template.header("Authorization", token); } }; } }
方案二:使用Resilience4j上下文传播扩展(Spring Boot 2.4+)
Resilience4j原生支持上下文传播,可直接传递Spring Security的SecurityContext。
- 实现自定义上下文传播器
@Component public class SecurityContextPropagator implements ContextPropagator<SecurityContext> { @Override public Supplier<Optional<SecurityContext>> retrieve() { return () -> Optional.ofNullable(org.springframework.security.core.context.SecurityContextHolder.getContext()); } @Override public Consumer<Optional<SecurityContext>> copy() { return context -> context.ifPresent(org.springframework.security.core.context.SecurityContextHolder::setContext); } @Override public Runnable clear() { return org.springframework.security.core.context.SecurityContextHolder::clearContext; } }
- 配置文件开启上下文传播
在application.yml中添加:
resilience4j: circuitbreaker: instances: myCircuitBreaker: context-propagation-enabled: true thread-pool-bulkhead: instances: myBulkhead: context-propagation-enabled: true
- Feign客户端直接读取SecurityContext
@Configuration public class FeignAuthConfig { @Bean public RequestInterceptor feignAuthInterceptor() { return template -> { Authentication auth = org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication(); if (auth != null && auth.getCredentials() instanceof String) { template.header("Authorization", (String) auth.getCredentials()); } }; } }
注意事项
- 若使用信号量隔离(SemaphoreBulkhead),请求在主线程执行,无需额外配置即可直接获取SecurityContext;
- 线程池隔离场景下,必须确保上下文在任务执行后清理,避免线程复用导致的上下文污染。
内容的提问来源于stack exchange,提问作者Bruno Freitas
相关产品推荐
相关产品推荐

