跳到主要内容

签名机制

本文介绍应用安装流程中使用的签名验证机制。

签名算法​

签名公式:

sign = SHA256(clientId + timestamp + clientSecret)

参数说明:

参数类型说明
clientIdstring应用的 Client ID(UUID 格式)
timestampnumberUnix 时间戳(秒级,10位数字)
clientSecretstring应用的 Client Secret(仅服务端使用)
signstring签名结果(64位小写十六进制字符串)

安全特性​

  • ✅ 不传输密钥 - clientSecret 仅在服务端使用,不通过网络传输
  • ✅ 时间戳防重放 - 签名有效期 10 分钟,允许 1 分钟时钟偏差
  • ✅ 无需 Redis - 无需存储中间状态,简化架构
  • ✅ 简单可靠 - 签名计算简单,便于调试

签名生成示例​

Java 示例​

import cn.hutool.crypto.SecureUtil;

public class SignGenerator {

/**
* 生成签名
* @param clientId 应用 Client ID
* @param timestamp 时间戳(秒级)
* @param clientSecret 应用 Client Secret
* @return 签名值(64位小写十六进制)
*/
public static String generateSign(String clientId, Long timestamp, String clientSecret) {
String signContent = clientId + timestamp + clientSecret;
return SecureUtil.sha256(signContent);
}

public static void main(String[] args) {
String clientId = "550e8400-e29b-41d4-a716-446655440000";
String clientSecret = "7c9e6679-7425-40de-944b-e07fc1f90ae7";
Long timestamp = System.currentTimeMillis() / 1000; // 当前时间戳(秒)

String sign = generateSign(clientId, timestamp, clientSecret);

System.out.println("=== 签名信息 ===");
System.out.println("clientId: " + clientId);
System.out.println("timestamp: " + timestamp);
System.out.println("sign: " + sign);
}
}

Python 示例​

import hashlib
import time

def generate_sign(client_id: str, timestamp: int, client_secret: str) -> str:
"""
生成签名
:param client_id: 应用 Client ID
:param timestamp: 时间戳(秒级)
:param client_secret: 应用 Client Secret
:return: 签名值(64位小写十六进制)
"""
sign_content = f"{client_id}{timestamp}{client_secret}"
return hashlib.sha256(sign_content.encode()).hexdigest()

if __name__ == "__main__":
client_id = "550e8400-e29b-41d4-a716-446655440000"
client_secret = "7c9e6679-7425-40de-944b-e07fc1f90ae7"
timestamp = int(time.time())

sign = generate_sign(client_id, timestamp, client_secret)

print("=== 签名信息 ===")
print(f"clientId: {client_id}")
print(f"timestamp: {timestamp}")
print(f"sign: {sign}")

JavaScript/Node.js 示例​

const crypto = require('crypto');

/**
* 生成签名
* @param {string} clientId 应用 Client ID
* @param {number} timestamp 时间戳(秒级)
* @param {string} clientSecret 应用 Client Secret
* @returns {string} 签名值(64位小写十六进制)
*/
function generateSign(clientId, timestamp, clientSecret) {
const signContent = `${clientId}${timestamp}${clientSecret}`;
return crypto.createHash('sha256').update(signContent).digest('hex');
}

// 使用示例
const clientId = "550e8400-e29b-41d4-a716-446655440000";
const clientSecret = "7c9e6679-7425-40de-944b-e07fc1f90ae7";
const timestamp = Math.floor(Date.now() / 1000);

const sign = generateSign(clientId, timestamp, clientSecret);

console.log("=== 签名信息 ===");
console.log(`clientId: ${clientId}`);
console.log(`timestamp: ${timestamp}`);
console.log(`sign: ${sign}`);

在线工具生成签名​

如果您需要快速测试,可以使用在线工具生成签名:

  1. 拼接字符串:clientId + timestamp + clientSecret

    示例:550e8400-e29b-41d4-a716-44665544000017045280007c9e6679-7425-40de-944b-e07fc1f90ae7
  2. 访问 SHA256 在线工具

  3. 输入拼接后的字符串,获取 SHA256 结果(小写)

签名验证流程​

IM 系统收到确认安装请求后,会进行 3 层验证:

1. 验证 Client ID​

ImApp app = appMapper.selectOne(
new LambdaQueryWrapper<ImApp>()
.eq(ImApp::getClientId, request.getClientId())
);
if (app == null) {
throw new ServiceException("无效的 Client ID");
}

2. 验证时间戳​

long currentTimestamp = System.currentTimeMillis() / 1000;
long timeDiff = currentTimestamp - request.getTimestamp();

// 有效期:10分钟,允许1分钟时钟偏差
if (timeDiff > 600 || timeDiff < -60) {
throw new ServiceException("签名已过期");
}

3. 验证签名​

String calculatedSign = SHA256(
request.getClientId() +
request.getTimestamp() +
app.getClientSecret()
);

if (!calculatedSign.equals(request.getSign())) {
throw new ServiceException("签名验证失败");
}

常见问题​

签名验证失败​

可能原因:

  1. clientSecret 不正确 - 检查配置的 clientSecret 是否与创建应用时获取的一致
  2. 签名计算错误 - 确保拼接顺序为 clientId + timestamp + clientSecret
  3. 时间戳格式错误 - 必须是秒级时间戳(10位数字),不是毫秒级

排查方法:

// 打印签名内容进行调试
String signContent = clientId + timestamp + clientSecret;
System.out.println("signContent: " + signContent);
System.out.println("sign: " + SecureUtil.sha256(signContent));

签名已过期​

可能原因:

  1. 服务器时间不同步 - 检查服务器时间是否准确
  2. 签名生成时间过早 - 签名有效期为 10 分钟

解决方案:

# 检查服务器时间
date

# 同步时间(Linux)
ntpdate pool.ntp.org

时间戳格式错误​

错误示例:

// 错误:毫秒级时间戳
Long timestamp = System.currentTimeMillis(); // 13位数字

// 正确:秒级时间戳
Long timestamp = System.currentTimeMillis() / 1000; // 10位数字

下一步​