1.微服务网关 的定义
正常来说 我们 请求转发到我们的微服务的时候 是对请求链接进行拼接的,比如 我们的购物车服务的 请求地址后面有一个 /cartservice ,然后我们其他请求 过来 就是对一个网址进行拼接,我们使用本地 请求购物车服务 就是 localhost/8080 /user/cartservice ,前面的基本都一样 就是我们最后几个请求不一样 ,而网关就是根据我们 请求的网址 进行鉴权 校验 看看需要转发到哪个 微服务中去,那 网关既然帮我们进行校验了,那网关就可以取代我们的普通的单体架构当中 的过滤器 拦截器的校验了。
=而网关怎么知道我们要访问哪个微服务呢,这时候就用到路由了,路由来指定哪个网址转发到哪个微服务中去
最终的请求转发是由 nettyroutingfilter进行的,所以我们的自定义登录鉴权 +用户信息的配置的路在请求转发到微服务之前来完成,也就是nettyroutingfilter之前,所以我们需要给我们的过滤波器也设置一个优先级 我们直接看实现代码
网关全局过滤器代码实现
@RequiredArgsConstructor
@Slf4j
@Component
public class TokenGlobalFilter implements GlobalFilter, Ordered {
private final JwtProperties jwtProperties;
private final StringRedisTemplate redisTemplate;
// 这个过滤器 请求的转发
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//我们只要是非登录请求全都要检验jwt 然后进行 用户信息的传递
//获取request对象
ServerHttpRequest request = exchange.getRequest();
RequestPath requestPath = request.getPath();
if(requestPath.toString().contains("/userLogin")){
return chain.filter(exchange);
}
//获取请求头的 token
// String redisToken =null;
// List<String> authorization = request.getHeaders().get("authorization");
// if (!CollUtil.isEmpty(authorization)) {
// redisToken = authorization.get(0);
// }
//获取请求 头的token 然后 拿到用户id 拼接到redis中
String first = request.getHeaders().getFirst(jwtProperties.getTokenname());
if(first==null){
log.info("first为空");
return exchange.getResponse().setComplete();
}
log.info("请求头的详细信息为{}",request.getHeaders());
log.info("first的token={}",first);
Claims claims1 = JwtUntils.parseJwt(first, jwtProperties.getSecretkey());
String loginId2 = claims1.get(MessageConstant.LOGIN_ID).toString();
String redisToken = (String)redisTemplate.opsForValue().get(MessageConstant.TOKEN+loginId2);
log.info("token:{}",redisToken);
if (redisToken != null && redisToken.equals(first) ) {
//进行jwt的解析
try {
Claims claims = JwtUntils.parseJwt(redisToken, jwtProperties.getSecretkey());
//每次 访问其他资源的时候 都把token更新
String loginId = claims.get(MessageConstant.LOGIN_ID).toString();
log.info("网关层当前用户的id:{}", Long.valueOf(loginId));
redisTemplate.expire(MessageConstant.TOKEN+loginId, 1000, TimeUnit.DAYS);
//证明 token有效 传递用户信息
ServerWebExchange loginId1 = exchange.mutate()
.request(b -> b.header(MessageConstant.LOGIN_ID, loginId))
.build();
return chain.filter(loginId1);
} catch (Exception e) {
log.info("{}",e.getMessage());
//出现异常返回一个异常响应
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(401);
return response.setComplete();
}
}
log.info("token错误");
return exchange.getResponse().setComplete();
}
//过滤器链中的优先级 数值越低 优先级就越高
@Override
public int getOrder() {
return 0;
}
}
这是登录鉴权 并且往下一级传递用户信息的代码,我们只需要解析关键部分即可,首先 我们这个网关在使用之前需要引入网关的依赖这个就不说了,然后我们自定义个过滤器类,实现
GlobalFilter, Ordered
这接口,然后重写其中的filter 与getorder方法,filter做登录校验和 信息传递,而getorder方法进行过滤器优先级的配置,我们直接返回0,
再往下看
我们解析到token之后,根据我们的登录设置jwt令牌的token 获得当时传的用户id,然后 手动设置一个请求投给 我们的请求体,然后把改请求头传给下一层过滤器,最后靠过滤器进行 用户信息的传递
微服务实例用户信息解析
好,那么现在 我们到 把请求转发到微服务了,我们的服务在接收的时候需要解析出来我们设置的请求 信息,然后把请求信息传递到整个实例中 , 我们怎么解析嘞,当然是请求进入实例的时候加一个拦截器就可以啦
@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String header = request.getHeader(MessageConstant.LOGIN_ID);
if(header !=null){
ThreadContext.setCurrentId(Long.valueOf(header));
log.info("网关之后 获得的用户id{}",ThreadContext.getCurrentId());
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
ThreadContext.removeCurrentId();
}
}
然后我们在微服务实例互相调用的时候需不需要知道我们的用户信息,那当然是也需要 我们只需要用feign中的一个实例就可以了
@Slf4j
public class UserFeignConfig {
@Bean
public RequestInterceptor requestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Long currentId = ThreadContext.getCurrentId();
if(currentId == null){
return;
}
log.info("feign调用的时候传递了用户id{}",currentId);
requestTemplate.header(MessageConstant.LOGIN_ID,currentId.toString());
}
};
}
}