SpringCloud微服务中网关如何传递用户信息?

我们已经实现了在网关层进行身份认证:首先从请求头中获取 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);
   return new AuthenticatorResult(true, "认证通过");
  } catch (ParseException | JOSEException e) {
   log.error("token解析失败{}",token);
   return new AuthenticatorResult(false, "Token错误,请重新登录!");
  }
  
 }
 
 return new 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);

                return new AuthenticatorResult(true, "认证通过");
            } catch (ParseException | JOSEException e) {
                log.error("token解析失败");
                return new AuthenticatorResult(false, "Token错误,请重新登录!");
            }          
        }        
        return new 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 {
    
    private final 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() {
        return this.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);
        
        return true;
    }
    
    @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
public class WebMvcConfigurerAdaptor implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInterceptor())
                .addPathPatterns("/api/pd/**")
                .excludePathPatterns(excludePathList);
    }
    
    private final String[] excludePathList = new String[]{
            "/api/pd/customer/login"
    };
    
    @Bean
    public HandlerInterceptor userInterceptor() {
        return new 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获取用户的详细信息,再将详细信息进行传递。同时,本文涉及的代码都已经上传至Github,感兴趣的可以通过文末方式获取。

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值