这篇文章,我们聊聊 magic-admin 项目中如何设计签名机制( ruoyi-vue-pro 基础上优化签名机制)。

之所以写这篇,因为签名机制实现起来相对简单,在互联网领域安全领域使用非常广泛 。而且 ruoyi-vue-pro 的签名设计代码是完成的,但是和后台管理并没有联系起来,需要手工在 Redis 中设置值,其实没有闭环,我在客户端管理里做了完善。
# 1 加密和验签
# 01 加密
加密的目标是:防止数据在传输过程中被窃听(防泄漏),确保机密性(Confidentiality)。
它的基本原理是: 通过算法将明文转换为密文,只有持有密钥的合法方才能解密。
典型应用场景:
- 用户密码、身份证号等敏感字段的传输
- HTTP 请求体/响应体的内容保护
实现方式:
1、对称加密(如AES)
- 加密/解密使用同一密钥
- 性能高,适合大数据量
2、非对称加密(如RSA)
- 公钥加密,私钥解密
- 性能相比较慢,适合小数据量
# 02 验签
验签的目标是:验证数据的完整性和来源真实性(防篡改+防伪装)。
它的基本原理是: 使用密钥生成数据的数字签名,接收方通过验证签名判断数据是否被篡改。
典型应用场景:
- API请求参数防篡改(如支付金额被修改)
- 客户端身份认证(防伪装攻击)
# 2 签名注解
在 Controller 的方法上,添加 @ApiSignature 注解,声明它需要签名。
然后,通过 AOP 切面,ApiSignatureAspect 对这些方法进行拦截,校验签名是否正确 。
验签的核心代码逻辑:
public boolean verifySignature(ApiSignature signature, HttpServletRequest request) {
// 1.1 校验 Header
if (!verifyHeaders(signature, request)) {
return false;
}
// 1.2 校验 clientKey 是否能获取到对应的 appSecret
String clientKey = request.getHeader(signature.clientKey());
SecurityClientDTO securityClientDTO = clientAdapter.getClient();
Assert.notNull(securityClientDTO.getClientSecret(), "[clientKey({})] 找不到对应的 clientSecret", clientKey);
// 2. 校验签名【重要!】
String clientSignature = request.getHeader(signature.sign()); // 客户端签名
String serverSignatureString = buildSignatureString(signature, request, securityClientDTO.getClientSecret()); // 服务端签名字符串
String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名
if (ObjUtil.notEqual(clientSignature, serverSignature)) {
return false;
}
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
String nonce = request.getHeader(signature.nonce());
signatureRedisDAO.setNonce(clientKey, nonce, signature.timeout() * 2, signature.timeUnit());
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
step 1 : 验证请求头参数
每次请求 Header 需要带上 clientKey、timestamp、nonce、sign 四个参数:
clientKey:客户端 key 。timestamp:请求时的时间戳。nonce:用于请求的防重放攻击,每次请求唯一,例如说 UUID。sign:HTTP 签名。
客户端 clientKey 在客户端管理中查看:
step 2 : 校验签名【重要!】
/**
* 构建签名字符串
* <p>
* 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
*
* @param signature signature
* @param request request
* @param appSecret appSecret
* @return 签名字符串
*/
private String buildSignatureString(ApiSignature signature, HttpServletRequest request, String appSecret) {
SortedMap<String, String> parameterMap = getRequestParameterMap(request); // 请求参数
SortedMap<String, String> headerMap = getRequestHeaderMap(signature, request); // 请求头参数
String requestBody = StrUtil.nullToDefault(ServletUtils.getBody(request), ""); // 请求体
return MapUtil.join(parameterMap, "&", "=")
+ requestBody
+ MapUtil.join(headerMap, "&", "=")
+ appSecret;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
构建签名字符串为:请求参数 + 请求体 + 请求头 + 密钥 ,然后通过 SHA256 进行加密,得到签名 sign 值。
注意: 此处的 appSecret 秘钥在客户端管理里的:


