diff --git a/mall-ebtp-cloud-feign-starter/pom.xml b/mall-ebtp-cloud-feign-starter/pom.xml index 7623cfd..7bb8c1e 100644 --- a/mall-ebtp-cloud-feign-starter/pom.xml +++ b/mall-ebtp-cloud-feign-starter/pom.xml @@ -8,7 +8,7 @@ com.chinaunicom.ebtp mall-ebtp-cloud-parent 0.0.1 - ../mall-ebtp-cloud-parent + com.chinaunicom.ebtp mall-ebtp-cloud-feign-starter @@ -16,14 +16,18 @@ mall-ebtp-cloud-feign-starter - - org.springframework.cloud - spring-cloud-starter-openfeign - - - io.github.openfeign - feign-okhttp - + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.cloud + spring-cloud-netflix-hystrix + + + io.github.openfeign + feign-okhttp + - + diff --git a/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/FeignStarterConfiguration.java b/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/FeignStarterConfiguration.java index 89a34bc..39d56f9 100644 --- a/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/FeignStarterConfiguration.java +++ b/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/FeignStarterConfiguration.java @@ -1,10 +1,12 @@ package com.chinaunicom.mall.ebtp.cloud.feign.starter; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration @PropertySource("classpath:feign-configuration.properties") +@ComponentScan(basePackages = "com.chinaunicom.mall.ebtp.cloud.feign.starter") public class FeignStarterConfiguration { } diff --git a/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/HystrixSecurityAutoConfiguration.java b/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/HystrixSecurityAutoConfiguration.java new file mode 100644 index 0000000..44e4dc5 --- /dev/null +++ b/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/HystrixSecurityAutoConfiguration.java @@ -0,0 +1,57 @@ +package com.chinaunicom.mall.ebtp.cloud.feign.starter; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.AllNestedConditions; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.netflix.hystrix.security.SecurityContextConcurrencyStrategy; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import com.chinaunicom.mall.ebtp.cloud.feign.starter.HystrixSecurityAutoConfiguration.HystrixSecurityCondition; +import com.netflix.hystrix.Hystrix; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +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; + +@Configuration +@Conditional(HystrixSecurityCondition.class) +@ConditionalOnClass({ Hystrix.class }) +public class HystrixSecurityAutoConfiguration { + + private @Autowired HystrixConcurrencyStrategy requestAttributeHystrixConcurrencyStrategy; + + @PostConstruct + public void init() { + HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); + HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher(); + HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); + HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook(); + + HystrixPlugins.reset(); + + HystrixPlugins.getInstance().registerConcurrencyStrategy( + new SecurityContextConcurrencyStrategy(requestAttributeHystrixConcurrencyStrategy)); + HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); + HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); + HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); + HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); + } + + static class HystrixSecurityCondition extends AllNestedConditions { + + public HystrixSecurityCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(name = "hystrix.shareSecurityContext") + static class ShareSecurityContext { + + } + } +} \ No newline at end of file diff --git a/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/RequestAttributeHystrixConcurrencyStrategy.java b/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/RequestAttributeHystrixConcurrencyStrategy.java new file mode 100644 index 0000000..03e453e --- /dev/null +++ b/mall-ebtp-cloud-feign-starter/src/main/java/com/chinaunicom/mall/ebtp/cloud/feign/starter/RequestAttributeHystrixConcurrencyStrategy.java @@ -0,0 +1,163 @@ +package com.chinaunicom.mall.ebtp.cloud.feign.starter; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +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 lombok.extern.slf4j.Slf4j; + +/** + * 为了解决熔断器开启后, RequestInterceptor无法注入header的问题 ( Hystrix有隔离策略:THREAD以 及 + * SEMAPHORE, thread策略会导致SecurityContext及RequestContext无法获取当前上下文, 因其实线程隔离的 ) + * + * @author Ajaxfan + */ +@Slf4j +@Component("requestAttributeHystrixConcurrencyStrategy") +public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { + + private HystrixConcurrencyStrategy delegate; + + public RequestAttributeHystrixConcurrencyStrategy() { + init(); + } + + /** + * @param + * @param callable + * @return + */ + @Override + public Callable wrapCallable(Callable callable) { + return new WrappedCallable<>(callable, RequestContextHolder.getRequestAttributes()); + } + + /** + * @param threadPoolKey + * @param corePoolSize + * @param maximumPoolSize + * @param keepAliveTime + * @param unit + * @param workQueue + * @return + */ + @Override + public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty corePoolSize, + HystrixProperty maximumPoolSize, HystrixProperty keepAliveTime, TimeUnit unit, + BlockingQueue workQueue) { + return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, + workQueue); + } + + /** + * @param threadPoolKey + * @param threadPoolProperties + * @return + */ + @Override + public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, + HystrixThreadPoolProperties threadPoolProperties) { + return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties); + } + + /** + * @param maxQueueSize + * @return + */ + @Override + public BlockingQueue getBlockingQueue(int maxQueueSize) { + return this.delegate.getBlockingQueue(maxQueueSize); + } + + /** + * @param + * @param rv + * @return + */ + @Override + public HystrixRequestVariable getRequestVariable(HystrixRequestVariableLifecycle rv) { + return this.delegate.getRequestVariable(rv); + } + + /** + * @author Ajaxfan + * + * @param + */ + static class WrappedCallable implements Callable { + private final Callable target; + private final RequestAttributes requestAttributes; + + public WrappedCallable(Callable 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(); + } + } + } + + /** + * 初始化隔离器 + */ + private void init() { + try { + this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); + if (this.delegate instanceof RequestAttributeHystrixConcurrencyStrategy) { + 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.getInstance().registerConcurrencyStrategy(this); + HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); + HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); + HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); + HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); + } catch (Exception e) { + log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e); + } + } + + /** + * 生产当前隔离参数日志 + * + * @param eventNotifier + * @param metricsPublisher + * @param propertiesStrategy + */ + private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier, + HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) { + log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + this.delegate + "]," + + "eventNotifier [" + eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "]," + + "propertiesStrategy [" + propertiesStrategy + "]," + "]"); + log.debug("Registering Sleuth Hystrix Concurrency Strategy."); + } +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/config/FeignConfig.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/config/FeignConfig.java index d2be1a7..ddef51d 100644 --- a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/config/FeignConfig.java +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/config/FeignConfig.java @@ -1,9 +1,13 @@ package com.chinaunicom.mall.ebtp.common.config; +import static com.chinaunicom.mall.ebtp.cloud.security.starter.common.Constants.AUTHORIZATION_HEADER; +import static com.chinaunicom.mall.ebtp.cloud.security.starter.common.Constants.TOKEN_PREFIX; + +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import feign.RequestInterceptor; import feign.RequestTemplate; @@ -14,23 +18,19 @@ import feign.RequestTemplate; @Configuration public class FeignConfig implements RequestInterceptor { - private final String AUTHORIZATION_HEADER = "Authorization"; - private final String BEARER_TOKEN_TYPE = "Bearer"; - @Override public void apply(RequestTemplate template) { -// ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); -// if (null != attributes) { -// HttpServletRequest request = attributes.getRequest(); -// String token = request.getHeader("JwtToken"); -// template.header("JwtToken", token); -// } - SecurityContext securityContext = SecurityContextHolder.getContext(); - Authentication authentication = securityContext.getAuthentication(); - - if (authentication != null && authentication.getCredentials() instanceof String) { - String token = (String) authentication.getCredentials(); - template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, token)); + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + + if (null != attributes) { + final String header = attributes.getRequest().getHeader(AUTHORIZATION_HEADER);// 提取request头信息 + + // 检查请求头是否包含 Bearer 前缀 + if (StringUtils.startsWith(header, TOKEN_PREFIX)) { + String authToken = RegExUtils.replaceAll(header, TOKEN_PREFIX, "");// 提取 token 信息 + + template.header(AUTHORIZATION_HEADER, String.format("%s %s", TOKEN_PREFIX, authToken)); + } } }