网关中如何传递用户信息?

微服务治理容器服务数据库

大家好,我是苏三。

今天来解决一个粉丝的问题:在SpringCloud Gateway实现鉴权时,如何在转发请求到子业务模块时获取登录用户信息?

首先从请求头中获取 Authorization 参数得到 JWT Token,然后解开 JWT 后即可获取用户信息。

以下是相关代码:

  
@Override  
public AuthenticatorResult auth(ServerWebExchange exchange) {  
 ServerHttpRequest request = exchange.getRequest();  
 HttpHeaders httpHeaders = request.getHeaders();  
  
// 获取JWT请求头 Authorization  
 String token = httpHeaders.getFirst(HttpHeaders.AUTHORIZATION);  
  
if (Objects.nonNull(token)) {  
try {  
   String subjectFromJWT = JwtUtil.getSubjectFromJWT(token);  
   log.info("用户请求token: {} , 身份Subject:{}", token, subjectFromJWT);  
   returnnew AuthenticatorResult(true, "认证通过");  
  } catch (ParseException | JOSEException e) {  
   log.error("token解析失败{}",token);  
   returnnew AuthenticatorResult(false, "Token错误,请重新登录!");  
  }  
  
 }  
  
returnnew AuthenticatorResult(false, "Token为空,请重新登录!");  
}  

实现方案

通常情况下,在 Spring Cloud 中将用户信息透传给后端服务有两种方式:

第一种:在网关解析出用户的 Token 得到用户 ID,然后将用户 ID 添加到请求头中传递下去。

第二种:在网关直接把 Token 传递下去,由各个子服务自行解析。

在 DailyMart 中我推荐使用第一种方式,将 JWT Token 解析后直接透传给后端服务。

以下是实现方案:

  1. 在网关解析 JWT Token 后得到 UserID,修改 Spring Cloud Gateway 的请求头,将 UserID 添加到请求头中。
  2. 自定义用户上下文 UserContextHolder ,并使用 ThreadLocal 进行存储。
  3. 在微服务中的 Web 组件中创建拦截器 UserTokenInterceptor ,从 Request 中获取 UserID,并将其添加到用户上下文 UserContextHolder 中。
  4. 将拦截器 UserInterceptor 注册到 Spring 容器中。

代码实现

1、在SpringCloud Gateway中修改请求头

  
public class DefaultApiAuthenticator implements ApiAuthenticator {  
      
    @Override  
    public AuthenticatorResult auth(ServerWebExchange exchange) {  
       ...  
        if (Objects.nonNull(token)) {  
            try {  
                String subjectFromJWT = JwtUtil.getSubjectFromJWT(token);  
                  
              //重新设置请求头  
                mutateNewHeader(exchange, subjectFromJWT);  
  
                returnnew AuthenticatorResult(true, "认证通过");  
            } catch (ParseException | JOSEException e) {  
                log.error("token解析失败");  
                returnnew AuthenticatorResult(false, "Token错误,请重新登录!");  
            }            
        }          
        returnnew AuthenticatorResult(false, "Token为空,请重新登录!");  
    }  
      
    /**  
     * 重新构建请求头,将用户账号放入Token  
     * @param subject 用户身份  
     */  
    private void mutateNewHeader(ServerWebExchange exchange, String subject) {  
        ServerHttpRequest newRequest = exchange.getRequest().mutate().header(CommonConstant.X\_CLIENT\_TOKEN, subject).build();  
        exchange.mutate().request(newRequest).build();  
    }  
}  

2、自定义用户上下文UserContextHolder

  
public class UserContextHolder {  
      
    privatefinal ThreadLocal<String> threadLocal;  
      
    private UserContextHolder() {  
        this.threadLocal = new ThreadLocal<>();  
    }  
      
    public static UserContextHolder getInstance() {  
        return SingletonHolder.instance;  
    }  
      
