签名机制
本文介绍应用安装流程中使用的签名验证机制。
签名算法
签名公式:
sign = SHA256(clientId + timestamp + clientSecret)
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
clientId | string | 应用的 Client ID(UUID 格式) |
timestamp | number | Unix 时间戳(秒级,10位数字) |
clientSecret | string | 应用的 Client Secret(仅服务端使用) |
sign | string | 签名结果(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}`);
在线工具生成签名
如果您需要快速测试,可以使用在线工具生成签名:
-
拼接字符串:
clientId + timestamp + clientSecret示例:550e8400-e29b-41d4-a716-44665544000017045280007c9e6679-7425-40de-944b-e07fc1f90ae7 -
访问 SHA256 在线工具
-
输入拼接后的字符串,获取 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("签名验证失败");
}
常见问题
签名验证失败
可能原因:
- clientSecret 不正确 - 检查配置的 clientSecret 是否与创建应用时获取的一致
- 签名计算错误 - 确保拼接顺序为
clientId + timestamp + clientSecret - 时间戳格式错误 - 必须是秒级时间戳(10位数字),不是毫秒级
排查方法:
// 打印签名内容进行调试
String signContent = clientId + timestamp + clientSecret;
System.out.println("signContent: " + signContent);
System.out.println("sign: " + SecureUtil.sha256(signContent));
签名已过期
可能原因:
- 服务器时间不同步 - 检查服务器时间是否准确
- 签名生成时间过早 - 签名有效期为 10 分钟
解决方案:
# 检查服务器时间
date
# 同步时间(Linux)
ntpdate pool.ntp.org
时间戳格式错误
错误示例:
// 错误:毫秒级时间戳
Long timestamp = System.currentTimeMillis(); // 13位数字
// 正确:秒级时间戳
Long timestamp = System.currentTimeMillis() / 1000; // 10位数字