赞
踩
在微服务环境中,完成一个http请求,经常需要调用其他好几个服务才可以完成其功能,这种情况非常普遍,无法避免。那么就需要服务之间的通过feignClient发起请求,获取需要的 资源。
一般而言,微服务项目部署环境中,各个微服务都是运行在内网环境,网关服务负责请求的路由,对外通过nginx暴露给请求者。
这种情况下,似乎网关这里做一个认证,就可以确保请求者是合法的,至于微服务调用微服务,反正都是自己人,而且是内网,无所谓是否验证身份了。
我有一个朋友,他们公司的项目确实就是这样做的,正经的商业项目。
讲道理,只要框架提供了这样的功能,那么就有存在的意义,但是,如果涉及权限的校验,微服务之间的feign调用就需要知道身份了,即需要做鉴权。
无论是JWT、还是OAUTH2、还是shiro,大家比较公认的认证、鉴权方案,就是在请求头中放一堆东西,然后服务提供者通过解析这些东西完成认证和鉴权,这些东西俗称token。
在feign调用中需要解决的就是token传递的问题,只有请求发起者将正确的token传递给服务提供者,服务提供者才能完成认证&鉴权,进而返回需要的资源。
在feign调用中可能会遇到如下问题:
编写一个拦截器,在feign请求前,将http请求携带的token传递给restTemplate。
具体实现方式为:
创建一个Component实现com.nghsmart.ar.context.RequestAttributeContext中的RequestInterceptor接口
重写apply方法
通过RequestContextHolder对象获取到RequestAttributes
通过RequestAttributes对象获取到HttpServletRequest
通过HttpServletRequest对象获取到请求头
在请求头中把token拿出来
将token塞进restTemplate创建的http请求头中
示例代码:
BizFeignRequestInterceptor
-
-
- import com.nghsmart.ar.context.RequestAttributeContext;
- import com.nghsmart.common.core.utils.ServletUtils;
- import com.nghsmart.common.core.utils.StringUtils;
- import com.nghsmart.common.core.utils.ip.IpUtils;
- import com.nghsmart.common.security.constant.FeignRequestHeader;
- import feign.RequestInterceptor;
- import feign.RequestTemplate;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
- import org.springframework.web.context.request.AbstractRequestAttributes;
- import org.springframework.web.context.request.FacesRequestAttributes;
- import org.springframework.web.context.request.RequestAttributes;
- import org.springframework.web.context.request.RequestContextHolder;
- import javax.servlet.http.HttpServletRequest;
- import java.util.Map;
-
-
-
- @Slf4j
- @Order(1)
- @Component
- public class BizFeignRequestInterceptor implements RequestInterceptor {
- @Override
- public void apply(RequestTemplate requestTemplate) {
- RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
- if (null! = attributes) {
- ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
- String token = servletRequestAttributes.getRequest().getHeader("token");
- requestTemplate.header("token",token);
- }
- }
- }
-
-
-
上述添加BizFeignRequestInterceptor只能解决同步调用环境下的token传递问题,当是异步线程环境下就GG了。
通过在主线程中主动将RequestAttribute传递到子线程中可以解决一部分异步线程中token传递的问题,示例代码如下:
-
- RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
-
-
但是这种方式有弊端,当主线程先于子线程结束的时候,子线程将获取不到RequestAttribute,原因是Tomcat会在http请求结束的时候清空数据。
我们可以创建一个InheritableThreadLocal用来保存RequestAttribute,这样就可以完美解决问题了。
实现思路为:
创建一个 RequestAttributeContext,其中维护一个InheritableThreadLocal对象,用来存RequestAttributes
创建一个RequestAttributeInterceptor,实现HandlerInterceptor, WebMvcConfigurer接口,用来在请求开始前把 RequestAttributes 存放到 RequestAttributeContext 中
修改 BizFeignRequestInterceptor ,当无法获取到 RequestAttributes 的时候,就从 RequestAttributeContext 中获取
透传逻辑不变
相关示例代码如下:
-
-
- import lombok.Data;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.web.context.request.RequestAttributes;
-
-
-
- @Slf4j
- public class RequestAttributeContext {
-
- private static final ThreadLocal<RequestAttributes> context = new InheritableThreadLocal<>();
-
- public static void setAttribute(RequestAttributes attributes) {
- if (null == attributes) {
- log.debug("RequestAttributes is null");
- }
- context.set(attributes);
- }
-
- public static RequestAttributes getAttribute() {
- return context.get();
- }
-
- public static void removeAttribute() {
- context.remove();
- }
-
- }
-
-
- import com.alibaba.fastjson.JSON;
- import com.nghsmart.ar.context.RequestAttributeContext;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.context.request.RequestAttributes;
- import org.springframework.web.context.request.RequestContextHolder;
- import org.springframework.web.servlet.HandlerInterceptor;
- import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
-
-
- @Slf4j
- @Configuration
- public class RequestAttributeInterceptor implements HandlerInterceptor, WebMvcConfigurer {
-
- /**
- * 重写 WebMvcConfigurer 的 addInterceptors,将 RequestAttributeInterceptor 添加到拦截器列表
- *
- * @param registry
- */
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(this).addPathPatterns("/**").excludePathPatterns("/swagger-resources/**", "/v2/api-docs/**");
- }
- /**
- * 重写 HandlerInterceptor 的 preHandle,在请求开始处理前,将 RequestAttribute 存入 RequestAttributeContext
- *
- * @param request current HTTP request
- * @param response current HTTP response
- * @param handler chosen handler to execute, for type and/or instance evaluation
- * @return
- * @throws Exception
- */
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
- RequestAttributeContext.setAttribute(requestAttributes);
- return true;
- }
-
- }
-
-
-
- import com.nghsmart.ar.context.RequestAttributeContext;
- import com.nghsmart.common.core.utils.ServletUtils;
- import com.nghsmart.common.core.utils.StringUtils;
- import com.nghsmart.common.core.utils.ip.IpUtils;
- import com.nghsmart.common.security.constant.FeignRequestHeader;
- import feign.RequestInterceptor;
- import feign.RequestTemplate;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
- import org.springframework.web.context.request.AbstractRequestAttributes;
- import org.springframework.web.context.request.FacesRequestAttributes;
- import org.springframework.web.context.request.RequestAttributes;
- import org.springframework.web.context.request.RequestContextHolder;
- import javax.servlet.http.HttpServletRequest;
- import java.util.Map;
-
-
- @Slf4j
- @Order(1)
- @Component
- public class BizFeignRequestInterceptor implements RequestInterceptor {
-
- @Override
- public void apply(RequestTemplate requestTemplate) {
- RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
- if (null! = attributes) {
- ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
- String token = servletRequestAttributes.getRequest().getHeader("token");
- requestTemplate.header("token",token);
- }else {
- RequestAttributes requestAttributes = RequestAttributeContext.getAttribute();
- if (null != requestAttributes) {
- RequestContextHolder.setRequestAttributes(requestAttributes);
- } else {
- log.debug("requestAttributes is null");
- }
- ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
- String token = servletRequestAttributes.getRequest().getHeader("token");
- requestTemplate.header("token",token);
- }
- }
- }
-
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。