[Spring Cloud] (7)gateway防重放拦截器

news2024/11/26 10:37:02

文章目录

  • 简述
    • 本文涉及代码已开源
    • Fir Cloud 完整项目
    • 防重放
      • 防重放必要性:
      • 防重放机制作用:
  • 整体效果
    • 后端进行处理
  • 后端
    • 增加防重放开关配置
    • 签名密钥 工具类
    • 防重放拦截器
  • 前端
    • 被防重放拦截
    • 增加防重放开关配置
    • 请求头增加防重放签名处理
    • 防重放验证处理函数
    • base64加密生成签名
    • 请求头信息增加

简述

本文涉及代码已开源

本文网关gateway,微服务,vue已开源到gitee
杉极简 / gateway网关阶段学习
https://gitee.com/dong-puen/gateway-stages

Fir Cloud 完整项目

该内容完整项目如下
Fir Cloud v1.0.0
https://gitee.com/dong-puen/fir-cloud
https://github.com/firLucky/fir-cloud
在这里插入图片描述

防重放

防重放攻击的必要性主要来自于网络安全中的一个核心原则:确保数据的完整性、机密性和不可否认性。重放攻击是一种常见的安全威胁,它利用网络通信的漏洞来重新发送之前捕获的通信数据,以欺骗系统执行未授权的操作。

防重放必要性:

  1. 数据完整性:防止攻击者通过重放攻击修改或替换数据,确保数据在传输过程中未被篡改。
  2. 防止身份盗用:防止攻击者利用截获的认证信息冒充合法用户,从而访问受保护的资源。
  3. 保护交易:在金融交易和电子商务中,防止攻击者通过重放交易来欺诈。
  4. 遵守法规:某些行业法规要求实施防重放机制,以符合数据保护和网络安全的法律要求。
  5. 维护信任:通过保护系统免受重放攻击,维护用户和系统之间的信任关系。

防重放机制作用:

  1. 验证时间戳:通过在消息中包含时间戳,并确保消息在合理的时间窗口内被接收,可以防止旧消息被重放。
  2. 数字签名:使用数字签名来验证消息的发送者和内容,确保消息未被篡改,并且发送者的身份得到验证。
  3. 应用层检查:在应用层进行额外的逻辑检查,例如验证用户的状态或会话,以防止重放攻击。
  4. 日志和监控:记录和监控网络活动,以便在发生重放攻击时能够快速检测和响应。

通过实施防重放机制,可以显著提高系统的安全性,保护关键数据和操作免受未授权的访问和篡改。在设计系统时,应考虑潜在的重放攻击,并采取适当的措施来防范。

整体效果

前端如果没有进行防重放信息处理,则会被直接拦截。

后端进行处理

image.png

后端

增加防重放开关配置

  # 防重放
  replay: true

    /**
     * 防重放攻击
     */
    private boolean replay;

image.png
image.png

签名密钥 工具类

package com.fir.gateway.utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * 签名密钥 工具类
 * 
 * @author fir
 */
public class SignatureUtils {

