In the process of Spring Cloud microservice invocation, you need to deal with token relaying, which is the only way to ensure the delivery of user authentication information in the invocation chain. Today we will share how to implement token relay in Feign.

Token Relay

To be clear, this means that the Token token is passed on between services to ensure that the resource server can properly authenticate the caller.

Couldn’t the token be automatically relayed at Feign?

If we carry a Token to access Service A, Service A can definitely authenticate, but Service A calls Service B through Feign, and then A’s token cannot be passed directly to Service B.

Here to briefly explain the reason, the call between services through the Feign interface to carry out. On the caller side, we usually write a Feign interface like the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@FeignClient(name = "foo-service",fallback = FooClient.Fallback.class)
public interface FooClient {
    @GetMapping("/foo/bar")
    Rest<Map<String, String>> bar();

    @Component
    class Fallback implements FooClient {
        @Override
        public Rest<Map<String, String>> bar() {
            return RestBody.fallback();
        }
    }
}

When we call the Feign interface, a dynamic proxy is used to generate the proxy class for the interface for us to call. If we don’t turn on fault tolerance we can extract the authentication object JwtAuthenticationToken from the Spring Security provided SecurityContext object to the resource server which contains the JWT token and then we can implement Feign’s interceptor interface RequestInterceptor to place the token in the request header, with the following pseudo-code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
 * 需要注入Spring IoC
 **/
static class BearerTokenRequestInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate template) {
            final String authorization = HttpHeaders.AUTHORIZATION;
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

            if (authentication instanceof JwtAuthenticationToken){
                JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
                String tokenValue = jwtAuthenticationToken.getToken().getTokenValue();
                template.header(authorization,"Bearer "+tokenValue);
            }
        }
    }

This wouldn’t be a problem if we didn’t turn on fault tolerance. Fault tolerance is basically turned on to prevent an avalanche of services in the call chain. At this point, we can’t get Authentication from SecurityContextHolder. This is because the Feign call is then made in another sub-thread opened under the caller’s call thread. Since the fault-tolerant component I’m using is Resilience4J , the source code for the corresponding thread is in Resilience4JCircuitBreaker.

1
Supplier<Future<T>> futureSupplier = () -> executorService.submit(toRun::get);

SecurityContextHolder saves information by default through the ThreadLocal implementation, which we all know is not cross-threaded, and Feign’s interceptor happens to be in the child thread at this time, so Feign with fault tolerance (circuitBreaker) turned on can not be directly token relay .

The fault-tolerant components are Hystrix (obsolete), Resilience4J, and Ali’s Sentinel, which may have slightly different mechanisms.

Implementing Token Relay

Although I can’t implement token relay directly, I found some information from it. In the processor FeignCircuitBreakerInvocationHandler of the Feign interface proxy, I found the following code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
private Supplier<Object> asSupplier(final Method method, final Object[] args) {
        final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        return () -> {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                return this.dispatch.get(method).invoke(args);
            }
            catch (RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new RuntimeException(throwable);
            }
            finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }

This is the execution code of the Feign proxy class and we can see that before the execution :

1
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

Here is to get information about the request in the calling thread, including ServletHttpRequest, ServletHttpResponse and other information. Immediately after the lambda code to Setter this information again into.

1
RequestContextHolder.setRequestAttributes(requestAttributes);

If this were an operation performed in one thread, it would be redundant. In fact the Supplier return value is executed in another thread. The purpose of this is to preserve some request metadata across threads.

InheritableThreadLocal

How does the RequestContextHolder pass data across threads?

1
2
3
4
5
6
7
8
9
public abstract class RequestContextHolder  {

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

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

The RequestContextHolder maintains two containers, a ThreadLocal that cannot cross threads and a NamedInheritableThreadLocal that implements the InheritableThreadLocal.

The InheritableThreadLocal is able to pass data from the parent thread to the child thread, based on this principle the RequestContextHolder brings the caller’s request information into the child thread, and with the help of this principle the token relay can be implemented.

Implementing token relay

Changed the initial Feign interceptor code a bit to implement the token relay

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 /**
     * 令牌中继
     */
    static class BearerTokenRequestInterceptor implements RequestInterceptor {
        private static final Pattern BEARER_TOKEN_HEADER_PATTERN = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$",
                Pattern.CASE_INSENSITIVE);

        @Override
        public void apply(RequestTemplate template) {
            final String authorization = HttpHeaders.AUTHORIZATION;
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (Objects.nonNull(requestAttributes)) {
                String authorizationHeader = requestAttributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
                Matcher matcher = BEARER_TOKEN_HEADER_PATTERN.matcher(authorizationHeader);
                if (matcher.matches()) {
                    // 清除token头 避免传染
                    template.header(authorization);
                    template.header(authorization, authorizationHeader);
                }
            }
        }
    }

This way when you call FooClient.bar(), the resource server (OAuth2 Resource Server) in foo-service can also get the caller’s token and thus the user’s information to handle resource permissions and operations.

Don’t forget to inject this interceptor into Spring IoC.

Summary

Microservice token relaying is very important to ensure that user state is passed through the invocation link. And this issue is also one of the hard parts of microservices. Today we have implemented token relaying with the help of Feign and some features of ThreadLocal.

For your reference.

Reference https://felord.cn/feign-token-relay.html