SpringBoot整合SpringCloud组件-Gateway网关

一、网关的简单介绍

(一)网关的定义和职能

1、网关的的定义

网关的角色是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问。

API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。

2、网关的的职能

SpringBoot整合SpringCloud组件-Gateway网关

(二)Gateway的介绍

1、什么是Gateway

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

2、为什么要用Gateway

Gateway是用来替代Zuul网关的,其明确的区分了Router和Filter,并且内置了许多功能都可以通过配置就行直接使用。
其内置了多种Router,可以在配置文件中根据Header、Path、Host等方式来做路由,我后面只介绍通过path的方式进行路由,至于其他方式可以自行查看网上资料。
其也内置了一般Filter和全局Filter,我们也可以自定义Filter进行业务处理,后面将介绍通过内置的Filter对请求path进行处理以及通过自定义Filter对token进行校验。

SpringBoot整合SpringCloud组件-Gateway网关

3、概念

SpringBoot整合SpringCloud组件-Gateway网关

二、基础路由规则的配置使用

(一)项目说明

用gateway作为项目的整体网关,所有请求都先打到gateway上,经过安全、权限过滤以后通过的请求再根据路由规则从nacos上获取服务名然后转发到对应的服务上。

gateway网关项目整体就是一个springboot项目,在项目里面做路由配置以及权限过滤等操作,该项目整体结构如下:
SpringBoot整合SpringCloud组件-Gateway网关

(二)依赖的导入

由于是微服务的,所以需要导入springcloud和springCloudAlibaba的依赖,至于2者与springboot版本之间的应该选择哪个版本可以参考我之前的文章:Spring,SpringCloud,SpringCloudAlibaba各版本对应关系
SpringBoot整合SpringCloud组件-Gateway网关
SpringBoot整合SpringCloud组件-Gateway网关
由于要用nacos做为注册中心获取各服务的真实地址,所以还需要导入nacos
SpringBoot整合SpringCloud组件-Gateway网关

当然gateway本身的依赖也是必不可少的
SpringBoot整合SpringCloud组件-Gateway网关

(三)配置文件说明

server:
  port: 7000
#
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      globalcors:
        # gateway 跨域设置
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowCredentials: true
            allowedMethods:
              - GET
              - POST
              - PUT
              - OPTIONS
      discovery:
        locator:
          ####开启以服务id去注册中心上获取转发地址
          enabled: true
        ###路由策略
      routes:
        ##路由策略id
        - id: cloud-admin
          #### 基于lb负载均衡形式转发(通过注册中心服务名称,获取真实访问地址)
          uri: lb://cloud-admin
          ###匹配规则
          predicates:
            - Path=/cloud-admin/**
          filters:
            # 去除的路径前缀的个数(strip)
            - StripPrefix=1
    nacos:  #注册中心地址
      discovery:
        server-addr: 10.10.83.73:8848

配置文件解释:
跨域和nacos注册中心的配置就不说了,主要细说路由策略
1)上面配置了一个路由规则id为cloud-admin,当请求的路径为/cloud-admin/**时将请求通过负载均衡转发到服务名为cloud-admin的服务上,从nacos上根据服务名获取服务真是地址进行转发。
2)至于配置文件中的StripPrefix=1代表将请求的前缀去除一个,例如我的请求路径是/cloud-admin/user/getUserById,该请求被转发至cloud-admin服务上时请求路径就变成了/user/getUserById。

为什么要去除前缀,直接用/user/getUserById不就行了吗?
前端服务可能会调取多个后端服务的方法,如果不在路径上做区分,等所有请求都到gateway时就没法区分调用哪个服务。

注:路由策略有很多,我只是用来path来做路由,真正用的时候可能多个Router共同组合来做策略。

三、全局token校验处理

gateway不仅能做路由转发,还能对所有的请求进行权限校验、过滤。我这个项目中需要在gateway中校验是否携带token,token校验通过以后把提取到的用户id再传到后面的服务中。
我在项目中自定义了一个token然后实现全局的filter,这样所有的请求都得进行token校验,代码如下:

package com.cloud.cloudgateway.filter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
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.HttpHeaders;
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.List;

/**
 * @Description: 自定义全局token校验
 * @Author: yl
 * @Date: 2021年10月9日15:42:31
 */
@Component
public class GlobalTokenFilter  implements GlobalFilter, Ordered {

    private static final String PATH = "/login";
    public static final String SECRET = "develop-Platform";            // JWT密钥
    public static final String TOKEN_KEY = "Authorization";// 存放Token的Header Key

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 获取请求路径
        String path = request.getPath().toString();
        //登录请求直接到认证服务
        if(path.contains(PATH)){
            return chain.filter(exchange);
        }

        //验证token
        HttpHeaders headers = request.getHeaders();
        List<String> headerList = headers.get(TOKEN_KEY);
        if(headerList.isEmpty()){
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            String msg = "无token,请重新登录";
            DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
            return response.writeWith(Mono.just(buffer));
        }

        String token = headerList.get(0);
        String userId;
        try {
            userId = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            throw new RuntimeException("401");
        }

        // 验证 token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        try {
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            throw new RuntimeException("401");
        }

        ServerHttpRequest host = request.mutate().header("userId",userId).header("userToken",token).build();

        //将request变成change对象
        ServerWebExchange build = exchange.mutate().request(host).build();
        return chain.filter(build);
    }

    //实现Ordered接口保证优先级,值越小加载的优先级越高
    @Override
    public int getOrder() {
        return -1;
    }
}

上一篇:Mybatis源码执行的浅读


下一篇:什么是函数式接口?自定义一个函数式接口