    /**
     * 解密前端防重放校验的签名密钥(失败未验证)
     *
     * @param signature 签名密钥
     * @param secretKey 加密密钥
     * @return 返回解密结果
     */
    public static String decryptSignature(String signature, String secretKey) {
        try {
            // 使用HMAC-SHA256算法
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKeySpec);

            // 对签名进行Base64解码
            byte[] signatureBytes = Base64.getDecoder().decode(signature);

            // 进行解密操作
            byte[] decryptedBytes = sha256Hmac.doFinal(signatureBytes);

            // 将解密结果转为字符串
            String decryptedSignature = new String(decryptedBytes, StandardCharsets.UTF_8);

            return decryptedSignature;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 解密前端防重放校验的签名密钥
     *
     * @param signature 签名密钥
     * @return 返回解密结果
     */
    public static String decryptSignatureBase64(String signature) {
        try {
            // 对签名进行Base64解码
            byte[] signatureBytes = Base64.getDecoder().decode(signature);

            // 将解密结果转为字符串

            return new String(signatureBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

防重放拦截器

package com.fir.gateway.filter.request;

import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.result.AjaxResult;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ReplayAttackInfo;
import com.fir.gateway.utils.SignatureUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;


/**
 * 防重放攻击-请求拦截器
 *
 * @author fir
 */
@Component
@Slf4j
public class ReplayAttackFilter implements GlobalFilter, Ordered {


    /**
     * 网关参数配置
     */
    @Resource
    private GlobalConfig globalConfig;


    /**
     * 5 * 60 * 1000 表示5分钟的间隔,用于防重放的间隔之中
     */
    private static final long TIMESTAMP_VALID_TIME = 5 * 60 * 1000;

    private final Set<String> usedNonceSet = Collections.synchronizedSet(new HashSet<>());


    /**
     * 每分钟执行一次
     */
    @Scheduled(cron = "0 * * * * *")
    public void clearUsedNonceSet() {
        usedNonceSet.clear();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("防重放攻击验证:start");
        boolean replay = globalConfig.isReplay();
        if(replay) {
            // 白名单路由判断
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getPath().toString();
            List<String> whiteUrls = globalConfig.getWhiteUrls();
            if(whiteUrls.contains(path)){
                log.info("防重放攻击验证:true,白名单");
                return chain.filter(exchange);
            }

            // 从请求头中获取Nonce和Timestamp
            String nonce = exchange.getRequest().getHeaders().getFirst("n");
            String timestamp = exchange.getRequest().getHeaders().getFirst("t");
            String s = exchange.getRequest().getHeaders().getFirst("s");
            // 验证Nonce和Timestamp是否合法
            boolean validateKey = validateNonceAndTimestamp(nonce, timestamp, s);
            if (validateKey) {
                // 如果合法,则放行请求
                log.info("防重放攻击验证:true");
            } else {
                log.info("防重放攻击验证:false");
                // 如果不合法,则返回错误响应
                ServerHttpResponse response = exchange.getResponse();
                // 自定义返回体描述
                AjaxResult error = AjaxResult.error(AjaxStatus.ANTI_REPLAY_VERIFY_FAILED);
                String resData = JSONObject.toJSONString(error);

                byte[] responseBody = resData.getBytes(StandardCharsets.UTF_8);

                response.getHeaders().setContentLength(responseBody.length);
                response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

                return response.writeWith(Mono.just(response.bufferFactory().wrap(responseBody)));

            }
        }else {
            log.info("防重放攻击验证:true,验证已关闭");
        }

        return chain.filter(exchange);
    }


    @Override
    public int getOrder() {
        // 设置过滤器的优先级
        return -200;
    }


    /**
     * 根据请求时间戳,与请求签名密钥,判断请求是否是合法的
     *
     * @param nonce 请求签名密钥
     * @param timestamp 请求时间戳
     * @return 是否合法
     */
    private boolean validateNonceAndTimestamp(String nonce, String timestamp, String s) {
        // 判断Nonce和Timestamp是否为空
        if (nonce == null || timestamp == null) {
            log.error("防重放攻击验证:非法请求,无请求时间戳");
            return false;
        }

        // 验证Nonce是否已经使用过
        if (usedNonceSet.contains(nonce)) {
            log.error("防重放攻击验证:请求签名已使用");
            return false;
        } else {
            // 将本次的请求签名记录,用于下次判断是否有相同的请求签名
            usedNonceSet.add(nonce);
        }

        // 判断事件戳与数据签名是否相同
        String str = SignatureUtils.decryptSignatureBase64(nonce);
        ReplayAttackInfo replayAttackInfo = JSONObject.parseObject(str, ReplayAttackInfo.class);
        String t = replayAttackInfo != null ? replayAttackInfo.getT() : null;
        if (StringUtils.isBlank(t) || !timestamp.equals(t)){
            log.error("防重放攻击验证:非法请求,请求时间非法");
            return false;
        }

        // 验证Timestamp是否在合理时间范围内
        long timeStampValue;
        try {
            timeStampValue = Long.parseLong(timestamp);
        } catch (NumberFormatException e) {
            log.error("防重放攻击验证:非法请求,请求时间错误");
            return false;
        }

        long currentTime = System.currentTimeMillis();

        // 判断请求是是否是在n分钟之前请求的
        boolean a = timeStampValue >= currentTime - TIMESTAMP_VALID_TIME;
        // 判断请求是是否是在n分钟后前请求的
        boolean b = timeStampValue <= currentTime + TIMESTAMP_VALID_TIME;

        boolean c = a && b;
        if (!c){
            log.info("防重放攻击验证:请求过期");
        }
        return c;
    }
}

前端

被防重放拦截

image.png
增加nonce(签名),t(时间戳)。

增加防重放开关配置

// 防重放
const replay = true;

image.png

请求头增加防重放签名处理

gatewayRequest中增加

            // 防重放请求通过信息
            if (replay) {
                this.replayAttack(request);
            }

image.png

防重放验证处理函数

    //************************************防重放-start
    /**
     * 防重放验证
     *
     * @param config Axios请求的配置对象
     */
    replayAttack(config) {
        let params = config.params;
        let reqData = config.params;
        let t = new Date().getTime();
        if (t) {
            config.headers.t = t

            if (reqData == null) {
                params = {
                    't': t,
                }
            } else {
                // params = JSON.parse(reqData)
                params["t"] = t;
            }

            const data = JSON.stringify(params);
            config.headers.n = this.gBase(data);
        }
    },
    //************************************防重放-end

base64加密生成签名

    /**
     * base64加密
     * @param data data 待加密数据
     */
    gBase(data) {
        return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(data));
    },

请求头信息增加

此时请求头中会增加两个信息,一个是时间戳,一个是签名。后端此时就会验证信息是否合法,合法则放行。
image.png

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信息系统项目管理师0104:详细可行性研究(7项目立项管理—7.2项目可行性研究—7.2.3详细可行性研究)

点击查看专栏目录 文章目录 7.2.3详细可行性研究1.详细可行性研究的依据2.详细可行性研究的原则3.详细可行性研究的方法4.详细可行性研究的内容5.详细可行性研究报告记忆要点总结7.2.3详细可行性研究 详细可行性研究是在项目决策前对与项目有关的技术、经济、

14.CAS原理

文章目录 CAS原理1.什么是CAS2.Unsafe类中的CAS方法2.1.获取UnSafe实例2.2.调用UnSafe提供的CAS方法2.3.调用Unsafe提供的偏移量相关2.4.CAS无锁编程2.4.1.使用cas进行无锁安全自增案例 CAS原理 由于JVM的synchronized重量级锁设计操作系统内核态下的互斥锁的使用&#xff0c;其…

Web安全:SQL注入之布尔盲注原理+步骤+实战操作

「作者简介」&#xff1a;2022年北京冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础对安全知识体系进行总结与归纳&#xff0c;著作适用于快速入门的 《网络安全自学教程》&#xff0c;内容涵盖系统安全、信息收集等…

5款可用于LLMs的爬虫工具/方案

5款可用于LLMs的爬虫工具/方案 Crawl4AI 功能: 提取语义标记的数据块为JSON格式&#xff0c;提供干净的HTML和Markdown文件。 用途: 适用于RAG&#xff08;检索增强生成&#xff09;、微调以及AI聊天机器人的开发。 特点: 高效数据提取&#xff0c;支持LLM格式&#xff0c;多U…

天龙怀旧游戏python脚本

设置图&#xff1a; 游戏窗口最大化。 海贼洞这里定位你要回点的定位。 运行bat就行&#xff0c;脚本出错了还是会重新运行脚本&#xff0c;运行自动启动&#xff0c;end暂停脚本&#xff0c;home重新启动脚本 1. 我常用的是内挂回点脚本&#xff0c; 下面都是前台脚本&…

【适用全主题】WordPress原创插件:弹窗通知插件 支持内容自定义

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 适用于所有WordPress主题的弹窗插件 一款WordPress原创插件&#xff1a;弹窗通知插件 支持内容自定义 二、效果展示 1.部分代码 代码如下&#xff08;示例&#xff09;&#xff1…

Oracle如何收缩减小表空间大小

比如我们发现一个表空间占用比较大&#xff0c;但是空闲空间很大&#xff0c;想要减小表空间占用大小。查看表空间的情况 发现BETEST表空间占用大&#xff0c;但是剩余大小比较大&#xff0c;可以减小存储占用。 如果我们想减小到100MB&#xff0c;那么就登录其用户执行&#…

Python | Leetcode Python题解之第86题分隔链表

题目&#xff1a; 题解&#xff1a; class Solution:def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:sml_dummy, big_dummy ListNode(0), ListNode(0)sml, big sml_dummy, big_dummywhile head:if head.val < x:sml.next headsml sm…

IDEA及Maven配置代理及Maven中央仓库配置详解

一、配置代理 首先&#xff0c;需要本地开启代理入口&#xff0c;如图。 这个跟你使用代理软件有关。像我使用的是qv2ray。 其次&#xff0c;idea配置代理&#xff0c;如图。 1.1 idea配置代理 打开Settings&#xff0c;如图 1.2 maven配置代理 maven配置代理&#xff0c;修…

【JavaEE】Spring Boot 入门:快速构建你的第一个 Spring Boot 应用

目录 第一个SpringBoot程序介绍项目创建创建项目目录介绍输出Hello World 第一个SpringBoot程序 介绍 在学习SpringBoot之前, 我们先来认识⼀下Spring 我们看下Spring官⽅(https://spring.io/)的介绍 可以看到, Spring让Java程序更加快速, 简单和安全. Spring对于速度、简单…

Android 系统全局Bug日志监听

一、Android DropBox Android用来持续化存储系统数据的一个管理类&#xff0c;主要用于记录Android运行过程中&#xff0c;内核、系统j进程、用户进程等出现严重问题时的Log&#xff0c;可以认为它就是一个可持续存储系统级别的Logcat. 日志储存位置&#xff1a;/data/system…

Golang | Leetcode Golang题解之第86题分隔链表

题目&#xff1a; 题解&#xff1a; func partition(head *ListNode, x int) *ListNode {small : &ListNode{}smallHead : smalllarge : &ListNode{}largeHead : largefor head ! nil {if head.Val < x {small.Next headsmall small.Next} else {large.Next hea…

云计算第十二课

安装虚拟机 第一步新建虚拟机 选择自定义安装 下一步 选择稍后安装操作系统 选择系统类型和版本 选择虚拟机文件路径&#xff08;建议每台虚拟机单独存放并且路径不要有中文&#xff09;点击下一步 选择bios下一步 选择虚拟机处理器内核数量 默认硬盘或者自行调大硬盘 选择虚…

Dato for Mac v5.2.11激活版:掌握时间,掌控生活

在忙碌的生活中&#xff0c;您是否常常觉得时间不够用&#xff1f;Dato for Mac&#xff0c;您的时间管理专家&#xff0c;助您轻松掌控每一天。清晰的日历视图、个性化的提醒功能&#xff0c;让您的日程安排井井有条。无论是工作还是生活&#xff0c;Dato for Mac都能成为您的…

ONES 功能上新 | 近期产品新功能一览

支持在 ONES Project 中通过弹窗查看、编辑 ONES Wiki 页面。 应用场景&#xff1a; 当需要在 ONES Project 中查看 ONES Wiki 的页面内容时&#xff0c;可以直接点击工作项关联的 ONES Wiki 页面或项目文档组件中的页面&#xff0c;即可在 ONES Project 中通过弹窗查看 ONES W…

OCR技术在历史文献数字化中的革命性作用

随着数字化技术的不断发展&#xff0c;历史文献的数字化已成为保存和传播文化遗产的重要途径。其中&#xff0c;光学字符识别&#xff08;OCR&#xff09;技术在历史文献数字化中发挥了革命性的作用&#xff0c;为研究者提供了更广阔的研究空间&#xff0c;推动了历史学研究的发…

【C语言|数据结构】双向链表

文章目录 前言1、初步认识双向链表1.1 定义&#xff1a;1.2 结构1.3 节点的存储 2、双向链表的接口函数2.1 链表的节点的动态申请2.2 链表的初始化2.3 尾插2.4 头插2.5 头删2.5 尾删2.6 在pos节点后面添加数据2.6 删除pos节点 3、双向链表的实现&#xff1a; 前言 各位小伙伴大…

读人工智能时代与人类未来笔记01_重塑人类社会秩序

1. AlphaZero 1.1. 2017年年底&#xff0c;由谷歌旗下DeepMind公司开发的人工智能程序AlphaZero击败了当时世界上最强大的国际象棋程序Stockfish 1.1.1. AlphaZero对Stockfish的百场战绩是28胜72平0负&#xff0c;可以说获得了压倒性的胜利 1.1.2. …

嵌入式学习-中断控制系统

补充一下前面NVIC内嵌向量中断控制器的知识 中断 中断类型 中断控制 配置中断 优先级 分组问题 中断使能 NVIC相关库函数和作用 库函数 函数名 描述 NVIC_DeInit 将外设 NVIC 寄存器重设为初始值 NVIC_SCBDeInit 将外设 SCB 寄存器重设为初始值 NVIC_PriorityGroupCon…

C++ | Leetcode C++题解之第85题最大矩形

题目&#xff1a; 题解&#xff1a; class Solution { public:int maximalRectangle(vector<vector<char>>& matrix) {int m matrix.size();if (m 0) {return 0;}int n matrix[0].size();vector<vector<int>> left(m, vector<int>(n, 0)…