Microservice Token Authentication and User Information Propagation Scheme

Design Approach

  1. Upon successful login, generate a token using the userId and have the frontend store it.
  2. When subsequent requests reach the gateway, create a filter to parse userId from the token and inject it into the request headers.
  3. Once the request arrives at the target service, create an interceptor to extract userId from the headers, fetch the corresponding UserPO, and store it in ThreadLocal<UserPO>.

Example Implementation

Gateway Filter for Token Validation and Header Injection

The following global filter performs authentication at the gateway level, extracts the user identifier from a JWT token, and propagates it via request headers:

import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang.StringUtils;
import my.util.JwtHelper;

@Component
public class AuthTokenFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest req = exchange.getRequest();
        ServerHttpResponse resp = exchange.getResponse();

        // Skip authentication for login endpoint
        if (req.getURI().getPath().contains("/login")) {
            return chain.filter(exchange);
        }

        String accessToken = req.getHeaders().getFirst("token");
        if (StringUtils.isBlank(accessToken)) {
            resp.setStatusCode(HttpStatus.UNAUTHORIZED);
            return resp.setComplete();
        }

        try {
            Claims payload = JwtHelper.parseToken(accessToken);
            int status = JwtHelper.checkExpiration(payload);
            if (status == 1 || status == 2) { // expired or invalid
                resp.setStatusCode(HttpStatus.UNAUTHORIZED);
                return resp.setComplete();
            }

            Object uidObj = payload.get("id");
            ServerHttpRequest modifiedReq = req.mutate()
                    .header("userId", String.valueOf(uidObj))
                    .build();
            exchange = exchange.mutate().request(modifiedReq).build();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Service Interceptor to Extract User ID and Populate ThreadLocal

This interceptor runs inside each microservice to retrieve userId from headers, load the user entity, and cache it in a thread-local holder:

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import my.model.UserAccount;
import my.context.UserContextHolder;

public class UserAuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uidStr = request.getHeader("userId");
        if (uidStr != null) {
            UserAccount account = new UserAccount();
            account.setId(Integer.parseInt(uidStr));
            UserContextHolder.setCurrentUser(account);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserContextHolder.clear();
    }
}

ThreadLocal Utility for Accessing User Data

A simple utility class ensures safe storage and retrieval of the user object during the lifecycle of a request:

import my.model.UserAccount;

public final class UserContextHolder {
    private static final ThreadLocal<UserAccount> USER_HOLDER = new ThreadLocal<>();

    public static UserAccount getCurrentUser() {
        return USER_HOLDER.get();
    }

    public static void setCurrentUser(UserAccount user) {
        USER_HOLDER.set(user);
    }

    public static void clear() {
        USER_HOLDER.remove();
    }
}

Tags: microservices Authentication JWT gateway filter interceptor

Posted on Thu, 07 May 2026 05:44:52 +0000 by edwinlcy