网关登录校验(2)----网关如何将用户信息传递给微服务

1.微服务获取用户信息

现在,网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?

由于网关发送请求到微服务依然采用的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

据图流程图如下:

因此,接下来我们要做的事情有:

  • 改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务

  • 编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行

1.1.保存用户到请求头

首先,我们修改登录校验拦截器的处理逻辑,保存用户信息到请求头中:

java 复制代码
package com.hmall.gateway.filters;

import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;


@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;
    private final JwtTool jwtTool;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取到request
        ServerHttpRequest request = exchange.getRequest();
        //2.判断是否需要拦截
        String path = request.getPath().toString();
        if(isExcluded(path)){
            //如果不需要拦截,放行
            return chain.filter(exchange);
        }
        //3.从请求头获取到token
        List<String> headers = request.getHeaders().get("authorization");
        String token = null;
        if(headers != null && !headers.isEmpty()){
            token = headers.get(0);
        }
        //4.解析token,获取到用户id
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            //token有问题,拦截
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);//设置401未登录状态码
            return response.setComplete();//直接结束
        }
        //5.TODO 传递用户信息到微服务
        String userInfo = userId.toString();
        ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-info",userInfo)).build();
        //6.放行
        return chain.filter(swe);
    }

    private boolean isExcluded(String path) {
        //匹配,如果成功匹配则true
        for (String pathPattern : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(pathPattern, path)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

1.2.拦截器获取用户信息

在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:

接下来,我们只需要编写拦截器,获取用户信息并保存到UserContext,然后放行即可。

由于每个微服务都有获取登录用户的需求,因此拦截器我们直接写在hm-common中,并写好自动装配。这样微服务只需要引入hm-common就可以直接具备拦截器功能,无需重复编写。

我们在hm-common模块下定义一个拦截器:

java 复制代码
package com.hmall.common.interceptor;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //此拦截器不作拦截,而是微服务想获取到网关或者上游微服务传递的请求头(用户信息)
        //1.获取到请求头(用户信息)
        String header = request.getHeader("user-info");
        //2.判断是否为空,有则获取,存入ThreadLocal
        if(StrUtil.isNotBlank(header)){
            UserContext.setUser(Long.valueOf(header));
        }
        //3.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserContext.removeUser();
    }
}

接着在hm-common模块下编写SpringMVC的配置类,配置登录拦截器:

java 复制代码
package com.hmall.common.config;

import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@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文件中:

java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig

1.3.恢复购物车代码

之前我们无法获取登录用户,所以把购物车服务的登录用户写死了,现在需要恢复到原来的样子。

找到cart-service模块的com.hmall.cart.service.impl.CartServiceImpl

相关推荐
二进制漫游记3 小时前
FastAPI项目集成 Qdrant向量数据库+阿里云Embedding完整实战(工具封装+业务调用)
数据库·python·阿里云·embedding
2501_928996223 小时前
GPT-4o换DeepSeek迁移成本多少?中科热备解析API聚合平台技术账本
前端·数据库·人工智能
zww89491114 小时前
匿名树洞系统开发实战:从需求分析到部署指南
java·eclipse
泡干脆面就番茄5 小时前
MySQL_子查询_分页查询与联合查询详解
数据库·mysql
黑马程序员毕设5 小时前
基于Java的医院药品管理系统的优化设计与实现
java·开发语言·spring boot·小程序·架构·课程设计·毕设
头茬韭菜5 小时前
第 01 篇:「架构鸟瞰与进程启动链路」—— JobManager / TaskManager 从零长出来的完整调用链
架构·flink
CaseyWei5 小时前
Harness 架构 Multi‑Agent(多智能体)完整深度解析
人工智能·ai·架构·harness
木井巳5 小时前
【BFS/DFS 解决 FloodFill 算法】太平洋大西洋水流问题
java·算法·leetcode·深度优先·广度优先·宽度优先·推荐算法
白山编程大哥6 小时前
Java 集合算法:从排序、查找到底层原理的实战指南
java·python·算法
梦Arrebol6 小时前
Kubernetes 微服务
微服务·容器·kubernetes