从流程可以看出,浏览在访问服务时会携带jwt向微服务发起请求,中间会先经过网关。网关从jwt中获取用户信息并保存到请求中,然后再转发给对应服务。在到达业务层之前会有拦截器将用户信息存入ThreadLocal中。
1.在经过网关过滤器被拦截时将用户信息保存到请求头中
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取request ServerHttpRequest request = exchange.getRequest(); // 判断是否需要拦截 if(isExclude(request.getPath().toString())){ // 无需拦截 放行 return chain.filter(exchange); } // 获取请求头中的token String token = null; List<String> headers = request.getHeaders().get("authorization"); if(CollUtils.isNotEmpty(headers)){ token = headers.get(0); } //校验并解析token Long userId = null; try { userId = jwtTool.parseToken(token); } catch (UnauthorizedException e) { //无效 拦截 ServerHttpResponse response = exchange.getResponse(); response.setRawStatusCode(401); return response.setComplete(); } // 如果有效,传递用户信息 String userInfo = userId.toString(); ServerWebExchange ex = exchange.mutate() .request(b -> b.header("user-info", userInfo)) .build(); // 放行 return chain.filter(exchange); }
由于jwt令牌中会携带用户id,将用户id存入请求头"user-info"中;
2.在公用模块中定义拦截器,在拦截器中将请求头中用户信息保存到ThreadLocal中
public class UserInfoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userInfo = request.getHeader("user-info"); //保存到ThreadLocal if(StrUtil.isNotBlank(userInfo)){ UserContext.setUser(Long.valueOf(userInfo)); } //放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //移除用户 UserContext.removeUser(); } }
从"user-info"请求头中获取到用户id,将其保存到ThreadLocal中。在经过网关后会先经过该拦截器再到达业务层,从而携带登录用户的id到业务逻辑中。
@Configuration @ConditionalOnClass(DispatcherServlet.class) public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInfoInterceptor()); } }
在该模块中定义SpringMVC的配置类,将定义的拦截器注册
由于是在微服务框架中,在其他启动模块中不会扫描到MvcConfig,所以需要手动配置
3.处理跨服务调用无法拿到用户信息的问题
当基于OpenFeign实现跨服务调用时,从一个服务向另一个服务发送请求时Feign会重新创建一个Http请求,而这个新的请求并不会继承原始的请求头信息。所以需要实现Feign提供的一个拦截器接口:feign.RequestInterceptor
public class DefaultFeignConfig { @Bean public RequestInterceptor userInfoRequestInterceptor(){ return new RequestInterceptor(){ @Override public void apply(RequestTemplate requestTemplate) { //获取登录用户 Long userId = UserContext.getUser(); if(userId == null){ return; } //如果不为空,添加到请求头中 requestTemplate.header("user-info", userId.toString()); } }; } }
在Feign发送Http请求前,通过RequestInterceptor拦截器将请求头添加到Feign创建的请求的头信息中。
Pasted image 20260724174630.png由于这是Feign的最佳实践,如果在DefaultFeignConfig类中加了 @Configuration,Spring 组件扫描会把它扫进主容器,里面的 @Bean 就变成全局 Bean 了。而 Feign 的配置类应该只作用于 Feign 自身的上下文,避免污染主容器。所以 Feign 框架会自己拿到这个类,为它创建独立的子上下文,完全不需要 Spring 扫描。需要在每个需要用到的微服务模块中添加这个注解。从流程可以看出,浏览在访问服务时会携带jwt向微服务发起请求,中间会先经过网关。网关从jwt中获取用户信息并保存到请求中,然后再转发给对应服务。在到达业务层之前会有拦截器将用户信息存入ThreadLocal中。
1.在经过网关过滤器被拦截时将用户信息保存到请求头中
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取request ServerHttpRequest request = exchange.getRequest(); // 判断是否需要拦截 if(isExclude(request.getPath().toString())){ // 无需拦截 放行 return chain.filter(exchange); } // 获取请求头中的token String token = null; List<String> headers = request.getHeaders().get("authorization"); if(CollUtils.isNotEmpty(headers)){ token = headers.get(0); } //校验并解析token Long userId = null; try { userId = jwtTool.parseToken(token); } catch (UnauthorizedException e) { //无效 拦截 ServerHttpResponse response = exchange.getResponse(); response.setRawStatusCode(401); return response.setComplete(); } // 如果有效,传递用户信息 String userInfo = userId.toString(); ServerWebExchange ex = exchange.mutate() .request(b -> b.header("user-info", userInfo)) .build(); // 放行 return chain.filter(exchange); }
由于jwt令牌中会携带用户id,将用户id存入请求头*"user-info"*中;
2.在公用模块中定义拦截器,在拦截器中将请求头中用户信息保存到ThreadLocal中
public class UserInfoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userInfo = request.getHeader("user-info"); //保存到ThreadLocal if(StrUtil.isNotBlank(userInfo)){ UserContext.setUser(Long.valueOf(userInfo)); } //放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //移除用户 UserContext.removeUser(); } }
从*"user-info"*请求头中获取到用户id,将其保存到ThreadLocal中。在经过网关后会先经过该拦截器再到达业务层,从而携带登录用户的id到业务逻辑中。
@Configuration @ConditionalOnClass(DispatcherServlet.class) public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInfoInterceptor()); } }
在该模块中定义SpringMVC的配置类,将定义的拦截器注册
697由于是在微服务框架中,在其他启动模块中不会扫描到MvcConfig,所以需要手动配置
3.处理跨服务调用无法拿到用户信息的问题
当基于OpenFeign实现跨服务调用时,从一个服务向另一个服务发送请求时Feign会重新创建一个Http请求,而这个新的请求并不会继承原始的请求头信息。所以需要实现Feign提供的一个拦截器接口:feign.RequestInterceptor
public class DefaultFeignConfig { @Bean public RequestInterceptor userInfoRequestInterceptor(){ return new RequestInterceptor(){ @Override public void apply(RequestTemplate requestTemplate) { //获取登录用户 Long userId = UserContext.getUser(); if(userId == null){ return; } //如果不为空,添加到请求头中 requestTemplate.header("user-info", userId.toString()); } }; } }
在Feign发送Http请求前,通过RequestInterceptor拦截器将请求头添加到Feign创建的请求的头信息中。
Pasted image 20260724174630.png由于这是Feign的最佳实践,如果在DefaultFeignConfig类中加了 @Configuration,Spring 组件扫描会把它扫进主容器,里面的 @Bean 就变成全局 Bean 了。而 Feign 的配置类应该只作用于 Feign 自身的上下文,避免污染主容器。所以 Feign 框架会自己拿到这个类,为它创建独立的子上下文,完全不需要 Spring 扫描。需要在每个需要用到的微服务模块中添加这个注解。从流程可以看出,浏览在访问服务时会携带jwt向微服务发起请求,中间会先经过网关。网关从jwt中获取用户信息并保存到请求中,然后再转发给对应服务。在到达业务层之前会有拦截器将用户信息存入ThreadLocal中。
1.在经过网关过滤器被拦截时将用户信息保存到请求头中
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取request ServerHttpRequest request = exchange.getRequest(); // 判断是否需要拦截 if(isExclude(request.getPath().toString())){ // 无需拦截 放行 return chain.filter(exchange); } // 获取请求头中的token String token = null; List<String> headers = request.getHeaders().get("authorization"); if(CollUtils.isNotEmpty(headers)){ token = headers.get(0); } //校验并解析token Long userId = null; try { userId = jwtTool.parseToken(token); } catch (UnauthorizedException e) { //无效 拦截 ServerHttpResponse response = exchange.getResponse(); response.setRawStatusCode(401); return response.setComplete(); } // 如果有效,传递用户信息 String userInfo = userId.toString(); ServerWebExchange ex = exchange.mutate() .request(b -> b.header("user-info", userInfo)) .build(); // 放行 return chain.filter(exchange); }
由于jwt令牌中会携带用户id,将用户id存入请求头*"user-info"*中;
2.在公用模块中定义拦截器,在拦截器中将请求头中用户信息保存到ThreadLocal中
public class UserInfoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userInfo = request.getHeader("user-info"); //保存到ThreadLocal if(StrUtil.isNotBlank(userInfo)){ UserContext.setUser(Long.valueOf(userInfo)); } //放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //移除用户 UserContext.removeUser(); } }
从*"user-info"*请求头中获取到用户id,将其保存到ThreadLocal中。在经过网关后会先经过该拦截器再到达业务层,从而携带登录用户的id到业务逻辑中。
@Configuration @ConditionalOnClass(DispatcherServlet.class) public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInfoInterceptor()); } }
在该模块中定义SpringMVC的配置类,将定义的拦截器注册
697由于是在微服务框架中,在其他启动模块中不会扫描到MvcConfig,所以需要手动配置
3.处理跨服务调用无法拿到用户信息的问题
当基于OpenFeign实现跨服务调用时,从一个服务向另一个服务发送请求时Feign会重新创建一个Http请求,而这个新的请求并不会继承原始的请求头信息。所以需要实现Feign提供的一个拦截器接口:feign.RequestInterceptor
public class DefaultFeignConfig { @Bean public RequestInterceptor userInfoRequestInterceptor(){ return new RequestInterceptor(){ @Override public void apply(RequestTemplate requestTemplate) { //获取登录用户 Long userId = UserContext.getUser(); if(userId == null){ return; } //如果不为空,添加到请求头中 requestTemplate.header("user-info", userId.toString()); } }; } }
在Feign发送Http请求前,通过RequestInterceptor拦截器将请求头添加到Feign创建的请求的头信息中。
由于这是Feign的最佳实践,如果在DefaultFeignConfig类中加了 @Configuration,Spring 组件扫描会把它扫进主容器,里面的 @Bean 就变成全局 Bean 了。而 Feign 的配置类应该只作用于 Feign 自身的上下文,避免污染主容器。所以 Feign 框架会自己拿到这个类,为它创建独立的子上下文,完全不需要 Spring 扫描。需要在每个需要用到的微服务模块中添加这个注解。