搭建gateway网关

1.创建springBoot项目

可以将Server URL换成start.aliyun.com 

 

 

 2.配置路由与跨域处理

路由:

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

跨域处理:

 

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

3.自定义过滤器

package cn.itcast.gateway.filters;
 
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
 
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // 2.获取authorization参数
        String auth = params.getFirst("authorization");
        // 3.校验
        if ("admin".equals(auth)) {
            // 放行
            return chain.filter(exchange);
        }
        // 4.拦截
        // 4.1.禁止访问,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        // 4.2.结束处理
        return exchange.getResponse().setComplete();
    }
}

过滤器的执行顺序:

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

排序的规则是什么呢?

每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

 

4.实现定义过滤器中的业务逻辑

//首先配置一个全局异常处理器来处理异常

梳理业务逻辑

@Order(-1)
@Component
@Slf4j
public class AynuFilter implements GlobalFilter {

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.用户发送请求到api网关
        log.info("进入网关过滤器");
        //2.配置请求日志
        
        //3.黑白名单
        //4.用户鉴权
        //5.请求的模拟接口是否存在
        //6.请求转发,调用模拟接口
        //7.响应日志
        //8.调用成功,接口调用次数+1
        //9.调用失败,返回一个规范的错误码

   
        return exchange.getResponse().setComplete();
    }
}

 配置请求日志

exchange(路由交换机):我们所有的请求的信息、响应的信息、响应体、请求体都能从这里拿到。

chain(责任链模式):因为我们的所有过滤器是按照从上到下的顺序依次执行,形成了一个链条。所以这里用了一个 chain ,如果当前过滤器对请求进行了过滤后发现可以放行,就要调用责任链中的 next 方法,相当于直接找到下一个过滤器,这里称为 filter 。有时候我们需要在责任链中使用 next,而在这里它使用了filter 来找到下一个过滤器,从而正常地放行请求。

@Order(-1)
@Component
@Slf4j
public class AynuFilter implements GlobalFilter {

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.用户发送请求到api网关
        log.info("进入网关过滤器");
        //2.配置请求日志

        //3.黑白名单
        //4.用户鉴权
        //5.请求的模拟接口是否存在
        //6.请求转发,调用模拟接口
        //7.响应日志
        //8.调用成功,接口调用次数+1
        //9.调用失败,返回一个规范的错误码


        return chain.filter(exchange);
    }
}

 使用exchange获取request,并输出日志

@Order(-1)
@Component
@Slf4j
public class AynuFilter implements GlobalFilter {

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.用户发送请求到api网关
        log.info("进入网关过滤器");
        //2.配置请求日志
        ServerHttpRequest request = exchange.getRequest();
        log.info("请求唯一标识:{}",request.getId());
        log.info("请求路径:{}",request.getPath().value());
        log.info("请求方法:{}",request.getMethod());
        log.info("请求参数:{}",request.getQueryParams());
        log.info("请求来源地址:{}",request.getRemoteAddress());
        String hostString = request.getLocalAddress().getHostName();
        log.info("请求来源地址:{}",hostString);
        //3.黑白名单
        //4.用户鉴权
        //5.请求的模拟接口是否存在
        //6.请求转发,调用模拟接口
        //7.响应日志
        //8.调用成功,接口调用次数+1
        //9.调用失败,返回一个规范的错误码


        return chain.filter(exchange);
    }
}

 配置白名单

通常情况下,G经常使用的是封禁IP。例如,如果某个远程地址频繁访问,我们可以将其添加到黑名单并拒绝访问。现在我们来试试设置一个规则,如果请求的来源地址不是 127.0.0.1,就拒绝它的访问。先写一个全局的常量。在这里我们用一个白名单,通常建议在权限管理中尽量使用白名单,少用黑名单。白名单的原则是只允许特定的调用,这样可能会更加安全,或者你可以默认情况下全禁止。

@Order(-1)
@Component
@Slf4j
public class AynuFilter implements GlobalFilter {

    private static  final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1");

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.用户发送请求到api网关
        log.info("进入网关过滤器");
        //2.配置请求日志
        ServerHttpRequest request = exchange.getRequest();
        log.info("请求唯一标识:{}",request.getId());
        log.info("请求路径:{}",request.getPath().value());
        log.info("请求方法:{}",request.getMethod());
        log.info("请求参数:{}",request.getQueryParams());
        log.info("请求来源地址:{}",request.getRemoteAddress());
        String hostString = request.getLocalAddress().getHostName();
        log.info("请求来源地址:{}",hostString);
        //3.黑白名单
        //获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        if (!IP_WHITE_LIST.contains(hostString)){
            //设置响应状态码为403(禁止访问)
            response.setStatusCode(HttpStatus.FORBIDDEN);
        }
        //4.用户鉴权
        //5.请求的模拟接口是否存在
        //6.请求转发,调用模拟接口
        //7.响应日志
        //8.调用成功,接口调用次数+1
        //9.调用失败,返回一个规范的错误码


        return chain.filter(exchange);
    }
}

 用户鉴权

这里举个例子,具体实现请参考自己的业务需求

@Order(-1)
@Component
@Slf4j
public class AynuFilter implements GlobalFilter {

    private static  final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1");
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    UserMapper userMapper;
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.用户发送请求到api网关
        log.info("进入网关过滤器");
        //2.配置请求日志
        ServerHttpRequest request = exchange.getRequest();
        log.info("请求唯一标识:{}",request.getId());
        log.info("请求路径:{}",request.getPath().value());
        log.info("请求方法:{}",request.getMethod());
        log.info("请求参数:{}",request.getQueryParams());
        log.info("请求来源地址:{}",request.getRemoteAddress());
        String hostString = request.getLocalAddress().getHostString();
        log.info("请求来源地址:{}",hostString);
        //3.黑白名单
        //获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        if (!IP_WHITE_LIST.contains(hostString)){
            //设置响应状态码为403(禁止访问)
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }
        //4.用户鉴权
        HttpHeaders headers = request.getHeaders();
        //调用者传过来的参数
        String accessKey = headers.getFirst("accessKey");
        String body = headers.getFirst("body");
        String timestamp = headers.getFirst("timestamp");
        String random = headers.getFirst("random");
        String sign = headers.getFirst("sign");
        //验证随机数,使用redis存储,以sign为键名
        //操作字符串数据对象
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //get
        String randomDB = (String) valueOperations.get(sign);
        if (randomDB==null){
            //setex    TimeUnit是一个枚举类,里面列举了时间单位
            valueOperations.set(sign,random,120, TimeUnit.HOURS);
        }else {
            if (!randomDB.equals(random)){
                throw new RuntimeException("无权限");
            }
        }
        //调用mapper校验key
        User userDB = userMapper.getUserByAccessKey(accessKey);
        if (userDB==null){
            throw new RuntimeException("无权限");
        }
        //时间戳验证,时间戳不能和当前时间超过5分钟
        if (TimeUtils.checkTimesTamp(timestamp)){
            throw new RuntimeException("无权限");
        }
        //秘钥验证,使用传过来的数据生成sign,查询与用户的sign是否一致
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("accessKey",accessKey);
        hashMap.put("body",body);
        hashMap.put("random", random);
        hashMap.put("timestamp",timestamp);
        String newSign = SignUtils.getSign(hashMap, userDB.getSecretKey());
        if (!newSign.equals(sign)){
            throw new RuntimeException("无权限");
        }
        //5.请求的模拟接口是否存在
        //todo
        //6.请求转发,调用模拟接口

        //7.响应日志
        //8.调用成功,接口调用次数+1
        //9.调用失败,返回一个规范的错误码


        return chain.filter(exchange);
    }
}

 自定义响应处理

问题:
预期是等模拟接口调用完成,才记录响应日志、统计调用次数。

但现实是 chain.filter 方法立刻返回了,直到 filter 过滤器全部 return 后才调用了模拟接口。

原因是:chain.filter 是个异步操作。

解决方案:利用 response 装饰者,增强原有response 的处理能力

@Component
@Slf4j
public class AynuFilter implements GlobalFilter ,Ordered{

    private static  final List<String> IP_WHITE_LIST = Arrays.asList("127.0.0.1");
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    UserMapper userMapper;
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.用户发送请求到api网关
        log.info("进入网关过滤器");
        //2.配置请求日志
        ServerHttpRequest request = exchange.getRequest();
        log.info("请求唯一标识:{}",request.getId());
        log.info("请求路径:{}",request.getPath().value());
        log.info("请求方法:{}",request.getMethod());
        log.info("请求参数:{}",request.getQueryParams());
        log.info("请求来源地址:{}",request.getRemoteAddress());
        String hostString = request.getLocalAddress().getHostString();
        log.info("请求来源地址:{}",hostString);
        //3.黑白名单
        //获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        if (!IP_WHITE_LIST.contains(hostString)){
            //设置响应状态码为403(禁止访问)
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }
        //4.用户鉴权
        HttpHeaders headers = request.getHeaders();
        //调用者传过来的参数
        String accessKey = headers.getFirst("accessKey");
        String body = headers.getFirst("body");
        String timestamp = headers.getFirst("timestamp");
        String random = headers.getFirst("random");
        String sign = headers.getFirst("sign");
        //验证随机数,使用redis存储,以sign为键名
        //操作字符串数据对象
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //get
        String randomDB = (String) valueOperations.get(sign);
        if (randomDB==null){
            //setex    TimeUnit是一个枚举类,里面列举了时间单位
            valueOperations.set(sign,random,120, TimeUnit.HOURS);
        }else {
            if (!randomDB.equals(random)){
                throw new RuntimeException("无权限");
            }
        }
        //调用mapper校验key
        User userDB = userMapper.getUserByAccessKey(accessKey);
        if (userDB==null){
            throw new RuntimeException("无权限");
        }
        //时间戳验证,时间戳不能和当前时间超过5分钟
        if (TimeUtils.checkTimesTamp(timestamp)){
            throw new RuntimeException("无权限");
        }
        //秘钥验证,使用传过来的数据生成sign,查询与用户的sign是否一致
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("accessKey",accessKey);
        hashMap.put("body",body);
        hashMap.put("random", random);
        hashMap.put("timestamp",timestamp);
        String newSign = SignUtils.getSign(hashMap, userDB.getSecretKey());
        if (!newSign.equals(sign)){
            throw new RuntimeException("无权限");
        }
        //5.请求的模拟接口是否存在
        //todo
        return  handleResponse(exchange,chain);

    }


    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode != HttpStatus.OK) {
                return chain.filter(exchange);//降级处理返回数据
            }
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    if (body instanceof Flux) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);

                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            //添加调用接口后的处理逻辑
                            //8.调用成功,接口调用次数+1
                            // 合并多个流集合,解决返回体分段传输
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer buff = dataBufferFactory.join(dataBuffers);
                            byte[] content = new byte[buff.readableByteCount()];
                            buff.read(content);
                            DataBufferUtils.release(buff);//释放掉内存
                            //构建日志
                            StringBuilder stringBuilder = new StringBuilder(200);
                            ArrayList<Object> arrayList = new ArrayList<>();
                            arrayList.add(originalResponse.getStatusCode());
                            String s = new String(content, StandardCharsets.UTF_8);
                            stringBuilder.append(s);

                            log.info("响应结果:{}", arrayList.toArray());
                            return bufferFactory.wrap(content);
                        }));
                    } else {
                        log.error("<-- {} 响应code异常", getStatusCode());
                    }
                    return super.writeWith(body);
                }
            };
            return chain.filter(exchange.mutate().response(decoratedResponse).build());


        } catch (Exception e) {
            log.error("gateway log exception.\n" + e);
            return chain.filter(exchange);
        }
    }
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/675397.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java的冷知识你知道吗?

