最近在学习SpringCloud项目时,想到了一些问题,各个微服务分别部署在不同的服务上,由naocs作为注册中心实现负载均衡,彼此之间通过Feign相互调用通信,信息同步并不像单体项目那样方便,传统单体项目的登录验证方式似乎在SpringCloud中不能满足项目的需求。那么当用户完成登录后,各微服务该如何确认用户的登录状态呢?
下面有几种实现思路:
这里为大家提供一种较为简单的方式:使用Redis分布式缓存储存用户登录信息。在gateway微服务中配置过滤器,在过滤器中获取到达网关的请求所携带的token信息,如果token为空或token对应的key在Redis中不存在,向用户返回401 UNAUTHORIZED的状态码;如果token验证正确,便刷新Redis中对应key的TTL,并继续向负载均衡的请求地址发送请求且携带相应的token信息。在接收请求的微服务中编写拦截器,在拦截器中获取token并通过Redis拿取对应的用户信息。
gateway中的过滤器代码实现:
@Order(1) @Configuration public class GlobalFilterConfig implements GlobalFilter { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst(GET_TOKEN); if (token == null || token.isEmpty()) { return unAuthorize(exchange); } Map
注意需要使用@Order注解为该全局过滤器设置优先级。当过滤器的order值一致时,过滤器的执行顺序为:defaultFilter>路由过滤器>GlobalFilter,因此该过滤器的order值应当设置为较小值,以确保该全局过滤器的正确执行。(order值越小,优先级越高,执行顺序越靠前)
微服务中拦截器的代码实现:
public class LoginHandlerInterceptor implements HandlerInterceptor { private StringRedisTemplate stringRedisTemplate; // 由于该类未交给spring管理,因此不能使用自动装配的方式获取RedisTemplate对象 public LoginHandlerInterceptor(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader(GATEWAY_TOKEN); if (token == null || token.isEmpty()) { return false; } Map
@Configuration public class MyWebConfig implements WebMvcConfigurer { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor(stringRedisTemplate)) .addPathPatterns("/**"); } }
到这里我们就完成了gateway到微服务的用户登录信息传递。接下来就需要解决微服务与微服务之间的登录信息传递问题。在这个项目中各微服务通过分Feign实现相互调用通信,那么我们只需要在调取Feign时携带token信息就好:
@FeignClient(name = "test-gateway") public interface ExampleClient { @GetMapping("/api/example") String getExampleData(@RequestHeader("token") String token); }
但每次调用该Feign接口时都需要我们手动传入token值,不太优雅,因此采用下面的方式来配置Feign,每当Feign接口被调用时就会携带token信息:
public class FeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { requestTemplate.header(GET_TOKEN, TokenContext.getToken()); } }
@FeignClient(name = "test-gateway", configuration = FeignRequestInterceptor.class) public interface ExampleClient { @GetMapping("/api/example") String getExampleData(@RequestHeader("token") String token); }
若遇到启动时报错A bean with that name has already been defined and overriding is disabled可以看这篇文章:【SpringCloud】使用OpenFeign的spring项目启动时报错bean注册问题
至此就完成了最基础的微服务登录信息传递。