    /**  
     * 设置用户数据  
     * @param userId 用户账号  
     */  
    public void setCurrentUser(String userId) {  
        this.threadLocal.set(userId);  
    }  
      
    /**  
     * 获取当前用户  
     * @return userId  
     */  
    public String getCurrentUser() {  
        returnthis.threadLocal.get();  
    }  
      
    /**  
     * 清理用户信息  
     */  
    public void clear() {  
        this.threadLocal.remove();  
    }  
}  

3、创建自定义拦截器UserTokenInterceptor

由于每个服务中都需要用到此功能,所以我们将此功能在公共组件dailymart-web-spring-boot-starter中实现。

  
public class UserTokenInterceptor implements HandlerInterceptor {  
      
    @Override  
    public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {  
        String userId = request.getHeader(CommonConstant.X\_CLIENT\_TOKEN);  
          
        // 设置用户信息  
        UserContextHolder.getInstance().setCurrentUser(userId);  
          
        returntrue;  
    }  
      
    @Override  
    public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) throws Exception {  
        UserContextHolder.getInstance().clear();  
    }  
}  

这里先从请求头中获取userId,然后使用单例类UserContextHolder将userId添加到ThreadLocal中,当然在处理完成后需要清空ThreadLocal的值,不然会出现内存泄露。

4、创建配置类,在Spring中注册拦截器

  
@SpringBootConfiguration  
@ConditionalOnWebApplication  
publicclass WebMvcConfigurerAdaptor implements WebMvcConfigurer {  
      
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        registry.addInterceptor(userInterceptor())  
                .addPathPatterns("/api/pd/**")  
                .excludePathPatterns(excludePathList);  
    }  
      
    privatefinal String[] excludePathList = new String[]{  
            "/api/pd/customer/login"  
    };  
      
    @Bean  
    public HandlerInterceptor userInterceptor() {  
        returnnew UserTokenInterceptor();  
    }  
}  

在上篇文章中我们已经规定来源标识为/pd的才算是浏览器的请求,所以在这个拦截器中我们只需要配置特定的拦截地址即可。

5、获取userId

在后续微服务中如果需要获取 UserID,只需从 UserContextHolder 获取即可:

  
@Operation(summary = "用户测试接口")  
@PostMapping("/api/pd/customer/info")  
public void info() {  
    
 String currentUser = UserContextHolder.getInstance().getCurrentUser();  
 log.info("当前登录用户:" + currentUser);  
}  

小结

本文介绍了在 Spring Cloud Gateway 中实现鉴权并将用户信息传递给后端服务的解决方案。

通过解析 JWT Token 并将用户身份信息添加到请求头,实现了简单而有效的用户身份认证和信息传递。

当然,文章中仅仅传递了UserID,在实际使用中,大家也可以先在网关层通过UserID获取用户的详细信息,再将详细信息进行传递。

  • End-

最后欢迎加入苏三的星球,你将获得:智能天气播报AI Agent、SaaS点餐系统(DDD+多租户)、100万QPS短链系统(超过并发)、复杂的商城微服务系统(分布式)、苏三商城系统、苏三AI项目、刷题吧小程序、秒杀系统、码猿简历网站、代码生成工具等10个项目的源代码、开发教程和技术答疑。 系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。

还有1V1免费修改简历、技术答疑、职业规划、送书活动、技术交流。

扫描下方二维码,可以加入星球:

picture.image

数量有限,先到先得。 目前星球已经更新了6100+篇优质内容,还在持续爆肝中.....

星球已经被官方推荐了3次,收到了小伙伴们的一致好评。戳我加入学习,已有2100+小伙伴加入学习。

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动云原生降本增效实践
本次分享主要介绍字节跳动如何利用云原生技术不断提升资源利用效率,降低基础设施成本;并重点分享字节跳动云原生团队在构建超大规模云原生系统过程中遇到的问题和相关解决方案,以及过程中回馈社区和客户的一系列开源项目和产品。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论