Spring Boot Feign service calls with token problems

  • 2021-11-13 07:46:22
  • OfStack

Directory Feign Service Transfer Data with token Verification Solution Summary 1 Feign Call for token Authentication 1 Project Scenario 2 Solution 3 Specific Implementation

Feign Service Tuning Service Delivery Data with token Authentication

It is worth reminding that when Feign service transfers data, for example, a certain user service needs token authentication, and when calling that user service, an error is reported, prompting that token is empty, because Feign does not bring token when requesting Feign

Solution

To solve this problem, you can guess that the most convenient thing is to add token to the request head and bring it with you

Feign provides 1 interface, RequestInterceptor

As long as this interface is implemented, simply do some processing. For example, if we verify that the token of the request header is called Access-Token, we will first take out the token of the current request and put it on the feign request header


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 Configure 
 *  Use FeignClient Invoke and transfer between services headers Information 
 */
@Configuration
public class FeignConfig implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // Add token
        requestTemplate.header("Access-Token", request.getHeader("Access-Token"));
    }
}

In this way, Token has been successfully added to the Feign request header. In order to facilitate local debugging, a filter can be added to Spring and Boot, and one filter can be added every time there is no Token in local call, as long as the Filter interface provided by Spring is realized


import org.apache.commons.lang3.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
/**
 *  Every request filter intercepts plus Token
 */
public class AddTokenFilter implements Filter {
    /**
     * superAdmin
     */
    private static final String DEFAULT_TOKEN = " Yours 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 {
     // Judgment is the development mode ( dev ) or production environment ( pro ) 
     // If it is not a development environment, do not do any operation, it is a development environment, and test it locally request Add request header 
        if (profilesActive == null || !EnumEnvType.DEV.toString().equalsIgnoreCase(profilesActive)) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        filterChain.doFilter(new CustomeizedRequest((HttpServletRequest) servletRequest), servletResponse);
    }
    @Override
    public void destroy() {
    }
// Inheritance HttpServletRequestWrapper  , rewrite getHeader Gets the value of the request header 
    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;
        }
    }
}

Using this Filter is very simple. Create a new WebMvcConfig class and configure an 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  Configure 
 * @author chenbin on 2017/12/25
 * @version 3.0.0
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    private final Logger logger = LoggerFactory.getLogger(WebMvcConfig.class);
    /**
     *  Currently active profile 
     */
    @Value("${spring.profiles.active}")
    private String env;
    /**
     *  Solve the problem of path resource mapping 
     *
     * @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/");
    }
    /**
     *  Use fastJson Substitute Jackjson Analyse JSON Data 
     *
     * @return
     */
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        /*
         *  Convert to JSON String, default :
         * WriteNullListAsEmpty    List If the field is null, Output is [], Instead of null
         * WriteNullStringAsEmpty   Character type field if null, The output is "" , Instead of null
         * WriteMapNullValue        Whether the output value is null Fields of , Default to 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);
    }
    /**
     *  This Filter  Solve the problem of cross-domain page access 
     */
    @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;
    }
    /**
     *  This Filter  Add 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;
    }
}

Summary 1

In this way, the token of adding local test in the development environment is realized. If it is not the development environment, the token of web page request is very convenient, and the problem of Feign missing the request header is also solved

Feign call for token authentication

1. Project scenario

Here, we use two springboot applications, which are called remotely through feign (yes, the architecture is so wonderful). Then, when calling feign, I hope token authentication can be performed.

2. Solutions

When the request comes in, check token of header by interceptor, and then intercept feign request by adding a new feign interceptor when calling feignClient in service, and add token in current header to the request header of feign. Realize the transmission of token in the link.

3. Concrete implementation

Added feign Interceptor Configuration


/**
 * Feign Request interceptor configuration .
 *
 * @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();
        // Settings token To request header 
        template.header(ConstantCommon.HEADER_TOKEN_KEY, request.getHeader(ConstantCommon.HEADER_TOKEN_KEY));
    }
}

Then in the feignClient interface, add = = configuration = FeignInterceptorConfig. class = =

Pay attention to Bug! ! !

Attention! ! ! In this case, there will be an exception, and the request obtained will be null. The reason is that the hytrix isolation policy is thread and the threadLocal variable cannot be read.

Solution! ! Change policy

Add the following configuration in the configuration file to solve it!


#  Replacement hystrix Policy, resolution cannot be delivered threadLocal Variable problem 
hystrix:
    command:
        default:
            execution:
                isolation:
                    strategy: SEMAPHORE

Related articles: