统一网关 Gateway【微服务】

文章目录

    • 1. 前言
    • 2. 搭建网关服务
    • 3. 路由断言工厂
    • 4. 路由过滤器
      • 4.1 普通过滤器
      • 4.2 全局过滤器
      • 4.3 过滤器执行顺序
    • 5. 跨域问题处理

1. 前言

通过前面的学习我们知道,通过 Feign 就可以向指定的微服务发起 http 请求,完成远程调用。但是这里有一个问题,我的微服务好像谁来都可以访问,这样会不会不安全?那是肯定的。
实际生产中,有很多的业务模块只有我们内部的人才能用,不是谁都能访问,这时候我们就需要对用户的身份进行验证,而这个验证的工作就是网关(Gateway)来完成的。

一切请求一定要先到网关,再到微服务,其实是对微服务的一种保护措施。

(1)网关功能:

① 身份认证和权限校验(认证通过后,再将请求转发到对应的微服务)
② 服务路由(根据请求信息判断应该将请求转发到哪个微服务)
③ 负载均衡(对外的服务也会有多个实例,需要做负载均衡)
④ 请求限流(限制微服务的用户请求量)

注意 Ribbon 是对内部的 userserver 做负载均衡,而网关是对外部的 orderserver 做负载均衡!

(2)网关的技术实现:

① gataway(新技术,响应式编程,非阻塞式,性能好,吞吐能力强)
② zuul(较早的技术,基于 Servlet,阻塞式,性能差)

2. 搭建网关服务

① 创建新的 module,引入 SpringCloudGateway 的依赖和 Nacos 的服务发现依赖

<dependencies>
    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-gatewayartifactId>
    dependency>
dependencies>

统一网关 Gateway【微服务】_第1张图片

② 创建启动类

统一网关 Gateway【微服务】_第2张图片

统一网关 Gateway【微服务】_第3张图片

③ 编写路由配置及 Nacos 地址,路由配置由三部分组成:路由 id、目标地址和路由断言(路由断言其实就是匹配规则)

路由断言:判断请求是否符合要求,符合则转发到路由目的地。

server:
  port: 10010 #网关端口
