spring cloud gateway网关

API网关简介

在使用微服务架构之后,系统中会有很多微服务,有些微服务需要用户满足特定的权限才可以访问,比如查看购买历史记录,查看优惠信息等等,这需要用户登录之后才能看到,倘若在每个微服务中都编写处理用户登录代码的话,势必会出现冗余,我们可以将这些代码放到API网关中,统一处理。其实API网关就是指定了程序的统一入口,我们可以将一些业务无关的公共部分代码放到这里,比如认证,鉴权,路由,监控等。

市面上比较知名的网关技术:

  • Spring Cloud Gateway
    Spring公司为了替换Zuul而开发的网关服务,spring cloud alibaba目前没有提供网关,我们可以使用这个。
  • Ngnix
    使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用。
  • Kong
    性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
  • ZuulNetflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx

Spring Cloud Gateway简介

早期spring cloud版本中集成了zuul作为网关,但由于zuul 1.x的性能问题和zuul 2.x不断延期,于是spring自己开发了网关spring cloud gateway,该技术基于spring5,spring boot2.0版本开发,性能方面比zuul1.x好一些。gateway底层使用了netty,所以需要netty环境来运行,不能使用我们所熟悉的tomcat等servlet容器来运行。

Spring Cloud Gateway的路由

路由(route)的作用是将请求的url分配到相应的程序上,这里我们使用路由就是将请求分配到相应的微服务中。在spring cloud gateway的路由route中包含下面内容:

  • id:路由的id,保证唯一性。
  • uri:路由将请求分配的 uri,即客户端请求最终被转发的微服务。
  • order:用于多个路由之间的排序,数值越小优先级越高。
  • predicate:如果请求地址匹配当前规则的话,则会转发到uri指定的微服务上。
  • filter:过滤器用于修改请求和响应信息。

接下来使用spring cloud gateway实现一个简单的路由功能。

1.创建新的模块并添加相关依赖,注意不要添加web依赖(里面包含了tomcat)。

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2020.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2021.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

2.application启动类上添加@EnableDiscoveryClient开启服务发现

@SpringBootApplication
@EnableDiscoveryClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.application.yml中添加网关相关配置

server:
  port: 80
spring:
  cloud:
    application:
      name: gateway-service
    gateway:
      nacos:
        discovery:
          server-addr: 127.0.0.1:8848 #nacos注册中心地址
      discovery:
        locator:
          enabled: true # 开启后,gateway可以发现nacos中的服务
      routes: # 路由数组,指定当请求跳转到相应的微服务
        - id: buy-route # 当前路由的id,保证唯一性
          uri: lb://buy-service # lb指的是负载均衡,buy-service是nacos中的微服务名字
          order: 1 #路由的优先级,数字越小级别越高
          predicates:
            - Path=/buy-path/** # 当url匹配/buy-path时,进行路由转发到上面uri配置的微服务中
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            - StripPrefix=1 # 转发之前去掉1层路径

上面倘若不配置filters的话,当我们发出下面请求时http://localhost/buy-path/buy,gateway会根据配置访问http://localhost:8086/buy-path/buy,这里多了一级目录buy-path,配置上面的filters之后,gateway会取消掉这级目录。

4.启动各个相关微服务,浏览器中访问http://localhost/buy-path/buy即可看到效果。

gateway工作方式

客户端gateway client向Spring Cloud Gateway发出请求,handler mapping会对url与设定的谓词进行匹配,失败会返回404,成功则将请求发送到Web handler进行处理,然后会调用一系列的filter过滤器,最终请求到达proxied service(即微服务)。

路由断言工厂

在spring cloud gateway中内置了很多路由断言工厂,这些断言工厂跟HTTP请求参数做匹配,我们使用这些断言工厂可以非常方便的对当前请求进行限制。

  • datetime断言工厂根据日期进行判断,分为after(表示判断请求日期是否晚于指定日期),before(判断请求日期是否早于指定日期),between(接收两个日期参数,判断请求日期是否在指定时间段内)。分别对应的类是AfterRoutePredicateFactoryBeforeRoutePredicateFactoryBetweenRoutePredicateFactory下面是after的设置,当请求的时间晚于设置的时间才可以访问到uri中的服务。在项目上线新的功能时可以使用after设置。
    spring:
      cloud:
        gateway:
          routes:
            - id: after_route
              uri: http://www.monkey1024.com
              predicates:
                - After=2022-02-01T17:42:47.789+08:00[Asia/Shanghai]
    
  • cookie断言工厂对应的类是CookieRoutePredicateFactory,接收两个参数,判断请求中是否包含某个cookie且值是否匹配正则表达式。下面配置表示请求中的的cookie名字必须要有username并且其值需要是monkey1024
    spring:
      cloud:
        gateway:
          routes:
            - id: cookie_route
              uri: http://www.monkey1024.com
              predicates:
                - Cookie=username, monkey1024
    
  • header断言工厂对应的类是HeaderRoutePredicateFactory,接收两个参数,判断请求中是否包含某个cookie且值是否匹配正则表达式。下面配置表示请求头中包含X-Request-Id并且值是一个正整数。
    spring:
      cloud:
        gateway:
          routes:
            - id: header_route
              uri: https://monkey1024.com
              predicates:
                - Header=X-Request-Id, \d+
    
  • host断言工厂对应的类是HostRoutePredicateFactory,判断请求的host是否满足条件。
    spring:
      cloud:
        gateway:
          routes:
            - id: host_route
              uri: https://monkey1024.com
              predicates:
                - Host=**.taobao.com,**.qq.com
    
  • method断言工厂对应的类是MethodRoutePredicateFactory,判断请求的方式是否满足条件。下面表示请求方式必须是get或者post。
    spring:
      cloud:
        gateway:
          routes:
            - id: method_route
              uri: https://monkey1024.com
              predicates:
                - Method=GET,POST
    
  • path断言工厂对应的类是PathRoutePredicateFactory,判断请求的路径是否满足条件。
    spring:
      cloud:
        gateway:
          routes:
            - id: path_route
              uri: https://monkey1024
              predicates:
                - Path=/user/{segment},/product/{segment}
    

    上面表示请求路径是user/jack或者product/car,才会匹配,其中jack和car可以是其他值。通过下面程序可以获取到:

    Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);
    
    String segment = uriVariables.get("segment");
    
  • query断言工厂对用的类是QueryRoutePredicateFactory,接收两个参数,判断请求的参数名字和值是否匹配。下面表示请求的参数需要有red,并且值要匹配gree.的正则,比如green,greet都满足。
    spring:
      cloud:
        gateway:
          routes:
            - id: query_route
              uri: https://monkey1024.com
              predicates:
                - Query=red, gree.
    
  • remoteAddr断言工厂对应的类是RemoteAddrRoutePredicateFactory,判断请求主机的ip地址是否满足指定的ip段。比如当请求主机的IP是192.168.1.10的时候是满足的。
    spring:
      cloud:
        gateway:
          routes:
            - id: remoteaddr_route
              uri: https://monkey1024.com
              predicates:
                - RemoteAddr=192.168.1.1/24
    
  • weight断言工厂对应的类是WeightRoutePredicateFactory,接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。下面设置表示倘若有10个请求,8个请求会转发到weighthigh上面,2个请求会转发到weightlow上面
    spring:
      cloud:
        gateway:
          routes:
            - id: weight_high
              uri: https://weighthigh.org
              predicates:
                - Weight=group1, 8
            - id: weight_low
              uri: https://weightlow.org
              predicates:
                - Weight=group1, 2
    

自定义断言工厂

当spring gateway内置的自定义工厂不能满足我们的需求时,可以自定义断言工厂,这里定义一个只允许id值在[1,100]范围内才能访问的断言工厂。

1.修改配置文件,在predicates下添加Id=1,100,注意首字母大写

server:
  port: 8000
spring:
  cloud:
    application:
      name: gateway-service
    gateway:
      nacos:
        discovery:
          server-addr: 127.0.0.1:8848 
      discovery:
        locator:
          enabled: true
      routes: 
        - id: buy-route 
          uri: lb://buy-service
          order: 1
          predicates:
            - Path=/buy-path/** 
            - Id=1,100 #限制id在1~100之间的可以访问
          filters: 
            - StripPrefix=1 

2.创建AbstractRoutePredicateFactory的子类,里面定义一个静态内部类Config,该类的作用是读取配置文件中的数据。重写的apply方法中的return里面编写断言规则,如果返回true则表示可以通过,false表示不允许通过。

package com.monkey1024.predicate;

import lombok.Data;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

@Component
public class IdRoutePredicateFactory extends AbstractRoutePredicateFactory<IdRoutePredicateFactory.Config> {

    public IdRoutePredicateFactory() {
        super(IdRoutePredicateFactory.Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(IdRoutePredicateFactory.Config consumer) {
        
        return serverWebExchange -> {
            //获取当前请求的参数id
            String id = serverWebExchange.getRequest().getQueryParams().getFirst("id");
			//这里返回true表示匹配,返回false表示不匹配
            //判断参数id是否满足条件
            if (Objects.nonNull(id)) {
                int i = Integer.parseInt(id);
                return i > consumer.min && i < consumer.max;
            }
            return true;
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        //方法参数中的顺序要于配置文件中的一致
        return Arrays.asList("min", "max");
    }

    //结合上面的shortcutFieldOrder方法,会将配置文件的数据读取进来
    @Data
    public static class Config {
        private int min;
        private int max;
    }
}

过滤器

spring cloud gateway提供了过滤器,这个跟servlet中的过滤器或者spring mvc中的拦截器比较像,gateway中使用pre对请求进行处理,post对响应进行处理。这里过滤器分为了如下两种:

  • 局部过滤器(GatewayFilter),局部过滤器可以对某个路由进行过滤。
  • 全局过滤器(GlobalFilter),全局过滤器可以对全部路由进行过滤。
局部过滤器

在gateway官网中可以看到内置了很多局部过滤器,使用方式跟断言类似,这里我们以RedirectTo为例,将请求重定向。相关配置

server:
  port: 8000
spring:
  cloud:
    application:
      name: gateway-service
    gateway:
      nacos:
        discovery:
          server-addr: 127.0.0.1:8848
      discovery:
        locator:
          enabled: true
      routes:
        - id: buy-route
          uri: lb://buy-service
          order: 1
          predicates:
            - Path=/buy-path/**
          filters:
            - StripPrefix=1 
            - RedirectTo=302,http://www.monkey1024.com #设置状态码和重定向的地址
自定义局部过滤器

当内置的局部过滤器不能满足我们的需求时,可以自定义局部过滤器。在配置文件中filters下添加配置

server:
  port: 8000
spring:
  cloud:
    application:
      name: gateway-service
    gateway:
      nacos:
        discovery:
          server-addr: 127.0.0.1:8848
      discovery:
        locator:
          enabled: true
      routes:
        - id: buy-route
          uri: lb://buy-service
          order: 1
          predicates:
            - Path=/buy-path/**
          filters:
            - StripPrefix=1
			- Flag=true

编写自定义局部过滤器工厂,方式跟之前的自定义断言工厂类似

package com.monkey1024.predicate;

import lombok.Data;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/*
    定义局部过滤器
 */
