网关登录校验

  1. 自定义过滤器

    • 网关过滤器有两种,分别是:

    • GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。

    • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。

  2. 用自定义的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;
        }
    }
    
  3. 当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用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
    
  4. 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());
            }
        };
    }
    
posted @ 2024-05-28 16:46  Hanyta  阅读(69)  评论(0编辑  收藏  举报