【微服务专题】深入理解与实践微服务架构(十二)之Gateway全局请求过滤功能实践

2,052 阅读17分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

上文已经成功集成了Gateway业务网关的基本功能,但是详细的应用场景和对应问题的功能还没有介绍,下面在实践中进行介绍:

6. 禁止绕过网关访问后端服务

前言

在上面的测试中,存在两个方式都可以调用成功服务接口:①经过网关规则过滤的请求;②直接访问后端服务的请求

虽然网关有隐藏后端真实服务地址的功能,但难免会有恶意请求探测到后端真实服务地址,从而绕过网关的规则直接请求到后端服务。

那么,为了保证后端服务的安全,我们需要禁止外部请求绕过网关访问到内部服务

实现思路

那么,如何防止绕过网关直接请求后端服务呢?

有几种思路:

  • 容器网络隔离:使用K8S自带的网络隔离功能。在使用Kubernetes部署SpringCloud架构时,给网关的Service配置NodePort,其他后端服务的Service使用ClusterIp,这样在集群外就只能访问到网关了。
  • 内外网隔离:后端普通服务都部署在内网,通过防火墙策略限制只允许网关应用访问后端服务。
  • 应用层拦截:请求后端服务时通过拦截器校验请求是否来自网关,如果不来自网关则提示不允许访问。

实现思路其实也很简单:在请求经过网关的时候给请求头中增加一个额外的 Header,在后端服务中写一个拦截器;判断请求头是否与在网关设置的请求 Header 一致,如果不一致则不允许访问并给出提示。

当然,这里有一个问题:保证所有请求都只能从网关访问后,那么原来内部服务之间直接调用会失效

怎么解决内部服务调用不用走网关问题呢?

在网关中设计服务白名单,这样内部服务调用时从网关获取服务信息就不用走CAS验证了。

这样又引出了另一个问题:网关存在单点故障问题

如何内部服务调用也要走网关,那么相当于将内部和外部的请求压力都放到了网关上面,那么需要做网关集群才能解决这个问题。

那么,怎么解决网关单点故障问题呢?

这里直接采用内外网隔离策略,至于安全、权限等外部请求访问控制功能交给网关,内网服务之间是可以直接通信的。因此,我们只需要在服务中缓存本地的服务列表和负载均衡规则就可以不用依赖网关直接调用了,也就解决了网关压力问题了。

参考阿里HSF架构的设计,是通过配置中心5s内将服务列表和负载规则快速推送到内部服务中,因此相当于每一个服务都有了缓存在本地的服务列表信息。因此,只要启动一次,无需网关内部也能直接调用,也就相当于内部服务间点对点调用了。

这种配置下发的方式,几乎只要初始化一次,就能实现内部服务之间直接高速调用。

CAS机制

因为Header可以伪造,为了设计得更安全点,我们可以采用Header中存储Token来保证——这就是CAS机制了。

CAS有两次跳转一次后台验证

第一次跳转:客户端访问应用系统,应用系统判断Session发现未登录,返回302跳转到sso登录页面,并传递service参数给sso,该service参数有两个作用:

  1. service一般传递应用系统url地址,用于sso认证通过后回跳到应用系统;
  2. service参数同时会被cas服务端的作为cas客户端的唯一标记记录下来,用于后期匹配相应的认证凭据;

第二次跳转:浏览器显示登录页面,用户输入账号密码登录成功后,sso会返回302跳转回到原来请求的应用系统页面,并携带ticket参数,作为认证票据,同时通过Set-Cookie向浏览器记录TGT,(TGT的作用将在下一个应用系统需要登录的时候体现出作用,是避免重复登录的关键)

一次后台验证:应用系统接收到带有ticket的请求后,从后台直接向sso服务器发起一个http请求,将service和ticket作为参数,用于验证ticket的有效性;如果ticket有效,sso服务器将返回该ticket对应的登录用户名。

当然,生产环境中,后端服务本身在内网中就是不可以直接访问的,因此搭配网关的白名单即可解决内部服务调用问题。

下面我们采用token验证的方式(先不使用CAS与内外网隔离的方式),来实现网关的拦截器隐蔽后端真实接口的功能