@Component
public class FlagGatewayFilterFactory extends AbstractGatewayFilterFactory<FlagGatewayFilterFactory.Config> {

    public static final String FLAG_KEY = "flag";

    public FlagGatewayFilterFactory() {
        super(FlagGatewayFilterFactory.Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(FLAG_KEY);
    }

    //过滤器中的内容
    @Override
    public GatewayFilter apply(FlagGatewayFilterFactory.Config consumer) {
        return (exchange, chain) -> {
            if (consumer.flag) {
                System.out.println("flag是true");
            } else {
                System.out.println("flag是false");
            }
            return chain.filter(exchange);
        };
    }

    //获取配置文件中的数据
    @Data
    public static class Config {
        private boolean flag;
    }
}
全局过滤器

spring cloud gateway中还提供了一些全局过滤器,具体可以查看其官方文档。我们之前在配置文件中uri部分写的lb就使用了全局过滤器LoadBalancerClientFilter。下面我们来编写一个自定义的全局过滤器,该过滤器的作用是判断请求中是否携带token。

package com.monkey1024.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Objects;

/*
    全局过滤器
 */
@Component
public class TokenGlobeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (Objects.isNull(token)) {
            System.out.println("没有token,不予放行");
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    //过滤器的优先级,数字越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

网关限流

使用网关之后,它就成为了整个系统的入口,因此我们可以在网关层面进行限流。这里使用之前学过的sentinel集成gateway进行限流。sentinel提供了两种维度的限流:

  • route维度:针对每个配置文件中的route进行限流
  • API维度:针对一组route进行限流,可以很好的复用限流规则

下面的操作是将限流规则放入到内存中,若要持久化,方式与sentinel中的持久化操作一致。

route维度

添加依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>

配置文件添加sentinel dashboard

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080

创建网关配置类

package com.monkey1024.config;

import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

@Configuration
public class GatewayRouteConfiguration {

    // 初始化一个限流的过滤器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

}

打开sentinel控制台,里面配置限流规则即可。

可以在上面的类中添加方法来设置限流后返回的内容。

@Configuration
public class GatewayRouteConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public GatewayRouteConfiguration(ObjectProvider<List<ViewResolver>>
                                        viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers =
                viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // 配置限流的异常处理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler
    sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers,
                serverCodecConfigurer);
    }
    // 自定义限流异常页面
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
            Map map = new HashMap<>();
            map.put("code", 0);
            map.put("message", "你的刷新过快,请稍后再试");
            return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).
                    body(BodyInserters.fromValue(map));
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

    // 初始化一个限流的过滤器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

}