spring:
  application:
    name: gateway #服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 #nacos地址
    gateway:
      routes: #网关路由配置
        - id: user-server #路由id,自定义,只要唯一即可
          uri: lb://userserver #路由的目标地址,lb就是负载均衡,后面跟服务名
          predicates: #路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** #只要以/user/开头就符合要求
        - id: order-server
          uri: lb://orderserver
          predicates:
            - Path=/order/**

统一网关 Gateway【微服务】_第4张图片

路由不止一个,用 - 来表示路由数组!

④ 依赖冲突报错

如果你的 spring-boot-starter-web 依赖是写到父工程里的,那么不出意外的话启动 GatewayApplication 时将会报错:

统一网关 Gateway【微服务】_第5张图片
翻译过来就是 SpringMVC 的依赖冲突了,SpringCloudGateway 的内部是通过 netty+webflux 实现的,而 webflux 实现和 SpringMVC 的配置依赖是有冲突的。

解决办法有两种:
第一种就是去除父工程中的 spring-boot-starter-web 这个依赖, 然后在各个子工程需要的地方进行单独引入依赖即可;
第二种就是在 pom.xml 中进行修改,用以下依赖替换原来的 spring cloud gateway。


<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-gatewayartifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        exclusion>
        <exclusion>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webfluxartifactId>
        exclusion>
    exclusions>
dependency>

⑤ 启动项目测试一下,访问 http://localhost:10010/order/2

统一网关 Gateway【微服务】_第6张图片

统一网关 Gateway【微服务】_第7张图片

整体流程:
用户发起 http://localhost:10010/user/2 请求,接着该请求进入网关,而网关是无法处理具体业务的,所以它会基于路由规则去做判断,然后代理到具体的服务上去处理业务。
上面步骤中我们定义了两个路由规则,以 user 开头的路径将会被代理到 userserver 服务,接着网关会拿着服务名(userserver)去找 Nacos 拉取服务列表,然后做一个负载均衡,发送请求 http://localhost:10010/user/2。

网关只是负责检验并转发请求,而最终处理业务的是具体的服务!

3. 路由断言工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被断言工厂读取并处理,最终转变为路由判断的条件。

Spring 提供了 11 种基本的 Predicate 工厂:

统一网关 Gateway【微服务】_第8张图片

Path 是我们用的最多的方式!

测试一下,时间设在 2031 年之后才可以访问,所以现在肯定是不能访问的:

只有当断言规则都满足的时候才能访问成功,路径符合,但时间不符合,照样不能访问。

统一网关 Gateway【微服务】_第9张图片

统一网关 Gateway【微服务】_第10张图片

4. 路由过滤器

4.1 普通过滤器

GatewayFilter 是网关中提供的一种过滤器,可以处理进入网关的请求,以及微服务返回的响应。

路由之后并不是立即向微服务发起请求的,其实我们还可以给路由配置各种各样的过滤器,这些过滤器最终会形成一个过滤器链,对进入网关的请求做各种处理。
请求给了微服务,微服务处理完之后会返回一个结果,这个结果也是先到达网关,网关同样会通过过滤器逐层处理这个响应结果,最终返回给用户。

断言匹配成功相当于请求通过了网关的大门,然后过滤器会对请求做一些处理(比如添加请求头、请求参数等),最后网关会将处理好的请求代理到具体的服务,做业务处理。

统一网关 Gateway【微服务】_第11张图片

Spring 提供了 31 种不同的路由过滤器工厂:

统一网关 Gateway【微服务】_第12张图片

做一个测试,我们可以在 Controller 中获取一下请求头,并在控制台输出:

统一网关 Gateway【微服务】_第13张图片
统一网关 Gateway【微服务】_第14张图片

如果要对所有的路由都生效,则可以将过滤器工厂写到 default-filters 中,与 routes 处于同一级:

统一网关 Gateway【微服务】_第15张图片

4.2 全局过滤器

全局过滤器即自定义过滤器,全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与 GatewayFilter 的作用一样。区别在于 GatewayFilter 通过配置定义,处理逻辑是固定的,而 GlobalFilter 的逻辑需要自己写代码实现, 定义方式是实现 GlobalFilter 接口。

做一个案例,定义全局过滤器拦截请求,判断请求的参数是否满足下面两个条件:
① 参数中是否有 authorization;
② authorization 的参数值是否为 admin。
如果同时满足则放行,否则拦截。

① 创建 AuthorizeFilter 类,实现 GlobalFilter 接口,并重写 filter 方法

统一网关 Gateway【微服务】_第16张图片

统一网关 Gateway【微服务】_第17张图片

第一个参数用于处理业务逻辑,第二个参数是用来放行的,把请求委托给下一个过滤器去处理,其实就是调用下一个过滤器的 filter 方法,等同于放行。

② 编写核心代码

过滤器之间是有先后执行顺序的,这个我们可以通过注解的方式设置,也可以通过实现 Ordered 接口的方式来设置,数值越小,优先级越高!

package com.zxe.gateway;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

//定义过滤器执行的顺序,数值越小优先级越高
//可以通过注解设置,也可以通过实现Ordered接口来设置
//@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
//    exchange请求上下文,里面可以获取Request、Response等信息
//    chain用来把请求委托给下一个过滤器
//    return表示当前过滤器业务结束
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        //2.获取参数中的authorization参数
        String auth = params.getFirst("authorization");
        //3.判断参数是否等于admin
        if ("admin".equals(auth)) {
            //4.等于admin放行,其实是调用下一个过滤器的filter方法,等同于放行
            return chain.filter(exchange);
        }
        //5.不等于就拦截,后面的业务不会再继续,我们一般会设置状态码返回给用户,提示未登录
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

    //定义过滤器执行的顺序,数值越小优先级越高
    @Override
    public int getOrder() {
        return -1;
    }
}

统一网关 Gateway【微服务】_第18张图片

③ 重启项目,我们可以在请求路径中携带 authorization 参数测试一下,无 authorization 参数或参数值不正确的一律被拦截,只有当 authorization = admin 时才会被放行

统一网关 Gateway【微服务】_第19张图片

统一网关 Gateway【微服务】_第20张图片

整体流程:
① 用户请求进入网关,断言匹配失败,报 404 错误,匹配成功直接将请求送入过滤器链;
② 过滤器分为普通过滤器和自定义过滤器,普通过滤器可以在配置文件中设置,自定义过滤器需要实现 GlobalFilter 接口,它可以处理更复杂的过滤业务;
③ 过滤不通过的请求就会被拦截,报401错误,只有层层过滤都通过了,请求才会被路由到具体的服务上,处理具体的业务。

4.3 过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由过滤器、默认过滤器和全局过滤器。

查看当前路由过滤器和默认过滤器的源码知道,它们的过滤器工厂首先会读取配置文件,然后生成一个真正的过滤器 GatewayFilter。再看全局过滤器的源码,可以看到 GlobalFilter 其实是被 GatewayFilterAdapter 适配到 GatewayFilter 的,也就是说网关中所有的过滤器都是 GatewayFilter 类型的。既然是同一种类型,那么就可以把它们放到同一个集合里面去排序。

所以请求路由后,会将当前路由过滤器和默认过滤器、全局过滤器合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。

每个过滤器都必须指定一个 int 类型的 order 值,order 值越小,优先级越高,执行顺序就越靠前。

GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值,过滤器的执行顺序由我们自己指定。而当前路由过滤器和默认过滤器的执行顺序是由 Spring 指定的,默认按照声明顺序从 1 开始递增,这里需要注意的是当前路由过滤器和默认过滤器是分开计数的,这就会出现计数冲突的问题(比如都是 1)。
源码里面的默认过滤器是先加载的,然后再加载当前路由过滤器,最后加载全局过滤器,组织过滤器链。所以,当过滤器的 order 值一样时,会按照 默认过滤器 > 当前路由过滤器 > 全局过滤器的顺序执行。

5. 跨域问题处理

域名不一致就是跨域,主要包括:
① 域名不同:www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
② 域名相同,端口不同。localhost:8080 和 localhost:8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域 ajax 请求,请求被浏览器拦截。

网关处理跨域采用的是 CORS 方案,CORS的底层逻辑网关其实已经帮我们做好了,我们只需要简单的配置即可实现。

spring:
  cloud:
    gateway:
      globalcors: #全局的跨域处理
        add-to-simple-url-handler-mapping: true #解决options请求被拦截的问题
        cors-configurations:
          '[/**]':
            allowedOrigins: #允许哪些网站的跨域请求
              - "http://localhost:8090"
              - "http://www.leyou.com"
            allowedMethods: #允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" #允许在请求中携带的头信息
            allowCredentials: true #是否允许携带cookie
            maxAge: 360000 #这次跨域检测的有效期

你可能感兴趣的:(SpringCloud,gateway,微服务,架构)