Spring Cloud之Feign 转发请求头(header参数)
1. 自定义feign隔离策略
package com.cloud.config; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import com.netflix.hystrix.strategy.properties.HystrixProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 自定义Feign的隔离策略: * 在转发Feign的请求头的时候, 如果开启了Hystrix, * Hystrix的默认隔离策略是Thread(线程隔离策略), 因此转发拦截器内是无法获取到请求的请求头信息的, * 可以修改默认隔离策略为信号量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE, * 这样的话转发线程和请求线程实际上是一个线程, 这并不是最好的解决方法, 信号量模式也不是官方最为推荐的隔离策略; * 另一个解决方法就是自定义Hystrix的隔离策略: * 思路是将现有的并发策略作为新并发策略的成员变量,在新并发策略中, * 返回现有并发策略的线程池、Queue;将策略加到Spring容器即可; */ @Component public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy { Logger log = LoggerFactory.getLogger(this.getClass()); private HystrixConcurrencyStrategy delegate; public FeignHystrixConcurrencyStrategyIntellif() { try { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) { // Welcome to singleton hell... return; } HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook(); HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher(); HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy); HystrixPlugins.reset(); HystrixPlugins instance = HystrixPlugins.getInstance(); instance.registerConcurrencyStrategy(this); instance.registerCommandExecutionHook(commandExecutionHook); instance.registerEventNotifier(eventNotifier); instance.registerMetricsPublisher(metricsPublisher); instance.registerPropertiesStrategy(propertiesStrategy); } catch (Exception e) { log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e); } } private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier, HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) { if (log.isDebugEnabled()) { log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]"); log.debug("Registering Sleuth Hystrix Concurrency Strategy."); } } @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); return new WrappedCallable<>(callable, requestAttributes); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties); } @Override public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) { return this.delegate.getBlockingQueue(maxQueueSize); } @Override public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) { return this.delegate.getRequestVariable(rv); } static class WrappedCallable<T> implements Callable<T> { private final Callable<T> target; private final RequestAttributes requestAttributes; public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) { this.target = target; this.requestAttributes = requestAttributes; } @Override public T call() throws Exception { try { RequestContextHolder.setRequestAttributes(requestAttributes); return target.call(); } finally { RequestContextHolder.resetRequestAttributes(); } } } }
2.设置feign拦截器,进行相应header的转发
2.1 示例一
package com.cloud.interceptor; import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import static com.cloud.config.Constants.CLOUD_SESSION_ID; /** * feign转发header参数*/ @Configuration public class FeignRequestInterceptor implements RequestInterceptor { @Autowired HttpServletRequest httpServletRequest; public FeignRequestInterceptor() { } public void apply(RequestTemplate requestTemplate) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId(); String sessionId2 = httpServletRequest.getSession().getId(); String appkey0 = httpServletRequest.getHeader("appKey"); String appkey = request.getHeader("appKey"); // String traceId = request.getHeader("traceId"); // String requestIp = MyRequestUtil.getIpAddress(request); // String userAgent = request.getHeader("User-agent"); // String contentType = request.getHeader("Content-Type"); // // requestTemplate.header("Content-Type", new String[]{contentType}); // requestTemplate.header("User-agent", new String[]{userAgent}); // requestTemplate.header("User-IP", new String[]{requestIp}); // requestTemplate.header("traceId", new String[]{traceId}); // requestTemplate.header("appKey", new String[]{appkey}); requestTemplate.header(CLOUD_SESSION_ID, sessionId); } }
2.2 示例一
转发所有header
package com.cloud.interceptor; import feign.RequestInterceptor; import feign.RequestTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; /** * 转发所有header*/ // @Configuration // 注释掉, 不启用该拦截器; 如果启用, 直接打开注释即可 public class FeignRequestInterceptor3 implements RequestInterceptor { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String values = request.getHeader(name); template.header(name, values); } logger.info("feign interceptor header:{}",template); } // 转发参数 Enumeration<String> bodyNames = request.getParameterNames(); StringBuilder body = new StringBuilder(); if (bodyNames != null) { while (bodyNames.hasMoreElements()) { String name = bodyNames.nextElement(); String values = request.getParameter(name); body.append(name).append("=").append(values).append("&"); } } if (body.length() != 0) { body.deleteCharAt(body.length() - 1); template.body(body.toString()); //logger.info("feign interceptor body:{}",body.toString()); } } }
2.3 示例一
客户端带Token 情况
- 如下图客户端携带token访问A服务。
- A服务通过FeginClient 调用B服务获取相关依赖数据。
- 所以只要带token 访问A 无论后边链路有多长 ABCD 都可以获取当前用户信息
- 权限需要有这些整个链路接口的全部权限才能成功
核心代码
fein 拦截器将本服务的token 通过copyToken的形式传递给下游服务
public class PigFeignClientInterceptor extends OAuth2FeignRequestInterceptor { @Override public void apply(RequestTemplate template) { Collection<String> fromHeader = template.headers().get(SecurityConstants.FROM); if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) { return; } accessTokenContextRelay.copyToken(); if (oAuth2ClientContext != null && oAuth2ClientContext.getAccessToken() != null) { super.apply(template); } } }
无token请求,服务内部发起情况处理。
[warning] 很多情况下,比如定时任务。A服务并没有token 去请求B服务,对这种情况进行了兼容。类似于A对外暴露API,但是又安全限制。
- FeignClient 需要带一个请求token,FROM_IN 声明是内部调用
remoteLogService.saveLog(sysLog, SecurityConstants.FROM_IN);
- 目标接口对内外调用进行限制
@Inner 注解,这样就避免接口对外暴露的安全问题。只能通过内部调用才能使用,浏览器不能直接访问该接口
@Inner @PostMapping public R save(@Valid @RequestBody SysLog sysLog) { return new R<>(sysLogService.save(sysLog)); }
- 若代码无法加
@Inner
也可以参考API 对外暴露章节对外暴露目标接口
ignore:
urls:
- 目标借口的Ant表达式即可
愿你眼中有光芒,活成你想要的模样