如何在NestJS中正确验证Telegram Web App数据?
优先排查代码问题(VPN影响哈希计算的概率极低)
数据解析逻辑错误
你的代码里用querystring.parse(authorizationData.telegramId),请确认authorizationData.telegramId是否是Telegram返回的原始query string格式字符串(比如auth_date=1690000000&hash=abcdef...&user={"id":123,...})。如果前端已经把initData解析成对象传给后端,再用querystring.parse会导致数据结构完全错误,最终哈希计算必然不匹配。建议先打印authorizationData.telegramId的实际值,确认格式是否正确。验证字符串换行符问题
Telegram要求验证字符串的键值对用**LF换行符(\n)**分隔,确保你的join("\n")没有被转成CRLF(\r\n)。可以打印dataCheckString的原始内容,检查换行符是否正确。哈希大小写不统一
Telegram返回的hash可能是十六进制小写或大写,而你的代码计算出的computedHash是小写(digest('hex')默认输出小写)。修改比较逻辑为统一大小写:if (computedHash.toLowerCase() !== hash.toLowerCase()) { console.log("Verification failed!"); }BotToken正确性
确认configService获取到的TELEGRAM_BOT_TOKEN完全正确,没有多余的空格、引号或拼写错误。可以直接打印botToken的值和机器人后台的token对比。字段完整性检查
确保data对象包含所有Telegram返回的字段(auth_date、user、chat_instance等,除了hash),且user字段是原始JSON字符串(未被前端解析成对象)。如果user被提前解析,拼接验证字符串时会变成user=[object Object],导致哈希计算错误。
VPN影响排查
VPN一般不会篡改HTTPS传输的数据,但可以做以下验证:
- 在前端打印
Telegram.WebApp.initData的原始值,和后端收到的authorizationData.telegramId对比,确认内容完全一致,没有字段缺失或内容修改。 - 检查前端是否在传输过程中对initData做了额外编码/解码操作,导致数据变形。
修正后的代码示例(关键部分调整)
async authorizationTest(@Body() authorizationData: AuthorizeUserDto) { console.log("Body received from Telegram", authorizationData); const botToken = this.configService.get("TELEGRAM_BOT_TOKEN"); // 先确认telegramId是原始query string,再解析 const data = querystring.parse(authorizationData.telegramId); console.log("Data before hash", data); const hash = data.hash as string; delete data.hash; // 构建验证字符串,确保用LF换行 const dataCheckString = Object.keys(data) .sort() .map((key) => `${key}=${data[key]}`) .join("\n"); console.log("dataCheckString", dataCheckString); // 计算密钥 const secretKey = crypto .createHmac("sha256", botToken) .update("WebAppData") .digest(); // 计算哈希 const computedHash = crypto .createHmac("sha256", secretKey) .update(dataCheckString) .digest("hex"); console.log("computedHash", computedHash); // 统一大小写比较 if (computedHash.toLowerCase() !== hash.toLowerCase()) { console.log("Verification failed!"); return; } console.log("Hooray! Verification succeeded!"); // 验证auth_date超时 const CURRENT_UNIX_TIME = Math.floor(Date.now() / 1000); const TIMEOUT_SECONDS = 3600; if (CURRENT_UNIX_TIME - Number(data.auth_date) > TIMEOUT_SECONDS) { console.log("Verification failed due to timeout"); return; } console.log("Verification successful"); }
内容的提问来源于stack exchange,提问作者tron21

