背景
在 Web 应用中,如果要根据用户身份限制图片的加载,最直接的方式是使用 cookies 验证身份。然而,在某些场景下,这种方式会带来问题。
当应用需要同时支持浏览器和桌面客户端(如 Tauri)时,图片权限的控制变得更加复杂。
JWT 方案的问题
最初的设计采用了 JWT(JSON Web Token)来实现图片权限控制。方案如下:
- 用户通过身份验证后获得 JWT Token
- 将 Token 添加到图片 URL 的查询参数中
- 服务器验证 Token 的签名和有效期
这个方案虽然可行,但存在以下问题:
Token 暴露风险:将 Token 放在 URL 中会通过浏览器历史记录、服务器日志、Referer 头等渠道泄露。
一次性 Token 的开销:为了安全性,需要使用一次性 Token 或极短过期时间的 Token,这意味着:
- 服务器需要维护 Token 的使用状态(防止重放攻击)
- 或者依赖极短的过期时间(增加客户端复杂性)
- 每次访问图片都需要重新签发 Token
性能开销:频繁的 Token 签发增加了服务器的计算负担。
为什么不用 Cookies
既然 JWT 方案这么麻烦,为什么不直接用 cookies 呢?答案与 Tauri 桌面应用的特殊性有关。
跨域请求的限制
Tauri 使用 asset://localhost 协议来加载页面,但后端 API 可能运行在不同的地址(如 http://localhost:8080)。这导致了跨协议/跨域请求的问题:
- 跨协议请求:从
asset://发出的请求无法自动携带 cookies 到http://或https://地址 - HTTP-only Cookie 失效:HTTP-only Cookie 绑定在特定域名,而
asset://与 API 地址不在同一域,无法正确传递 - CORS 限制:浏览器的安全策略限制了跨域请求时的 Cookie 传递
CSP 配置
Tauri 的 Content Security Policy(CSP)配置需要明确声明允许加载的资源协议:
"csp": "default-src blob: data: filesystem: http: https: tauri: asset: asset://localhost https://asset.localhost http://asset.localhost 'self'"
跨平台差异
在 Linux 平台上,asset:// 协议的实现与 Windows 不同,可能导致相同的 CSP 设置无法正常工作。这增加了开发和维护的复杂性。
HMAC-sig 方案
为了解决上述问题,我们采用了 HMAC 签名方案,使用 BLAKE3 keyed hash 算法。
签名算法
签名使用一个密钥(从 KEY 环境变量派生)对消息进行哈希:
- Message:
user_id+file_id - Key:从环境变量
KEY派生 - Encoding:Base64-URL(无填充)
URL 格式
/img/{user_id}/{file_id}?sig={signature}
验证流程
服务器收到请求后,使用相同的密钥对 user_id + file_id 进行 BLAKE3 keyed hash 哈希,然后与 URL 中的 sig 参数进行比对。这种方式完全无状态,不需要服务器存储任何信息。
方案对比
| 特性 | JWT | Cookies | HMAC-sig |
|---|---|---|---|
| Token 暴露 | URL 中可见 | HTTPOnly Cookie | URL 中可见但不可伪造 |
| 服务器状态 | 可选 | 无 | 无 |
| 过期时间 | 需要 | 自动 | 可选 |
| Tauri 兼容 | 良好 | 差 | 良好 |
| 验证性能 | 中等 | 快 | 快 |
安全性分析
为什么 HMAC-sig 比 JWT 更安全
JWT 的问题在于:即使 Token 只能使用一次,攻击者仍然可以在有效期内窃取并使用它。而 HMAC-sig 方案中:
- 签名基于
user_id和file_id,任何参数的改变都会导致签名失效 - 签名无法被伪造,因为没有密钥无法生成有效的签名
- 不需要设置过期时间,因为签名本身已经绑定了用户和文件
重放攻击防护
虽然 HMAC-sig 是无状态的,但我们可以通过以下方式防止重放攻击:
- 文件级绑定:签名与具体的
file_id绑定,窃取的签名只能访问特定文件 - 可选时间戳:在签名消息中加入时间戳,可以实现自动过期
总结
从 JWT 迁移到 HMAC-sig 方案的主要原因:
- 简化架构:无状态验证,不需要服务器存储 Token 状态
- 提升性能:避免了频繁的 Token 签发
- 增强安全性:签名与资源绑定,无法被重用于其他资源
- 兼容 Tauri:解决了 Cookies 的各种问题
HMAC-sig 方案在保持安全性的同时,提供了更好的性能和兼容性,特别适合需要同时支持浏览器和桌面客户端的应用场景。