1、方法参数不能超过255个 在Java中&#xff0c;方法的参数数量是有限制的&#xff0c;最多不能超过255个。这个知识点可能对于大多数程序员来说并不常用&#xff0c;因此即使是经验丰富的Java开发者也可能不清楚这一点。2、Java中的自动装箱与拆箱 自动装箱是Java 5引入的新特…

站点被篡改快照被劫持解决服务方法教程_一招制敌

站点被篡改快照被劫持解决服务方法教程_一招制敌 被篡改表现形式&#xff1a; 站点打不开或跳转到别的网站。 攻击者目的&#xff1a; 报复、勒索、卖防御产品&#xff08;如DDOS防御产品&#xff09;。 攻击成本&#xff1a; 工具&#xff08;如VPN购买&#xff09;成本、人…

当新手小白有了一块【香橙派OrangePi AIpro】.Demo

当新手小白有了一块【香橙派OrangePi AIpro】.Demo 文章目录 当新手小白有了一块【香橙派OrangePi AIpro】.Demo一、香橙派OrangePi AIpro概述1.简介2.引脚图 二、“点亮”香橙派OrangePi AIpro1.官方工具下载2.官方镜像下载3.镜像烧录4.访问香橙派 AIpro 三、香橙派OrangePi A…

数据结构第三篇【链表的相关知识点一及在线OJ习题】

数据结构第三篇【链表的相关知识点一及在线OJ习题】 链表链表的实现链表OJ习题顺序表和链表的区别和联系 本文章主要讲解关于链表的相关知识&#xff0c;喜欢的可以三连喔 &#x1f600;&#x1f603;&#x1f604;&#x1f604;&#x1f60a;&#x1f60a;&#x1f643;&#…

Dubbo 自定义 Filter 编码实例

Dubbo的Filter机制为我们做应用的扩展设计提供了很多可能性&#xff0c;这里的Filter也是“责任链”机制的一种实现场景&#xff0c;作为Java码农&#xff0c;我们也经常接触到很多责任链的实现场景&#xff0c;如Tomcat进入Servlet前的filter&#xff0c;如Spring Aop代理的链…

性能飙升50%,react-virtualized-list如何优化大数据集滚动渲染

在处理大规模数据集渲染时&#xff0c;前端性能常常面临巨大的挑战。本文将探讨 react-virtualized-list 库如何通过虚拟化技术和 Intersection Observer API&#xff0c;实现前端渲染性能飙升 50% 的突破&#xff01;除此之外&#xff0c;我们一同探究下该库还支持哪些新的特性…

自友科技破解走班教育排课难题

新高考后&#xff0c;校园教务都面临着晋级&#xff0c;其中走班教育的分班排课是个巨大的挑战。 所以在分班排课的时候要清楚一下几个问题 一是&#xff1a;清楚的核算学生的选考科目。学生选科提交后做好并承认&#xff0c;最好是在分班后不要改或很少的一部分人改动。 二是…

手写防抖debounce

手写防抖debounce 应用场景 当需要在事件频繁触发时&#xff0c;只执行最后一次操作&#xff0c;可以使用防抖函数来控制函数的执行频率,比如窗口resize事件和输入框input事件&#xff1b; 这段代码定义了一个名为 debounce 的函数&#xff0c;它接收两个参数&#xff1a;fn…

linux中最基础使用的命令

小白学习记录&#xff1a; 前情提要&#xff1a;Linux命令基础格式!查看 ls看目录的小技巧 进入指定目录 cd查看当前工作目录 pwd创建一个新的目录(文件夹&#xff09; mkdir创建文件 touch查看文件内容 cat、more操作文件、文件夹- 复制 cp- 移动 mv- 删除【危险操作&#xff…

Scrum 的速度如何衡量和提高

了解你的 Scrum 团队的实际开发速度是非常多敏捷团队的诉求&#xff0c;而速度&#xff08;Velocity&#xff09;作为敏捷项目的度量工具&#xff0c;为管理者提供了对团队工作能力深入了解的机会。 这份指南将深入探讨 Scrum 中速度的概念&#xff0c;指导你如何进行计算&…

