Documentation
¶
Overview ¶
Package security 提供独立的安全原语:
- DownloadToken: 取件下载令牌(时间窗 HMAC + 恒时比较)
- SSRF: 存储端点 URL 校验(scheme 白名单 + 私网地址策略)
Index ¶
- Variables
- func ContentDispositionAttachment(name string) string
- func DownloadSessionKey(code, clientIP string) string
- func DownloadTokenFingerprint(token string) string
- func DownloadTokenTTL() time.Duration
- func GenerateDownloadToken(code string) string
- func SetDownloadTokenSecret(secret string)
- func SetDownloadTokenTTL(seconds int)
- func TextViewKey(code, clientIP string) string
- func ValidateEndpointHost(hostPort string) error
- func ValidateEndpointURL(rawURL string) error
- func VerifyDownloadToken(code, token string) bool
- func VerifyDownloadTokenFingerprint(code, token string) string
Constants ¶
This section is empty.
Variables ¶
var ErrEndpointURL = fmt.Errorf("存储端点 URL 不合法")
ErrEndpointURL 存储端点 URL 不合法
Functions ¶
func ContentDispositionAttachment ¶ added in v0.18.0
ContentDispositionAttachment 构造下载 Content-Disposition(RFC 6266 + 5987):
- 引号/反斜杠/控制字符消毒防头断义;
- 纯 ASCII 名只给 filename=;
- 非 ASCII 名(中文等)补 filename*=UTF-8”<pct-encoded>,且 filename= 改用 纯 ASCII 兜底名——老客户端(Android 9 下载器等)不认 filename*,遇到裸 UTF-8 字节会把整个头丢弃变成 download.bin,兜底名至少保住扩展名与可辨识度。
pct-encoded 按 RFC 5987 attr-char 白名单逐字节编码(PathEscape 会放行 `;`/`,` 等非 attr-char,ext-value 里裸分号会截断头参数,故不复用它)。
func DownloadSessionKey ¶ added in v0.32.0
DownloadSessionKey 无令牌部署时的兜底「取件会话」键(按分享码 + 取件人 IP)。
场景:`security.download_token.enabled=false` ⇒ 没有令牌 ⇒ 没有天然的 「一次取件」凭证 ⇒ 若每次交付都算一次取件,多文件分享逐个下载会逐个扣次 (「取件次数=1 只能下载第一个文件」的老问题在关闭令牌时重现)。 ⇒ 用 (分享码, 取件人 IP) 兜底:同一收件人的一次取件(多文件/续传/重下) 只扣 1 次;不同分享互不干扰。
单向(HMAC)⇒ 落库的不是明文 IP。域不同("ip|" vs "fp|")⇒ 不会与令牌指纹撞键。
func DownloadTokenFingerprint ¶ added in v0.32.0
DownloadTokenFingerprint 令牌的计次指纹(**不校验有效性**)。
签发方用它在签发的同一刻算计次键(文本分享在 select 交付正文时计次); 校验方用 VerifyDownloadTokenFingerprint(校验 + 指纹一体)。
func DownloadTokenTTL ¶ added in v0.32.0
DownloadTokenTTL 当前令牌有效期。
func GenerateDownloadToken ¶
GenerateDownloadToken 生成下载令牌 "exp.nonce.sig"。
令牌 = **一次取件的凭证**:下载时按令牌计次(同一令牌至多扣 1 次), 见 repo/db/model.PickupCharge。空密钥(未注入)时返回空串——调用方按 「功能未启用」处理。
⚠️ 形态变更(2026-10-24,破坏性):此前是 "exp.sig"(载荷 `code.exp`), 现在是 "exp.nonce.sig"(载荷 `code.exp.nonce`)。旧令牌在升级后校验失败 (窗口 ≤ TTL,取件页重新取一次即可)。
func SetDownloadTokenSecret ¶
func SetDownloadTokenSecret(secret string)
SetDownloadTokenSecret 注入下载令牌签名密钥(应与 jwt_secret 等强的独立随机值)。
func SetDownloadTokenTTL ¶ added in v0.32.0
func SetDownloadTokenTTL(seconds int)
SetDownloadTokenTTL 设置令牌有效期(秒);<=0 时回落到默认值。
func TextViewKey ¶ added in v0.32.2
TextViewKey **文本交付面**的计次键:按 (分享码, 取件人 IP) 派生。
为什么文本面不用令牌指纹(2026-10-24 回归修复):select 每次都会签发**新**令牌 ⇒ 令牌指纹每次都不同 ⇒ 收件人**刷新一次页面就扣一次**取件次数 ⇒ 额度很快耗尽, 第三次 select 直接 403「超过取件次数」——**正文都看不了**(改动前 select 从不 扣次,文本分享可以随便刷新查看 ⇒ 这是引入的功能性回归)。
文本的交付动作是「打开页面」而非「下载」⇒ 判重粒度应当是**取件者**而不是 一次请求:同一 IP 重复打开/刷新 = 同一次取件(只扣 1 次,能一直看); 不同的人(不同 IP)= 各自的取件(各扣 1 次 ⇒ owner 的「N 次」= N 个取件者)。
单向(HMAC)⇒ 落库的不是明文 IP。与 DownloadSessionKey 的域不同("text|" vs "ip|")⇒ 两类键不会互相撞。
func ValidateEndpointHost ¶
ValidateEndpointHost 校验裸主机端点(FTP/SFTP 的 host 字段:host[:port], 无 scheme 不适用 URL 解析)。策略与 ValidateEndpointURL 一致:IP 字面量直接 判私网;域名解析后逐 IP 复判;allow_private_networks 开启时全放行。
func ValidateEndpointURL ¶
ValidateEndpointURL 校验 s3/webdav 存储端点 URL:
- 仅允许 http/https scheme
- 必须携带 host
- DNS 解析后逐 IP 复判私网策略(防重绑定攻击:解析结果非配置期望网段时拒绝)
allowPrivate 未配置(默认 false)时拒绝环回/私网/链路本地地址。 局域网 MinIO/WebDAV(飞牛 NAS)部署需显式 security.ssrf.allow_private_networks=true。
func VerifyDownloadToken ¶
VerifyDownloadToken 恒时比较校验令牌(exp 窗口 + HMAC 签名)。
func VerifyDownloadTokenFingerprint ¶ added in v0.32.0
VerifyDownloadTokenFingerprint 校验令牌并返回其**计次指纹**(空串 = 无效)。
令牌串 = "exp.nonce.sig",签名载荷 = "code.exp.nonce" ⇒ 签名覆盖分享码 (令牌不可跨分享使用),nonce 落在令牌串里(可重建载荷,无需服务端记账)。
指纹 = HMAC(secret, "fp|" + 令牌):单向,**不是令牌本身** —— 计次记录要落库, 若直接存签名段,DB 泄漏即等于泄漏可直接下载的凭证。
Types ¶
This section is empty.