springsecurity调用不同微服务报401的问题

场景

在开发中本模块需要调用用户的 feign接口来获取用户信息,但是出现了401,
原因是在访问其它模块微服务时没有携带token令牌.

解决方法

在调用服务中新建FeignConfig类并实现RequestInterceptor接口,重写apply方法。

public class FeignConfig implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate requestTemplate) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            //添加token
            requestTemplate.header("Token", request.getHeader("Token"));
        }
    }

在主启动类监听所有请求,防止RequestContextHolder.getRequestAttributes()报空指针

/**
     * 监听器:监听HTTP请求事件
     * 解决RequestContextHolder.getRequestAttributes()空指针问题
     * @return
     */
    @Bean
    public RequestContextListener requestContextListener(){
        return new RequestContextListener();
    }

调用

@FeignClient(name = "被调用服务名",configuration = FeignConfig.class)
    public interface UaaClient {
        @RequestMapping(value = "被调用服务中方法名")
        String test();
    }

注意事项

RequestContextHolder.getRequestAttributes()的使用

在Spring Boot中,如果我们要获取当前Request实例,可以通过以下这个方法获取。

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

使用这种方法获取的时候需要注意使用多线程会出现些状况,例如一个请求过来后,请求达到Service方法,然后Service方法里另起一个线程启动,在该线程run方法里面想要通过以上方法可能获取不到Request实例。

且看RequestContextHolder内部分源码:


    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<>("Request attributes");

    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<>("Request context");

.....

    /**
     * Bind the given RequestAttributes to the current thread.
     * @param attributes the RequestAttributes to expose,
     * or {@code null} to reset the thread-bound context
     * @param inheritable whether to expose the RequestAttributes as inheritable
     * for child threads (using an {@link InheritableThreadLocal})
     */
    public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
        if (attributes == null) {
            resetRequestAttributes();
        }
        else {
            if (inheritable) {
                inheritableRequestAttributesHolder.set(attributes);
                requestAttributesHolder.remove();
            }
            else {
                requestAttributesHolder.set(attributes);
                inheritableRequestAttributesHolder.remove();
            }
        }
    }

    /**
     * Return the RequestAttributes currently bound to the thread.
     * @return the RequestAttributes currently bound to the thread,
     * or {@code null} if none bound
     */
    @Nullable
    public static RequestAttributes getRequestAttributes() {
              
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }

可看到之所以能通过静态方法getRequestAttributes获取Request实例,是因为ThreadLocal获取。一个请求到达容器后,Spring会把该请求Request实例通过setRequestAttributes方法 把Request实例放入该请求线程内ThreadLocalMap中,然后就可以通过静态方法取到。原理就是ThreadLocal,但ThreadLocal不能让子线程继承ThreadLocalMap信息,可以使用InherbritableThreadLocal实现子线程信息传递。
但Spring Boot 默认使用ThreadLocal把Request设置进请求线程中,这样如果在请求方法里面另起一个子线程然后再通过getRequestAttributes方法获取,是获取不到的。
所以要在能让子线程获取到,就可以使用InherbritableThreadLocal,看setRequestAttributes方法有这个布尔值可以设,至于在哪里设就没去深究。但个人认为最好不要修改该布尔值,默认就行,否则会有意向不到的可能

使用注意事项:
1.不要在请求方法里另起一个子线程调用该方法;

2.在请求周期中,尽可能不要传递Request实例给多线程使用,因为子线程可能在Request生命周期结束销毁后再使用Request时获取不了参数,否则必须同步线程 让其在生命周期结束前调用;


版权声明:本文为weixin_44602460原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。