当我们创建好了N多个微服务或者微服务的实例之后,每个服务暴露出不同的端口地址,一般对于客户端请求,只需要请求一个端口,要隔离客户端和微服务的直接关系,保证微服务的安全性和灵活性,避免敏感信息的泄露。这时就需要网关。
网关
例如,我们在外人和村庄之间建立了一座桥梁,在桥上有一个管理员(守卫),他负责检查每个想要过桥的人。如果检查合格,他会允许通过,并告诉你要找的人的住处;如果不合格,则不允许通过。
网关充当了统一的接入点(桥梁),客户端仅需通过网关的固定端口或地址进行访问,无需了解各个微服务的具体端口和地址。网关内部根据配置的服务路由规则进行操作,一旦请求符合这些规则,网关便会从Nacos注册中心获取相应的服务地址,并将客户端的请求路由至指定的微服务。其他功能如身份校验,限流,路由转发,请求过滤等。
配置网关
将网关作为一个独立的模块,服务部署。配置启动类,引入依赖,配置路由规则即可。
到此启动网关后,通过8080及具体的路径就可以访问到任何一个微服务了,具体是哪个是由负载均衡决定好了。但是,目前是不安全的,因为没有做身份校验。我们希望的是请求必须经过网关处理。
配置网关内部过滤拦截器
可以在网关内部声明一个全局过滤器专门处理登录校验,这个自定义过滤器要实现GlobalFilter接口,并实现其方法。过滤器有很多个,是依次链式调用的,直到最后一个。
全局过滤器链的最后一个执行的过滤器,通常是 NettyWriteResponseFilter
。它执行完了,这个过滤过程也就结束了,这个过滤器的作用是负责将响应数据写回给客户端,它是默认过滤器链中最末端的一个过滤器。对于前置过滤器,会在请求到达微服务目标之前执行,可以实现Ordered接口给过滤器设置优先级,数字越小,优先级越高,越先执行。
大致步骤:
1,需要判断当前路径是否需要拦截,不需要就放行。
@Component
@RequiredArgsConstructor
public class MyAuthGlobalFilter implements GlobalFilter, Ordered {
private final AuthProperties authProperties;
private final static AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
List<String> excludePaths = authProperties.getExcludePaths();
// 判断当前路径是否需要拦截校验,不需要就放行
if (checkUrl(excludePaths, path)) {
return chain.filter(exchange);
}
AuthProperties中放的就是自定义好的不需要拦截的路径。这里需要的是将当前请求的路径与不需要拦截的路径进行匹配,True就放行。自定义一个匹配方法并使用Spring提供的AntPathMatcher进行路径匹配。
(放行:网关内部有很多个拦截过滤器,是依次执行调用的,实际上是调用下一个拦截器,然后将当前请求响应的上下文对象exchange传递过去)
2,如果需要拦截,就获取请求中的token信息进行校验
不仅要验证获取请求头中是否有相应字段,还要验证token是否有结果,以及是否能够成功解析,不管哪一步失败了,我们就向客户端响应失败的信息(响应状态码)。setComplete()
方法表示响应已完成。它会立即返回并结束当前请求的处理流程,不再执行后续的过滤器或业务逻辑。setComplete()
的返回值是一个 Mono<Void>
,这是 Reactor 中的一个信号,表示响应结束。
String token = null;
List<String> authorization = request.getHeaders().get("authorization");
if (authorization != null && !authorization.isEmpty()) {
token = authorization.get(0);
}
// 校验 token
Long parseToken=null;
if (token != null && !token.isEmpty()) {
parseToken = jwtTool.parseToken(token);
if (parseToken == null) {
// Token 校验失败,返回 401 未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
} else {
// Token 为空,返回 401 未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
此时访问8080/items/page有结果,因为它无需拦截:
访问另外一个被拦截的就没有结果8080/carts: