这篇文章,我们聊聊 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;
}
1
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 需要带上 clientKeytimestampnoncesign 四个参数:

  • 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;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

构建签名字符串为:请求参数 + 请求体 + 请求头 + 密钥 ,然后通过 SHA256 进行加密,得到签名 sign 值。

注意: 此处的 appSecret 秘钥在客户端管理里的:

上次更新: 2026/6/28