我们还可以在上面的类中设置初始的限流,在网关启动后这些设置就会生效,添加下面方法设置

// 配置初始化的限流参数
@PostConstruct
public void initGatewayRules() {
    Set<GatewayFlowRule> rules = new HashSet<>();
    rules.add(
        new GatewayFlowRule("buy-route") //配置文件中的路由id
        .setCount(1) // 限流阈值
        .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
    );
    GatewayRuleManager.loadRules(rules);
}

API维度

api维度需要在代码中定义路由谓词集合和分组,将集合放入对应的分组中,然后再对分组设置限流规则,这样就可以将一个限流规则使用到多个路由上。

创建类编写下面内容,然后再启动路由即可看到限流效果:

@Configuration
public class GatewayApiConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayApiConfiguration(ObjectProvider<List<ViewResolver>>
                                           viewResolversProvider,
                                   ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers =
                viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // 初始化一个限流的过滤器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    // 配置初始化的限流参数
    @PostConstruct
    public void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        //加入api组
        rules.add(new
                GatewayFlowRule("buy-path").setCount(1).setIntervalSec(1));
        GatewayRuleManager.loadRules(rules);
    }

    //自定义API分组
    @PostConstruct
    private void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();

        //定义谓词set,将要限流的路径传入
        Set<ApiPredicateItem> buyPathItem = new HashSet<ApiPredicateItem>() {{
            add(new ApiPathPredicateItem().setPattern("/buy-path/buy/**").
                    setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
        }};

        //定义一个分组名为buy-path,将上面的路由谓词set传入
        ApiDefinition api = new ApiDefinition("buy-path")
                .setPredicateItems(buyPathItem);
        definitions.add(api);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

    // 配置限流的异常处理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler
    sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers,
                serverCodecConfigurer);
    }

    // 自定义限流异常页面
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
            Map map = new HashMap<>();
            map.put("code", 0);
            map.put("message", "你的刷新过快,请稍后再试");
            return ServerResponse.status(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).
                    body(BodyInserters.fromValue(map));
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}

跨域问题

如果 访问的host,协议,端口都相同,则称之为同源(不跨域),否则为非同源(跨域)。比如有一个页面地址为:

http://www.monkey1024.com/content/way.html

该页面中的js以ajax或axios等方式访问下面地址是否跨域:

url 是否跨域
https://www.monkey1024.com/content/user 是,协议不同,http和https
http://www.monkey1024.com:8080/content/user 是,端口号不同
http://www.monkey.com/content/user 是,host不同
http://www.monkey1024.com/content/user

之前经常会在自己写的html中引入其他域名下的js,css,图片等,例如:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>hello</title>
</head>

<body>
    <div id="app">
        <!--引入外部图片-->
        <img src="http://monkey1024.com/images/logo.png"/>
    </div>
    <!--引入外部js-->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</body>

</html>

这种写法并未出现跨域问题,原因是浏览器是支持跨域的,但是在JavaScript代码中是不能进行跨域请求,示例如下,vue通过axios发送一个跨域请求,axios的请求地址是之前微服务的网关,双击html打开(确保跟之前的网关满足跨域条件),点击购买按钮后,浏览器f12可以看到跨域请求错误:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>hello</title>
</head>

<body>
    <div id="app">
        <button @click="buy">购买</button>
        <div v-text="message"></div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                message: ''
            },
            methods: {
                buy: function () {
                    let that = this
                    axios.get("http://localhost/buy-path/buy").then(function (response) {
                        console.log(response)
                        that.message = response.data
                    }, function (error) {
                    })
                }
            }
        });
    </script>
</body>
</html>

我们可以在网关中编写下面代码来解决跨域问题

package com.monkey1024.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

/*
    解决跨域问题
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        //*表示所有
        config.addAllowedMethod("*");//允许的请求方法,get、post、put、delete等
        config.addAllowedOrigin("*");//允许哪个域名的请求
        config.addAllowedHeader("*");//允许的请求头
        UrlBasedCorsConfigurationSource source = new
                UrlBasedCorsConfigurationSource(new PathPatternParser());
        /*
            允许哪个请求进行跨域,如果只允许/buy-path下的请求跨域,则
            source.registerCorsConfiguration("/buy-path/*", config);
         */
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}