Spring Cloud系列教程(十九):下一代网关服务Gateway-全局Filter(Finchley版本)

官网文档: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

一、前言

Spring Cloud Gateway根据过滤器Filter的作用范围划分为GatewayFilterGlobalFilter,二者区别如下:

GatewayFilter : GatewayFilter称为内置过滤器,需要通过 spring.cloud.routes.filters配置在具体路由下,只作用在当前路由上或者特定路由上,可以通过配置 spring.cloud.default-filters,表明作用在所有路由上,GatewayFilter允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway提供了许多内置的GatewayFilter工厂。

GlobalFilter: GlobalFilter称为全局过滤器,不需要在配置文件中配置,作用在所有的路由上,全局有效。

二. GlobalFilter(全局过滤器)

GlobalFilter全局过滤器不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

Spring Cloud Gateway框架提供的内置的GlobalFilter如下:
Spring Cloud系列教程(十九):下一代网关服务Gateway-全局Filter(Finchley版本)_第1张图片

三. GlobalFilter和GatewayFilter区别

GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter会作用于所有路由,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP访问限制等等;而GatewayFilter作用于某一个特定的路由, GlobalFilter全局过滤器是一系列特殊的过滤器,会根据条件应用到所有路由中。GatewayFilter网关过滤器是更细粒度的过滤器,作用于指定的路由中。

四. Gateway提供的GlobalFilter有哪些

gateway自带的GlobalFilter实现类有很多,如下图:
Spring Cloud系列教程(十九):下一代网关服务Gateway-全局Filter(Finchley版本)_第2张图片
有转发,路由,负载,WebSocket等相关的GlobalFilter,有兴趣的小伙伴可以下载对应的源码研究下。

五. 如何定义一个GlobalFilter

关于如何定义一个GlobalFilter实现自己的业务逻辑,Gateway官方给出了一个例子:

例子5.1. ExampleConfiguration.java

@Bean
public GlobalFilter customFilter() {
    return new CustomGlobalFilter();
}

/**
 * 自定义全局过滤器
 */
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    /**
     * 过滤器业务逻辑
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("custom global filter");
        return chain.filter(exchange);
    }
   
    /**
     * 过滤器执行顺序,数值越小,优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }
}

六. GlobalFilter实现统一鉴权

定义一个AuthFilter过滤器实现 GlobalFilter, Ordered全局过滤器,实现统一鉴权,例如摘自ruoyi-cloud中的实现方案如下:

参考自: https://gitee.com/zhangmrit/ruoyi-cloud/

package com.ruoyi.gateway.fiflt;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

import javax.annotation.Resource;

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.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.R;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 网关鉴权
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered
{
    // 排除过滤的 uri 地址
    // swagger排除自行添加
    private static final String[]           whiteList = {"/auth/login", "/user/register", "/system/v2/api-docs",
            "/auth/captcha/check", "/auth/captcha/get","/auth/login/slide"};

    @Resource(name = "stringRedisTemplate")
    private ValueOperations<String, String> ops;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        String url = exchange.getRequest().getURI().getPath();
        log.info("url:{}", url);
        // 跳过不需要验证的路径
        if (Arrays.asList(whiteList).contains(url))
        {
            return chain.filter(exchange);
        }
        String token = exchange.getRequest().getHeaders().getFirst(Constants.TOKEN);
        // token为空
        if (StringUtils.isBlank(token))
        {
            logger.info( "token is empty..." );
            return setUnauthorizedResponse(exchange, "token can't null or empty string");
        }
        String userStr = ops.get(Constants.ACCESS_TOKEN + token);
        if (StringUtils.isBlank(userStr))
        {
            return setUnauthorizedResponse(exchange, "token verify error");
        }
        JSONObject jo = JSONObject.parseObject(userStr);
        String userId = jo.getString("userId");
        // 查询token信息
        if (StringUtils.isBlank(userId))
        {
            return setUnauthorizedResponse(exchange, "token verify error");
        }
        // 设置userId到request里,后续根据userId,获取用户信息
        ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(Constants.CURRENT_ID, userId)
                .header(Constants.CURRENT_USERNAME, jo.getString("loginName")).build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
        return chain.filter(mutableExchange);
    }

    private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg)
    {
        ServerHttpResponse originalResponse = exchange.getResponse();
        originalResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
        originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] response = null;
        try
        {
            response = JSON.toJSONString(R.error(401, msg)).getBytes(Constants.UTF8);
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
        return originalResponse.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder()
    {
        return -200;
    }
}

在上面的AuthFilter需要实现GlobalFilter和Ordered接口,这和实现GatewayFilter很类似。然后根据ServerWebExchange获取ServerHttpRequest,再根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发,否则执行正常的逻辑。

你可能感兴趣的:(Spring,Cloud2.x系列教程)