开发中的鉴权逻辑:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务端对token进行解密,判断是否有效。

image-20220627081717068

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

第一步,将访问网关服务没有携带token的请求过滤(可以搭配认证服务器,完成认证授权逻辑 -- 安全机制)

package com.deepinsea.common.filter;
​
import com.alibaba.cloud.commons.lang.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ServerHttpRequest;
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.util.Map;
​
/**
 * Created by deepinsea on 2022/6/27.
 */
//自定义全局过滤器需要实现GlobalFilter和Ordered接口
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
​
    private static final Logger log = LoggerFactory.getLogger(AuthGlobalFilter.class);
​
    //完成判断逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest(); //请求
        ServerHttpResponse response = exchange.getResponse(); //响应
        String token = request.getQueryParams().getFirst("token");
        // 正确的调用方式: http://localhost:9070/provider-nacos/hello?token=gateway
​
        if (StringUtils.isBlank(token)) {
            //首先应该是跳转到登录页面进行登录
//            System.out.println("鉴权失败,缺少token参数!");
            log.info("鉴权失败,缺少token参数!");
            //鉴权失败,返回错误码
            return returnMsg(response,1);
//            response.setStatusCode(HttpStatus.UNAUTHORIZED);
//            return response.setComplete();
        } else if (!"gateway".equals(token)) {
//            System.out.println("token无效,请传入正确的token!");
            log.info("token无效,请传入正确的token!");
            return returnMsg(response, 2);
//            response.setStatusCode(HttpStatus.UNAUTHORIZED);
//            return response.setComplete();
        } else {
            //调用chain.filter继续向下游执行(这里应该是直接将header/cookie中的token向下游服务传递,自己保存一份进行验证)
//            System.out.println("token有效,请求成功!");
            log.info("token有效,请求成功!");
            //为下游服务添加请求头(注意: 浏览器不能获取,因为浏览器获取响应的优先级低于全局Filter)
            //下游服务可以通过@RequestHeader获取(@RequestHeader("token_uuid") String tokenUuid)
            ServerHttpRequest httpRequest = request.mutate().header("gateway_token", "2bb062ad-682a-4eab-bd72-11f0b9f54a53").build();
//            ServerHttpRequest httpRequest = request.mutate().header("gateway_token", UUID.randomUUID().toString()).build();
            log.info("Header添加token成功!");
            //打印添加的header
            log.info(request.getHeaders().toString());
//            returnMsg(response, 3); //和下面的选择一种返回即可
            return chain.filter(exchange.mutate().request(httpRequest).build());
//            return chain.filter(exchange);
        }
    }
​
    // 顺序,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 999;
    }
​
    public Mono<Void> returnMsg(ServerHttpResponse response, int state) {
        // 封装错误信息
        Map<String, Object> responseData = Maps.newHashMap();
        //注意,这里的信息不应该展示给用户,需要给前端处理后展示,同时上面在网关上判断请求头的token应该设置过期时间,并且应该交给登录模块做
        if (state == 1) {
            responseData.clear();
            responseData.put("code", 401);
            responseData.put("message", "非法请求");
            responseData.put("result", "鉴权失败,缺少token参数!");
        }
        if (state == 2) {
            responseData.clear();
            responseData.put("code", 401);
            responseData.put("message", "非法请求");
            responseData.put("result", "token无效,请传入正确的token!");
        }
        if (state == 3) {
            responseData.clear();
            responseData.put("code", 200);
            responseData.put("message", "请求成功");
            responseData.put("result", "token有效,Header添加网关校验token成功!");
        }
        // 将信息转换为 JSON
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] data = new byte[0];
        try {
            data = objectMapper.writeValueAsBytes(responseData);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        // 输出错误信息到页面
        DataBuffer buffer = response.bufferFactory().wrap(data);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return response.writeWith(Mono.just(buffer));
    }
}

使用curl命令进行测试,结果如下所示:

C:\Users\deepinsea>curl -d '123' http://localhost:9070/service-consumer-openfeign/consumer-openfeign/hello?token=
{"result":"鉴权失败,缺少token参数!","code":401,"message":"非法请求"}
C:\Users\deepinsea>curl -d '123' http://localhost:9070/service-consumer-openfeign/consumer-openfeign/hello?token=gate
{"result":"token无效,请传入正确的token!","code":401,"message":"非法请求"}
C:\Users\deepinsea>curl -d '123' http://localhost:9070/service-consumer-openfeign/consumer-openfeign/hello?token=gateway
hi, this is service-provider-api!

控制台日志为:

2022-07-02 02:54:46 [reactor-http-nio-5] INFO  com.deepinsea.common.filter.AuthGlobalFilter - 鉴权失败,缺少token参数!
2022-07-02 02:54:51 [reactor-http-nio-6] INFO  com.deepinsea.common.filter.AuthGlobalFilter - token无效,请传入正确的token!
2022-07-02 02:54:54 [reactor-http-nio-7] INFO  com.deepinsea.common.filter.AuthGlobalFilter - token有效,请求成功!
2022-07-02 02:54:54 [reactor-http-nio-7] INFO  com.deepinsea.common.filter.AuthGlobalFilter - Header添加token成功!
2022-07-02 02:54:54 [reactor-http-nio-7] INFO  com.deepinsea.common.filter.AuthGlobalFilter - [Host:"localhost:9070", User-Agent:"curl/7.79.1", Accept:"*/*", Content-Type:"application/x-www-form-urlencoded", content-length:"5", gateway_token:"2bb062ad-682a-4eab-bd72-11f0b9f54a53"]

测试成功,但是全局Intercepter拦截真实服务地址IP请求效果还未测试,目前只是过滤了网关转发的未携带有效token的请求。

下面将实现一个全局请求拦截器服务(可自定义starter),通过将全局拦截器服务依赖导入子服务,实现所有子服务只允许来自网关代理的请求访问的功能。全局拦截器服务具体实现逻辑是:通过校验请求头中是否有来自网关的Header头(或者cookie/session/token,可进一步升级为安全认证和CAS单点登录架构),如果存在,则通过校验;否则,将拒绝请求访问。

参考:

全局请求过滤方案

但是,上面的配置是拦截经过网关代理的情况(即内外网隔离的情况,所有服务只能通过网关代理与外界访问),那么如何在服务即使不在内外网隔离的环境中还能只接收来自网关的请求呢?

答案就是:配置一个公共模块,添加一个全局过滤器starter,过滤掉所有没有网关标识的请求

这里有两种公共模块项目的创建方式:

  • 直接创建一个公共模块项目,然后以utils工具类的方式编写公共模块功能;
  • 创建一个公共父模块项目,然后在父模块下创建多个依赖于上级父模块的子模块,然后其他项目引入子模块的artifactId来引入公共模块功能;

这两种配置方式各有优缺点:第一种方式只需创建一个项目,功能配置简单,但是会导致所有功能都被其他项目所依赖;第二种方式需要创建多个子模块,功能配置复杂,但是功能可以更精细地配置和依赖并且可以改造成为基础架构部门(进一步可成为服务中台)。综合考虑,为了控制复杂项目的功能细粒度,选择使用第二种父子模块的方式作为公共模块创建方式。

① 创建公共父模块

首先我们需要创建一个公共Maven父模块项目 service-common

image-20220701224337195

创建完成后,删除多余的文件夹,只需要留下一个pom文件用来管理子模块项目的依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- spring-cloud-alibaba-starter父模块 -->
    <parent>
        <artifactId>spring-cloud-alibaba-starter</artifactId>
        <groupId>com.deepinsea</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
​
    <!-- 子模块名 -->
    <artifactId>service-common</artifactId>
​
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
​
    <!-- 父模块依赖管理(本身已经依赖于上级父模块了) -->
    <dependencyManagement>
        <dependencies>
            <!-- web依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.6.3</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

注意:父模块 service-common 也是 spring-cloud-alibaba-starter父模块的子模块,因此,service-common 本身依赖于上级父模块项目。

② 创建公共过滤器子模块

然后在公共父模块 service-common 下,创建一个Maven功能过滤器子模块 common-security-filter

image-20220701230704986

生成了依赖于上级父模块依赖的pom配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-alibaba-starter</artifactId>
        <groupId>com.deepinsea</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
