Web请求体数字签名(JS加签、Java验签)

本文探讨了仅使用HTTPS的安全局限性,并提出了一种通过数字签名增强安全性的方法。详细介绍了前后端如何实现请求签名验证,包括使用MD5算法加盐、设置过滤器及异常处理。

前情

为什么要搞,要这么做? 难道只用HTTPS不够吗?

如果应用只使用HTTPS,那还真不够用!
原因:攻击者可以模拟客户端操作,枚举敏感用户信息、攻击应用。譬如,管理界面只要是放在互联网中,那么攻击者
就能够通过网络直接访问。只要是能访问,那么客户端与服务端的链接通道就找到了,并打开了。在数据还没有进入到互联网
环境前,攻击者可利用三方工具对模拟真实的请求,并对其拦截、抓包、修改,如此变绕开了前端的基础校验。

对于一些特殊敏感数据,例:用户表,主键id(userId)。这些数据如果通过HTTP、互联网环境传输到服务器端,而恰巧主键生成策略是有规律可循(bigint自增、某种规律性的公式)的,那么攻击者可以通过枚举的方式,高频繁修改请求包信息,请求服务端。以此来获取一些敏感的数据信息。

思路

攻击者能够肆无忌惮的攻击服务器,归根结底是因为两点:

  1. 请求被抓包,对包信息修改
  2. 修改后的包信息可以直接发送给服务端;

对于抓包我们无法处理,但是我们可以对修改的信息做些手脚!这就用到了数字签名,加签验签!


数字签名的注意事项:

  1. 因为是全局性处理,所以必须要考虑性能损耗!
  2. 数字签名不能被攻击者复制。否则数字签名就无效了!
  3. 对传输的请求不要任何脏影响,也就是说请求体数据必须保证完整性

针对以上思考,采用的方案:

  1. 签名算法使用MD5(AES、国密、甚至RSA都可以);
  2. 考虑到MD5的易破解性,所以我们加slat (必须包含特殊字符,确保安全)
  3. 服务端使用Filter对请求做合法判断处理;
  4. 因为HTTP方法有多种,Content-Type存在多种。所以我们采用String格式做签名**(保证数据顺序的一致性)**;

开始

环境介绍:

  1. 前端框架:Vue 4.5.10,使用Axios作为网络请求库;
  2. 包管理工具:npm 6.9.0
  3. 后端框架:SpringBoot 2.4.1

前端开发

安装加密组件

 # npm 安装加密组件
 cnpm install crypto-js

说明:用其他组件也可以,或者自己手写都行。关键是能保证前后端的验签算法保持一致即可。

crypto.js (封装 util)

import CryptoJS from 'crypto-js'

/**
 * 加盐MD5加密,可以作为加签算法
 * @param {加密对象} obj 
 * @param {*} slat 
 */
export function MD5(obj, slat) {
   
   
	if (!obj) {
   
   
		return obj;
	}

	// 转换成字符串
	let str = JSON.stringify(obj);
	if (!!slat) {
   
   
	    // 拼接slat
		str = str.concat(slat)
	}

	// 关键点:将所有上引号替换成空,理由:后台Filter获取的参数全部为String,所以为了保证格式一致,取消掉上引号
	str = str.replaceAll(/"/g, "");
	// JSON数据存在特殊符号[和]
	str = str.replaceAll("\[", "");
	str = str.replaceAll("]", "");
	
	// MD5加密后,转成字符串
	return CryptoJS.MD5(str).toString();
}

关键点:

  • 为了保证后端在验签时,对数据的还原保持一致性,所以需要对特殊字符做处理(删除)
  • 加盐的公式,我们可以任意自定义,不变的是,保证slat具有一定的复杂性

Axios全局request拦截

import axios from 'axios'

/**
 * 需要加签、验签的路径集合
 * 例:"/user",将匹配以"/user"开头的所有API
 */
const blackBeginUrl = ["/user", "/role"]

// HTTP request拦截
axios.interceptors.request.use(
	(config) => {
   
   
		const meta = config.meta || {
   
   }
		const isToken = meta.isToken === false
		if (getToken() && !isToken) {
   
   
			config.headers['Authorization'] = getToken() 
		}

		// 判断是否需要对路径做加签操作
		let needSign = false;
		for (let blackUrl of blackBeginUrl) {
   
   
			if (config.url.indexOf(blackUrl) != -1) {
   
   
				needSign = true;
				break;
			}
		}

		if (needSign) {
   
   
		    
		    // 这里默认post请求的Content-Type:application/json (可以和开发者做好约定)
			let requestData = "";
			if (config.method === "get" && !!config.params) {
   
   
				requestData = config.params
			} else if (config.method == "post" && !!config.data) {
   
   
				requestData = config.data
			}

            // 时间戳,作为slat的必备组成之一
			const timestamp = Date.parse(new Date())
			config.headers['Timestamp'] = timestamp
			// 随机字符串,作为slat的必备组成之一
			const randomStr = "K:*C8bw6zJ"
			// slat = 时间戳 + 随机字符串 (自定义slat公式)
			const slat = timestamp.concat(randomStr);
			
			const signature = MD5(requestData, slat);
			config.headers['Signature'] = signature;
		}

		return config;
	},
	(error) => {
   
   
		tryHideFullScreenLoading()
		return Promise.reject(error)
	},
)

随机生成网站:在线生成随机字符串

说明:如果业务上对个别API加签,可以仿照上述代码的方式,定义需要验签的API黑名单。

  1. 效果
    在这里插入图片描述

通过上图可以看到,我们对本次请求成功生成了数字签名。Headers key为 Signature。

至此,

评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_函数_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值