cURL error 60: SSL certificate problem: unable to get local issuer certifica

本地小程序把接口换到本地的服务器接口&#xff0c;然后就报错了&#xff1a; cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) 经查询查到&#xff1a;此问题的出现是由于没有配…

5月更新!优维EasyOps®平台7大新功能上线~

5月&#xff0c;优维EasyOps全平台产品能力又升级啦&#xff01;&#x1f44f; 快来看看都有新增的功能与优化吧&#xff01;&#x1f447; 重点升级 架构可观测 1.系统监控态势感知 过去&#xff0c;用户在使用监控平台的过程中&#xff0c;存在如下问题&#xff1a; 告警…

基于单片机的超声波倒车雷达设计

摘 要&#xff1a;文 章设计了一种基于单片机的超声波倒车雷达系统&#xff0c;以 AT89C51 型单片机作为控制核心&#xff0c;集距离测量、显示&#xff0c;方位显示和危险报警于一体&#xff0c;以提高驾驶者在倒车泊车时的安全性和舒适性。本设计采用 Keil 软件对系统程序…

详解:重庆耶非凡的选品师项目有哪些优势?

在竞争激烈的电商市场中&#xff0c;重庆耶非凡科技有限公司凭借其独特的选品师项目&#xff0c;成功地在众多企业中脱颖而出。这一项目不仅体现了公司对市场趋势的敏锐洞察力&#xff0c;更彰显了其专业的选品能力和对消费者需求的深刻理解。 首先&#xff0c;耶非凡的选品师项…

军用电源性能测试有哪些测试项目?需要遵循什么标准?

为了确保军用电源在极端条件下能够正常工作&#xff0c;必须对其进行一系列严格的性能测试。这些测试不仅包括效率、电压调整率和负载调整率等基本参数的测试&#xff0c;还包括动态响应能力、绝缘电阻、耐压测试、温度系数以及高低温循环等综合性能的评估。 测试项目 效率 电压…

【Python Cookbook】S01E15 将名称映射到序列的元素中

目录 问题解决方案讨论 问题 对于访问列表或元组中的元素&#xff0c;我们通常使用索引或者下标的方法。但是这明显会降低代码的可阅读性。如果我们想通过命名来提高代码的可阅读性&#xff0c;减少结构中对位置的依赖&#xff0c;怎么做&#xff1f; 解决方案 python 提供 …

vscode运行命令报错:标记“”不是此版本中的有效语句分隔符。

1. 报错问题 标记“&&”不是此版本中的有效语句分隔符。 2. 解决办法 将 terminal 中的 owershell 改成 cmd 就 ok

我们如何收到卫星信号?(导航电文,载波与测距码)

卫星信号 在介绍所有卫星信号之前&#xff0c;首先要明确一些概念&#xff1a; 所有的卫星信号&#xff0c;都是一段电磁波&#xff0c;用户接收的&#xff0c;也是一段电磁波。 但是我们认知中的电磁波&#xff0c;就是一段波&#xff0c;就像我们打出去的交一样&#xff0c…

Vue——监听器简单使用与注意事项

文章目录 前言编写简单demo注意事项 前言 监听器&#xff0c;在官网中称为侦听器&#xff0c;个人还是喜欢称之为监听器。官方文档如下&#xff1a; vue 官网 侦听器 编写简单demo 侦听器在项目中通常用于监听某个属性变量值的变化&#xff0c;并根据该变化做出一些处理操作。…

ENVI 5.3/6.0打开Landsat 8/9 C2L2级别数据(带有Metadata),附常见问题

ENVI 5.3/6.0打开Landsat 8/9 C2L2级别数据&#xff08;带有Metadata&#xff09; 文章目录 ENVI 5.3/6.0打开Landsat 8/9 C2L2级别数据&#xff08;带有Metadata&#xff09;前言数据下载ENVI 5.3打开Landsat 8 C2L2级别数据ENVI 5.3打开Landsat 9 C2L2级别数据ENVI 6.0打开La…