​
    <artifactId>common-security-filter</artifactId>
​
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
​
    <dependencies>
        <!-- servlet 3.0以上版本支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies></project>

添加 filter 文件夹,然后创建全局请求过滤器用于验证上游网关设置的请求头:

package com.deepinsea.filter;
​
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
​
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
​
/**
 * Created by deepinsea on 2022/7/1.
 * 验证网关在请求头添加的信息,如果请求头有这个值,表示已经经过网关认证,可以访问微服务
 */
@Component
public class AccessControlFilter implements Filter {
    /**
     * 网关在请求头添加的信息,如果 请求头 有这个值,表示已经经过网关认证,可以访问微服务
     */
    private static final String GATEWAY_TOKEN = "gateway_token";
    private static final Logger log = LoggerFactory.getLogger(AccessControlFilter.class);
​
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String header = request.getHeader(GATEWAY_TOKEN); //获取上游请求的请求头
        log.info("token: " + header);
        if (!"2bb062ad-682a-4eab-bd72-11f0b9f54a53".equals(header)) { //验证上游请求中请求头的token值
            response.setContentType("application/json;charset=UTF-8");
            response.setCharacterEncoding("UTF-8");
            PrintWriter writer = response.getWriter();
            writer.write("请从网关访问微服务");
            return;
        }else {
            filterChain.doFilter(request, response);
            log.info("token校验正确, 请求正常经过网关!");
        }
    }
}
③ 引入公共模块依赖

service-consumer-openfeign 子模块项目添加公共模块的依赖(相当于自定义starter):

        <!-- 公共安全过滤器子模块 -->
        <dependency>
            <groupId>com.deepinsea</groupId>
            <artifactId>common-security-filter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

回过头去看公共子模块的AccessControlFilter类,由透明灰色转为实质白色,说明有模块引入了这个类(这个类也就开始生效了)。

④ 全局请求过滤功能测试

下面分别启动服务提供者 service-provider-nacosservice-provider-api ,服务消费者 service-consumer-openfeign 以及服务网关 service-gateway 一共四个子模块项目(不用启动 common-security-filter 公共子模块,并且也没启动器),然后使用curl命令进行测试。

因为错误的请求方式(token为空或token值错误),在网关处就给拦截了,因此下游服务只能处理到经过网关后携带正确token的请求。因此,这里分经过网关的请求(token为空、token值错误和正常token)和直接访问后端服务的请求两种情况进行验证。

经过网关的请求

C:\Users\deepinsea>curl -d '123' http://localhost:9070/service-consumer-openfeign/consumer-openfeign/hello?token
{"result":"鉴权失败,缺少token参数!","code":401,"message":"非法请求"}
C:\Users\deepinsea>curl -d '123' http://localhost:9070/service-consumer-openfeign/consumer-openfeign/hello?token=gate
{"result":"token无效,请传入正确的token!","code":401,"message":"非法请求"}
C:\Users\deepinsea>curl -d '123' http://localhost:9070/service-consumer-openfeign/consumer-openfeign/hello?token=gateway
hi, this is service-provider-nacos!

网关控制台输出如下:

2022-07-02 03:07:44 [reactor-http-nio-9] INFO  com.deepinsea.common.filter.AuthGlobalFilter - 鉴权失败,缺少token参数!
2022-07-02 03:07:54 [reactor-http-nio-10] INFO  com.deepinsea.common.filter.AuthGlobalFilter - token无效,请传入正确的token!
2022-07-02 03:07:59 [reactor-http-nio-11] INFO  com.deepinsea.common.filter.AuthGlobalFilter - token有效,请求成功!
2022-07-02 03:07:59 [reactor-http-nio-11] INFO  com.deepinsea.common.filter.AuthGlobalFilter - Header添加token成功!
2022-07-02 03:07:59 [reactor-http-nio-11] INFO  com.deepinsea.common.filter.AuthGlobalFilter - [Host:"localhost:9070", User-Agent:"curl/7.79.1", Accept:"*/*", Content-Type:"application/x-www-form-urlencoded", content-length:"5", gateway_token:"2bb062ad-682a-4eab-bd72-11f0b9f54a53"]

服务调用者控制台输出如下:

2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.deepinsea.filter.AccessControlFilter   : token: 2bb062ad-682a-4eab-bd72-11f0b9f54a53
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : okhttp 发送请求, method: GET, url: http://192.168.174.1:9010/provider-nacos/hello
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : okhttp request body: null
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : okhttp 接收响应, response body: hi, this is service-provider-nacos!
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : <--- Start HTTP Headers
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : Connection: keep-alive
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : Content-Length: 35
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : Content-Type: text/plain;charset=UTF-8
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : Date: Sat Jul 02 03:31:40 CST 2022
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : Keep-Alive: timeout=60
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.d.c.c.i.OkHttpLogInterceptor           : <--- END HTTP Headers
2022-07-02 03:31:40.947  INFO 33004 --- [nio-9020-exec-2] c.deepinsea.filter.AccessControlFilter   : token校验正确, 请求正常经过网关!

因为前面在网关处的token校验失败就被拦截了,因此,只有经过网关代理且token正常的请求才会被消费者接收到,因此网关代理的日志都是正常的日志。但是不包括直接访问真实后端服务的请求。

下面直接请求后端服务的方式来验证全局过滤器效果:

直接访问后端服务的请求

C:\Users\deepinsea>curl -d '123' http://localhost:9020/consumer-openfeign/hello
请从网关访问微服务
C:\Users\deepinsea>curl -d '123' http://localhost:9020/consumer-openfeign/hello?token=gateway
请从网关访问微服务
C:\Users\deepinsea>curl -X POST -H "gateway_token:2bb062ad-682a-4eab-bd72-11f0b9f54a53" -d '{"token":"111"}' http://localhost:9020/consumer-openfeign/hello
hi, this is service-provider-nacos!

上面分别不携带网关的token直接请求真实服务器接口、携带正确的token在查询参数中以及携带网关生成的正确token在header中三种情况,下面是真实后端服务 -- 服务消费者的控制台日志:

2022-07-02 03:42:01.583  INFO 33004 --- [nio-9020-exec-4] c.deepinsea.filter.AccessControlFilter   : token: null
2022-07-02 03:42:31.178  INFO 33004 --- [nio-9020-exec-6] c.deepinsea.filter.AccessControlFilter   : token: null
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.deepinsea.filter.AccessControlFilter   : token: 2bb062ad-682a-4eab-bd72-11f0b9f54a53
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : okhttp 发送请求, method: GET, url: http://192.168.174.1:9010/provider-nacos/hello
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : okhttp request body: null
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : okhttp 接收响应, response body: hi, this is service-provider-nacos!
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : <--- Start HTTP Headers
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : Connection: keep-alive
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : Content-Length: 35
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : Content-Type: text/plain;charset=UTF-8
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : Date: Sat Jul 02 03:42:42 CST 2022
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : Keep-Alive: timeout=60
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.d.c.c.i.OkHttpLogInterceptor           : <--- END HTTP Headers
2022-07-02 03:42:42.059  INFO 33004 --- [nio-9020-exec-8] c.deepinsea.filter.AccessControlFilter   : token校验正确, 请求正常经过网关!

可以看到,成功拦截了所有没有经过网关的错误请求。

只有两种情况可以请求到真实服务器接口:

① 携带能经过网关校验的token在查询参数中的请求;

② 携带网关生成的正确token在header中。

因为正常情况下用户不能获取到网关生成的认证token(除非网关服务器被植入了代理)并且也无法访问到真实服务器(内外网隔离情况下),因此这种双重校验的方式其实是比单层校验更加安全的。但是,现实中一般不会由网关修改token值后做两次token校验,除非是一些隐匿后门接口(即很简单能记住的token值,经过修改后返回正常的token)。

其实这已经是Spring Cloud Security的初级版本了,后面完善用户名和密码换临时token以及异常全局处理处理的逻辑就是完整版本了。

另外用户id和全局时限较长的token(用户token自动续期)放在Redis中,就是Redis+Session/Token实现的CAS单点登录方案了。

同样的,全局异常处理器以及白名单也是在公共子模块中处理的。

欢迎点赞,谢谢大佬了ヾ(◍°∇°◍)ノ゙