Spring Boot Feign服务调用之间带token问题

Feign服务调服务传递数据带token验证

Feign服务调服务就不多做介绍了,值得提醒的是,Feign服务调服务传递数据的时候,比如某用户服务是需要带token验证的,而调用那个用户服务的时候报错,提示token为空,是因为Feign请求的时候没有带上token

解决方式

要解决这个问题,想必能猜到最方便的就是往请求头里加上token,一起带过去

Feign有提供一个接口,RequestInterceptor

只要实现这个接口,简单做一些处理,比如说我们验证请求头的token叫Access-Token,我们就先取出当前请求的token,然后放到feign请求头上

import feign.RequestInterceptor;
import feign.RequestTemplate;
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;
/**
 * Feign配置
 * 使用FeignClient进行服务间调用,传递headers信息
 */
@Configuration
public class FeignConfig implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //添加token
        requestTemplate.header("Access-Token", request.getHeader("Access-Token"));
    }
}

这样已经成功往Feign请求头里加上了Token,还可以这样,为了方便本地调试,可以在Spring Boot加上过滤器,每次本地调用没有Token的时候加上一个,只要实现Spring提供的Filter接口

import org.apache.commons.lang3.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
/**
 * 每次请求过滤器拦截加Token
 */
public class AddTokenFilter implements Filter {
    /**
     * superAdmin
     */
    private static final String DEFAULT_TOKEN = "你的token";
    private String profilesActive;
    public AddTokenFilter(String profilesActive) {
        super();
        this.profilesActive = profilesActive;
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
     //判断是开发模式(dev)还是生产坏境(pro)
     //如果不是开发坏境,不做任何操作,是开发坏境,往本地测试的request加请求头
        if (profilesActive == null || !EnumEnvType.DEV.toString().equalsIgnoreCase(profilesActive)) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        filterChain.doFilter(new CustomeizedRequest((HttpServletRequest) servletRequest), servletResponse);
    }
    @Override
    public void destroy() {
    }
//继承HttpServletRequestWrapper ,重写getHeader获取请求头的值
    private class CustomeizedRequest extends HttpServletRequestWrapper {
        /**
         * Constructs a request object wrapping the given request.
         *
         * @param request
         * @throws IllegalArgumentException if the request is null
         */
        public CustomeizedRequest(HttpServletRequest request) {
            super(request);
        }
        @Override
        public String getHeader(String name) {
            if (!Constant.HTTP_HEADER_ACCESS_TOKEN.equalsIgnoreCase(name)) {
                return super.getHeader(name);
            }
            String token = super.getHeader(name);
            return StringUtils.isNotBlank(token) ? token : DEFAULT_TOKEN;
        }
    }
}

使用这个Filter很简单,新建一个WebMvcConfig类,配置一个bean

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.uhope.rl.watersource.filter.AddTokenFilter;
import com.uhope.rl.watersource.filter.ServiceFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.nio.charset.Charset;
/**
 * Spring MVC 配置
 * @author chenbin on 2017/12/25
 * @version 3.0.0
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    private final Logger logger = LoggerFactory.getLogger(WebMvcConfig.class);
    /**
     * 当前激活的配置文件
     */
    @Value("${spring.profiles.active}")
    private String env;
    /**
     * 解决路径资源映射问题
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
    /**
     * 使用fastJson代替Jackjson解析JSON数据
     *
     * @return
     */
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        /*
         * 转换为JSON字符串,默认:
         * WriteNullListAsEmpty    List字段如果为null,输出为[],而非null
         * WriteNullStringAsEmpty  字符类型字段如果为null,输出为”“,而非null
         * WriteMapNullValue       是否输出值为null的字段,默认为false
         */
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        fastConverter.setDefaultCharset(Charset.forName("UTF-8"));
        HttpMessageConverter converter = fastConverter;
        return new HttpMessageConverters(converter);
    }
    /**
     * 这个Filter 解决页面跨域访问问题
     */
    @Bean
    public FilterRegistrationBean omsFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new ServiceFilter());
        registration.addUrlPatterns("/*");
        registration.setName("MainFilter");
        registration.setAsyncSupported(true);
        registration.setOrder(1);
        return registration;
    }
    /**
     * 这个Filter 添加token
     */
    @Bean
    public FilterRegistrationBean addTokenFilter(){
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new AddTokenFilter(env));
        registration.addUrlPatterns("/*");
        registration.setName("addTokenFilter");
        registration.setAsyncSupported(true);
        registration.setOrder(2);
        return registration;
    }
}

小结一下

这样就实现了开发坏境下添加本地测试的token,若不是开发坏境,用网页请求过来的token,很方便,也解决了Feign丢失请求头的问题

Feign调用进行token鉴权

1、项目场景

这边使用 两个springboot应用,中间通过feign来进行远程调用(是的没错,架构就是这么奇葩)。然后在调用feign的时候,希望可以进行token鉴权。

2、解决办法

请求进来时,通过拦截器,校验header的token,然后在业务中调用feignClient时,通过新加一个feign拦截器,拦截feign请求,把当前的header中的token添加到feign的请求头中去。实现token在链路中的传递。

3、具体实现

新增 feign 拦截器配置

/**
 * Feign请求拦截器配置.
 *
 * @author linzp
 * @version 1.0.0
 * @date 2021/4/16 21:19
 */
@Configuration
public class FeignInterceptorConfig implements RequestInterceptor {
    public FeignInterceptorConfig() {}
    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //设置token到请求头
        template.header(ConstantCommon.HEADER_TOKEN_KEY, request.getHeader(ConstantCommon.HEADER_TOKEN_KEY));
    }
}

然后在feignClient接口中,添加 == configuration = FeignInterceptorConfig.class==

注意有Bug!!!

注意!!!,这里会有个异常,获取到的request会是null。原因是hytrix隔离策略是thread,无法读到 threadLocal变量。

解决办法!!更改策略

在配置文件中新增如下配置,即可解决!

# 更换hystrix策略,解决无法传递threadLocal变量问题
hystrix:
    command:
        default:
            execution:
                isolation:
                    strategy: SEMAPHORE

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

你可能感兴趣的:(Spring Boot Feign服务调用之间带token问题)