serverless deploy --stage staging
serverless deploy --stage production # 部署到生产环境
serverless info --stage staging # 查看部署信息
- 本地开发:
# 快速检查
curl http://localhost:3001/health
# 详细调试
curl http://localhost:3001/health/detailed
# 只看 Redis
curl http://localhost:3001/health/redis- 生产环境监控:
# 监控系统调用
curl https://your-api.com/health
# 负载均衡器健康检查
GET /health- Nonce 处理:
- Nonce 生成后存储在 Redis 中,但没有与特定地址绑定,这可能导致任何人都可以使用这个 nonce
- 建议将 nonce 与请求者的 IP 或其他标识符绑定
Redis是否需要定期调用某特殊接口缓存数据
- 签名验证过程:
- 没有对传入消息格式进行严格验证
- 建议使用结构化消息并遵循 EIP-191 或 EIP-712 标准
-
EIP-191: 签名数据标准 (Signed Data Standard)
- 防止重放攻击:确保一个为特定目的签名的消息不能被用于另一个不相关的目的(例如,不能把一个普通消息签名当成交易签名)。
- 版本化:为不同类型的签名数据提供了一个区分机制。
-
EIP-712: 以太坊类型化结构数据的哈希与签名 (Ethereum typed structured data hashing and signing)
- EIP-712 是
在 EIP-191 的基础上发展起来的一个更高级、更用户友好的签名标准。它旨在解决 eth_sign 带来的“盲签”问题——即用户签署的是一串难以理解的十六进制哈希,无法确切知道自己究竟同意了什么。 - EIP-712 的核心思想是让用户在签名时能够看到清晰、结构化的、人类可读的数据内容。
- 提升安全性:用户能明确知道自己签的是什么内容,大大降低了被钓鱼攻击的风险。
- 提升用户体验:将复杂的数据以结构化、可读的方式呈现。
- 防止跨应用/链的重放攻击:通过域分隔符确保签名的唯一上下文。
- EIP-712 是
-
EIP-191 是一个基础的“信封”标准,告诉你这个信封里装的是什么类型的签名数据;而 EIP-712 则是一种具体的信件格式,让你能清楚地读懂信件的内容,并且知道这封信是写给谁的、用在什么场合的。
-
- 代码优化
-
错误处理:
- 有多处类似的错误处理代码,可以抽象为共用方法
- 错误信息没有国际化
-
代码结构:
extractNonceFromMessage使用正则表达式,容易出现边界问题- 消息格式应当更加规范化并使用结构化数据
-
性能优化:
- Redis 操作没有异常处理机制
- 多次数据库操作可以合并或使用事务
- 安全建议
- 实现请求速率限制,防止暴力攻击
- 添加日志脱敏处理,避免敏感信息泄露
- 考虑增加双因素认证选项
- 添加防重放攻击机制(例如,时间戳验证)
- 测试建议
- 增加单元测试覆盖所有边界情况
- 添加集成测试验证整个认证流程
- 模拟恶意请求的安全测试
private users: Map<string, User> = new Map();
private nonces: Map<string, { nonce: string; expires: Date }> = new Map();- 数据不持久化 当服务器重启、崩溃或部署新版本时,所有存储的用户数据会完全丢失。这意味着:
- 所有用户需要重新注册/登录
- 历史数据无法恢复
- 系统状态无法保持连续性
- 单实例限制 用户数据只存在于当前服务实例的内存中:
- 无法进行水平扩展(多实例部署)
- 如果部署多个实例,用户在A实例注册后,访问B实例时会被视为新用户
- 负载均衡会导致用户状态不一致
- 内存占用问题 随着用户数量增长:
- 内存占用持续增加
- 可能导致服务器内存不足
没有垃圾回收或数据过期机制
- 缺乏数据管理功能 与数据库相比:
- 无法进行复杂查询(只能遍历全部数据)
- 缺乏索引优化(查找效率低下)
- 没有事务支持
- 无法进行数据备份和恢复
- 安全隐患
- 敏感数据明文存储在内存中
- 缺乏访问控制和权限管理
- 没有数据加密保护
这种实现方式只适合:
- 开发和测试环境
- 概念验证或原型
- 极小规模的演示应用
在实际生产环境中,应当使用数据库(如MongoDB、MySQL、PostgreSQL等)替代内存存储,以确保数据的持久性、一致性和可扩展性。
从您提供的日志中,我发现了两个主要问题:
-
最新的401错误原因:nonce提取失败
[Nest] 17377 - 2025/05/21 17:38:03 DEBUG [AuthService] 从消息中提取的nonce: 0 [Nest] 17377 - 2025/05/21 17:38:03 WARN [AuthService] 未找到对应的nonce: 0当前的正则表达式在处理
0x前缀的十六进制nonce时只提取了第一个字符"0",导致nonce验证失败。 -
控制器调用错误的验证方法
@Post('simple-verify') async verifySimpleSignature(...) { // 错误:调用verifySignature而不是verifySimpleSignature const result = await this.authService.verifySignature(message, signature, address); // ... }
虽然您已经实现了
verifySimpleSignature方法,但控制器仍在调用verifySignature方法。
在auth.service.ts中更新提取nonce的方法:
private extractNonceFromMessage(message: string): string | null {
try {
// 更新正则表达式,正确匹配0x开头的十六进制nonce
const nonceMatch = message.match(/Nonce: (0x[a-f0-9]+|[a-f0-9]+)/i);
if (nonceMatch && nonceMatch[1]) {
this.logger.debug(`提取到的原始nonce字符串: '${nonceMatch[1]}'`);
return nonceMatch[1];
}
return null;
} catch (error) {
this.logger.error(`提取nonce失败: ${error.message}`);
return null;
}
}
// 同样修复简单消息的nonce提取
private extractNonceFromSimpleMessage(message: string): string | null {
try {
const nonceMatch = message.match(/Nonce: (0x[a-f0-9]+|[a-f0-9]+)/i);
if (nonceMatch && nonceMatch[1]) {
this.logger.debug(`提取到的原始nonce字符串: '${nonceMatch[1]}'`);
return nonceMatch[1];
}
return null;
} catch (error) {
this.logger.error(`从简单消息提取nonce失败: ${error.message}`);
return null;
}
}在auth.controller.ts中修改simple-verify路由:
@Post('simple-verify')
async verifySimpleSignature(
@Body() body: { message: string; signature: string; address: string },
) {
this.logger.debug(`收到简化验证请求: ${JSON.stringify(body, null, 2)}`);
const { message, signature, address } = body;
// 参数验证...
this.logger.debug('开始简化签名验证...');
// 修正:使用正确的verifySimpleSignature方法
const result = await this.authService.verifySimpleSignature(
message,
signature,
address,
);
// 结果处理...
}在前端的AuthContext.tsx中添加更多日志:
const createSimpleSignMessage = async (address: string) => {
const nonce = await fetchNonce();
console.log('获取到的nonce:', nonce); // 确认nonce格式
// 创建消息...
const message = `Sign this message to authenticate with ${domain}.\n\nAddress: ${address}\nNonce: ${nonce}\nTimestamp: ${new Date().toISOString()}`;
console.log('创建的简单消息:', message);
return message;
};
const login = async () => {
// ...
// 添加更多日志以便调试
console.log(
'发送到后端的数据:',
JSON.stringify(
{
message,
signature,
address: normalizedAddress,
},
null,
2,
),
);
// ...
};- 正则表达式处理:修改正则表达式以正确匹配带有
0x前缀的十六进制nonce - 方法调用一致性:确保控制器调用正确的验证方法
- 日志增强:添加更详细的日志记录,特别是在nonce提取和处理阶段
- 错误处理完善:添加更清晰的错误消息,便于调试
通过这些修改,您的钱包签名认证系统应该能够正常工作了。重启前后端服务后测试,您应该能够成功通过钱包签名完成认证流程。