Spring Cloud GateWay过滤器

作者: adm 分类: java 发布时间: 2021-07-21

Spring Cloud Gateway除了具备请求路由功能之外,也支持对请求的过滤。通过Zuul网关类似,也是通
过过滤器的形式来实现的。那么接下来我们一起来研究一下Gateway中的过滤器
3.3.1 过滤器基础
(1) 过滤器的生命周期
Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么丰富,它只有两个:“pre” 和 “post”。
PRE : 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择
请求的微服务、记录调试信息等。
POST :这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP
Header、收集统计信息和指标、将响应从微服务发送给客户端等。

( 2) 过滤器类型
Spring Cloud Gateway 的 Filter 从作用范围可分为另外两种GatewayFilter 与 GlobalFilter。
GatewayFilter :应用到单个路由或者一个分组的路由上。
GlobalFilter :应用到所有的路由上。
3.3.2 局部过滤器
局部过滤器(GatewayFilter),是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在
Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的局部过滤器。这里简单将
Spring Cloud Gateway内置的所有过滤器工厂整理成了一张表格,虽然不是很详细,但能作为速览使
用。如下:

 

每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory 结尾,这是
Spring Cloud Gateway的一个约定,例如 AddRequestHeader 对应的实现类为
AddRequestHeaderGatewayFilterFactory 。对于这些过滤器的使用方式可以参考官方文档
3.3.3 全局过滤器
全局过滤器(GlobalFilter)作用于所有路由,Spring Cloud Gateway 定义了Global Filter接口,用户
可以自定义实现自己的Global Filter。通过全局过滤器可以实现对权限的统一校验,安全性验证等功
能,并且全局过滤器也是程序员使用比较多的过滤器。
Spring Cloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:

 

3.4 统一鉴权
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己
编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。
3.4.1 鉴权逻辑
开发中的鉴权逻辑:
当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
认证通过,将用户信息进行加密形成 token,返回给客户端,作为登录凭证
以后每次请求,客户端都携带认证的 token
服务端对 token进行解密,判断是否有效。

 

 

如上图,对于验证用户是否已经登录鉴权的过程可以在网关层统一检验。检验的标准就是请求中是否携
带token凭证以及token的正确性。
3.4.2 代码实现
下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求
参数“token”则不转发路由,否则执行正常的逻辑。

一、返回401状态码和提示信息
只要将自定义的GlobalFilter声明成Spring Bean就会自动生效,Ordered接口用来指定拦截器生效顺序(数字越小优先级越高)。

 

package com.yinzhong.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@Component
public class LoginFilter implements GlobalFilter, Ordered {

    /**
     * 执行过滤器中的业务逻辑
     * 对请求参数中的access-token进行判断
     * 如果存在此参数:代表已经认证成功
     * 如果不存在此参数 : 认证失败.
     * ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器");
//1.获取请求参数access-token
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
       //2.判断是否存在
        //返回401状态码和提示信息
        if (StringUtils.isBlank(token)) {
            ServerHttpResponse response = exchange.getResponse();
            JSONObject message = new JSONObject();
            message.put("status", -1);
            message.put("data", "鉴权失败");
            byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //指定编码,否则在浏览器中会中文乱码
            response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        }
       //3.如果存在,继续执行
        return chain.filter(exchange); //继续向下执行
    }

    /**
     * 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

二、重定向(redirect)到指定页面
对于浏览器,通常是发现没有权限后跳转到登录页面。响应状态码需要为HttpStatus.SEE_OTHER(303)。

重定向(redirect)会丢失之前请求的参数,对于需要转发到目标URL的参数,需手工添加。

package com.yinzhong.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

@Component
public class LoginFilter implements GlobalFilter, Ordered {

    /**
     * 执行过滤器中的业务逻辑
     * 对请求参数中的access-token进行判断
     * 如果存在此参数:代表已经认证成功
     * 如果不存在此参数 : 认证失败.
     * ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器");
//1.获取请求参数access-token
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
       //2.判断是否存在
        //返回401状态码和提示信息
        if (StringUtils.isBlank(token)) {
             String url = "http://想跳转的网址";
           ServerHttpResponse response = exchange.getResponse();
           //303状态码表示由于请求对应的资源存在着另一个URI,应使用GET方法定向获取请求的资源
           response.setStatusCode(HttpStatus.SEE_OTHER);
           response.getHeaders().set(HttpHeaders.LOCATION, url);
           return response.setComplete();
        }
      //3.如果存在,继续执行
        return chain.filter(exchange); //继续向下执行
    }

    /**
     * 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!