网关登录校验
-
自定义过滤器
-
网关过滤器有两种,分别是:
-
GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
-
GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。
-
-
用自定义的GlobalFilter来完成登录校验
-
提前准备的工具:
- AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
- JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置
- SecurityConfig:工具的自动装配
- JwtTool:JWT工具,其中包含了校验和解析token的功能
- hmall.jks:秘钥文件
-
其中AuthProperties和JwtProperties所需的属性要在application.yaml中配置
hm: jwt: location: classpath:hmall.jks # 秘钥地址 alias: hmall # 秘钥别名 password: hmall123 # 秘钥文件密码 tokenTTL: 30m # 登录有效期 auth: excludePaths: # 无需登录校验的路径 - /search/** - /users/login - /items/**
- 定义一个登录校验的过滤器:网关已经可以完成登录校验并获取登录用户身份信息,在网关模块进行配置,只要是请求就都到这个过滤器走一遍,排除的就直接放行,拦截到的就进行登录校验,校验通过后将user信息放到请求头中就放行,这样后面的微服务就可以拿到当前这个登录人的登录信息。
@Component @RequiredArgsConstructor @EnableConfigurationProperties(AuthProperties.class) public class AuthGlobalFilter implements GlobalFilter, Ordered { private final JwtTool jwtTool; private final AuthProperties authProperties; private final AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.获取Request ServerHttpRequest request = exchange.getRequest(); // 2.判断是否不需要拦截 if(isExclude(request.getPath().toString())){ // 无需拦截,直接放行 return chain.filter(exchange); } // 3.获取请求头中的token String token = null; List<String> headers = request.getHeaders().get("authorization"); if (!CollUtils.isEmpty(headers)) { token = headers.get(0); } // 4.校验并解析token Long userId = null; try { userId = jwtTool.parseToken(token); } catch (UnauthorizedException e) { // 如果无效,拦截 ServerHttpResponse response = exchange.getResponse(); response.setRawStatusCode(401); return response.setComplete(); } // TODO 5.如果有效,传递用户信息 System.out.println("userId = " + userId); // 6.放行 return chain.filter(exchange); } private boolean isExclude(String antPath) { for (String pathPattern : authProperties.getExcludePaths()) { if(antPathMatcher.match(pathPattern, antPath)){ return true; } } return false; } @Override public int getOrder() { return 0; } }
-
-
当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用
-
改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务
-
拦截器获取用户:由于每个微服务都有获取登录用户的需求,因此拦截器我们直接写在通用工具类模块中,并写好自动装配。这样微服务只需要引入hm-common就可以直接具备拦截器功能,无需重复编写。在此模块中定义一个工具类
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(); } }
- 编写SpringMVC的配置类,配置登录拦截器
@Configuration @ConditionalOnClass(DispatcherServlet.class) public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInfoInterceptor()); } }
- 但是因为这个配置类默认是不会生效的,因为目前它所在的包是com.hmall.common.config,与其他微服务的扫描包不一致,无法被扫描到,因此无法生效,基于springboot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.hmall.common.config.MyBatisConfig,\ com.hmall.common.config.MvcConfig
-
-
OpenFeign传递用户: 有些业务是比较复杂的,请求到达微服务后还需要调用其它多个微服务,比如一个复杂的下单业务,所以必须在微服务发起调用时把用户信息存入请求头:
- 借助Feign中提供的一个拦截器接口:feign.RequestInterceptor,实现这个接口,然后实现apply方法,利用RequestTemplate类来添加请求头,将用户信息保存到请求头中,目的是让每一个由OpenFeign发起的请求自动携带登录用户信息。
@Bean public RequestInterceptor userInfoRequestInterceptor(){ return new RequestInterceptor() { @Override public void apply(RequestTemplate template) { // 获取登录用户 Long userId = UserContext.getUser(); if(userId == null) { // 如果为空则直接跳过 return; } // 如果不为空则放入请求头中,传递给下游微服务 template.header("user-info", userId.toString()); } }; }