前言
单体架构在后端只需要进行一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。由于前端所有请求都要通过网关,所以我们将JWT鉴权放到网关中进行(通过自定义过滤器,在过滤器中实现逻辑,并将过滤器嵌入到过滤器链中实现)
从JWT解析到的用户信息可以加到请求头后放行
//如果有效,传递用户信息
String userInfo = userId.toString();
ServerWebExchange exchange2 = exchange.mutate().request(Builder -> Builder.header("user-info", userInfo)).build();
//放行
return chain.filter(exchange2);
但是让每个微服务中的每一个接口多增加一个参数用于接收不现实,所以我们需要用到SpringMVC中的拦截器,将请求中的用户信息存入ThreadLocal中,但为每一个微服务定义一个拦截器工作量一样很大。
在下图项目目录中,hm-common用于存放通用内容,所以其他微服务都引入了其pom坐标依赖,所以,我们只需在该项目下定义拦截器即可
1.定义拦截器
在hm-common的interceptors文件夹中建一个拦截器,此处名为UserInfointerceptor,实现HandlerInterceptor,重写preHandle和afterCompletion方法,用于ThreadLocal存信息和防止泄露(当线程结束,如果ThreadLocal变量没有被手动清除,就会导致这部分内存无法被回收,最终导致内存泄漏)
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的用户信息
String userInfo = request.getHeader("user-info");
// 2.判断是否为空
if (StrUtil.isNotBlank(userInfo)) {
// 不为空,保存到ThreadLocal
UserContext.setUser(Long.valueOf(userInfo));
}
// 3.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserContext.removeUser();
}
}
其中UserContext是hm-common中用于保存登录用户的ThreadLocal工具
public class UserContext {
private static final ThreadLocal<Long> tl = new ThreadLocal<>();
/**
* 保存当前登录用户信息到ThreadLocal
* @param userId 用户id
*/
public static void setUser(Long userId) {
tl.set(userId);
}
/**
* 获取当前登录用户信息
* @return 用户id
*/
public static Long getUser() {
return tl.get();
}
/**
* 移除当前登录用户信息
*/
public static void removeUser(){
tl.remove();
}
}
此时拦截器是不会生效的,还需要定义一个配置类将其注册
2.注册拦截器
在hm-common的config文件夹中建一个类,此处名为Mvcconfig,实现WebMvcConfigurer,重写
addInterceptors方法
@Configuration
public class Mvcconfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}
3.问题一
这个配置类默认是不会生效的,因为它所在的包是 com.hmall.common.config
,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。
基于SpringBoot的自动装配原理,我们要将其添加到 resources
目录下的 META-INF/spring.factories
文件中:
4.问题二
表面上看起来问题都解决了,但当我们启动网关时,会报错找不到配置文件
原因:网关引入了hm-common的pom坐标,网关中就有了Mvcconfig,但是Mvcconfig是属于SpringMvc包下的(实现WebMvcConfigurer),而网关的底层不是SpringMvc,而是Webflux的响应式编程,所以里面没有SpringMVC
实际情况是,网关是不需要引用这个配置类的,所以可以利用条件装配注解使其在网关中不生效而在其他微服务中生效(其中DispatcherServlet是SpringMVC的核心API,作为判断条件)