Design Approach
- Upon successful login, generate a token using the
userIdand have the frontend store it. - When subsequent requests reach the gateway, create a filter to parse
userIdfrom the token and inject it into the request headers. - Once the request arrives at the target service, create an interceptor to extract
userIdfrom the headers, fetch the correspondingUserPO, and store it inThreadLocal<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();
}
}