Documentation
¶
Overview ¶
Package captcha 给任意 Go HTTP 服务的登录加 ALTCHA 人机校验(proof-of-work captcha,自托管、无第三方、无跟踪),几行即可接入。
框架无关:核心只依赖 net/http,gin / chi / 标准库都能用(见 README)。
工作流程(无状态、HMAC 签名、无需落库):
登录页加载 → GET 挑战端点取一道题 → 前端 widget 后台算 PoW 解出 → 提交登录时把 base64 解答放进请求体的 altcha 字段 → 服务端调 Verify 校验。
特性开关:HMACKey 为空 = 关闭校验(Verify 恒为 true)。此时挑战端点仍照常出题 (用空 key 签名,widget 一样能解),保证「先上线代码、后开启校验」的灰度期间任何 登录都不中断——部署序:先上服务(key 空)、再上前端、最后设 key 重启即开启。
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Assets ¶
Assets 返回内嵌的前端套件(altcha-login.js / altcha-login.css),根目录即这两个文件。 这样消费者无需手动从仓库拷贝 web/——go get 本模块后直接挂载即可,做到前后端都「go get 开箱」:
mux.Handle("/captcha/", http.StripPrefix("/captcha/", http.FileServer(http.FS(captcha.Assets()))))
// → /captcha/altcha-login.js, /captcha/altcha-login.css
前端页面据此引用:
<link rel="stylesheet" href="/captcha/altcha-login.css"> <script src="/captcha/altcha-login.js"></script>
func AssetsHandler ¶
AssetsHandler 是一个直接发前端套件的 http.Handler(你自己负责 strip 前缀):
mux.Handle("/captcha/", http.StripPrefix("/captcha/", captcha.AssetsHandler()))
func ClientIP ¶ added in v1.1.0
ClientIP 从请求里取客户端 IP,作为 Gate 的 key 来源:优先 X-Forwarded-For 的首跳 (信任反代时),否则用 RemoteAddr。⚠️ 仅在你的反代会设置/覆盖 XFF 时才信任它, 否则客户端可伪造——不确定就别用 XFF、直接用 RemoteAddr 或改用账号做 key。
func Solve ¶
Solve 解开一道挑战(ChallengeHandler 输出的 JSON),返回与前端 widget 等价的 base64 解答载荷——可直接喂给 Verify。
仅供消费者写**后端集成测试**:生产中由浏览器 widget 解题,无需调用本函数。 有了它,消费者无需直接 import 二级依赖 altcha-lib-go、也无需手拼 payload 结构, 就能跑完整的「取题 → 解 → 校验」服务端冒烟:
chJSON := httptest 取到的 challenge 响应体 payload, _ := captcha.Solve(chJSON) ok := v.Verify(payload) // true
Types ¶
type Gate ¶ added in v1.1.0
type Gate struct {
// contains filtered or unexported fields
}
Gate 是「自适应」验证码门:平时无感放行,只有某个 key(IP / 账号 / IP+账号)在窗口内 登录失败累计到阈值后,才**要求**该 key 带上通过校验的验证码。登录成功即清零。
它把 ALTCHA 从「每次都验」变成「可疑才验」——上游 altcha 库不提供这层,由本包补。 状态在内存、按 key 计数、并发安全、过期自动清理(无需后台 goroutine / Close)。
典型登录处理(key 可用 ClientIP(r),或 ip+":"+email):
if !gate.Pass(key, req.Altcha) { // 需要验证码且没过 → 拒
writeJSON(w, 400, gin.H{"error": "人机验证失败", "need_captcha": true})
return
}
if !passwordOK { // 口令错
gate.Failed(key)
writeJSON(w, 401, gin.H{"error": "邮箱或密码错误", "need_captcha": gate.Required(key)})
return
}
gate.Succeeded(key) // 成功 → 清零
前端据响应里的 need_captcha 决定是否显示 widget:平时不显示(无感),被标记后才显示。
func NewGate ¶ added in v1.1.0
func NewGate(o GateOptions) *Gate
NewGate 构造一个自适应门。Verifier 为 nil 会 panic(必填)。
func (*Gate) Pass ¶ added in v1.1.0
Pass 报告该 key 这次请求是否「过了验证码这一关」: 不需要验证码 → 直接 true;需要 → 取决于 payload 是否通过 Verify。 在查库 / 比对口令之前调用。注意:它只管验证码,口令对错由你后续判断并回写 Failed/Succeeded。
func (*Gate) Required ¶ added in v1.1.0
Required 报告某 key 当前是否需要验证码(失败计数已达阈值且未过窗)。 用它给前端下发 need_captcha,或做登录前预检。
func (*Gate) RequiredHandler ¶ added in v1.1.0
RequiredHandler 是给前端的预检端点:GET 返回 {"required": bool}, key 由你给的 keyFn 从请求里取(通常用 ClientIP)。前端据此决定是否显示 widget。
mux.Handle("GET /auth/captcha/required", gate.RequiredHandler(func(r *http.Request) string {
return captcha.ClientIP(r)
}))
type GateOptions ¶ added in v1.1.0
type GateOptions struct {
// Verifier 是底层校验器(必填)。其 HMACKey 为空(关闭态)时 Pass 仍恒放行。
Verifier *Verifier
// Threshold:同一 key 在窗口内失败达到此次数后开始要求验证码。0 = 默认 3。
Threshold int
// Window:失败计数的有效窗口;超窗自动清零(也是「冷却」时间)。0 = 默认 15m。
Window time.Duration
}
GateOptions 配置 Gate。
type Options ¶
type Options struct {
// HMACKey 用于签发挑战、校验解答(同一把 key 两用)。空 = 关闭校验。
// 生成示例:openssl rand -base64 32。务必保密、各副本一致。
HMACKey string
// TTL 是一道挑战的有效期。登录页加载即取题并解题,30 分钟足够覆盖正常填表,
// 也是这里唯一的防重放手段(登录另有密码兜底,重放一道已解挑战价值很低)。
// 0 = 用默认 30m。
TTL time.Duration
// MaxNumber 是 PoW 搜索空间(难度)。0 = 默认 1_000_000(现代浏览器亚秒级解出)。
MaxNumber int64
// SingleUse 开启「单次失效」:一道解答只接受一次,TTL 内再次提交即拒。
// 默认 false(沿用 ALTCHA 原生语义——一道解答在 TTL 内可重复提交)。
//
// 为何需要:不开启时,攻击者解一次 PoW 就能用同一个 payload 在整个 TTL(默认 30m)内
// 反复提交,把暴破成本摊薄到接近零;开启后每次提交都得是一道**没用过的**新解,
// 把成本抬回「每次尝试都要重解一次 PoW」。
//
// ⚠️ 调用方配合:开启后,调用方(前端)必须在**每次登录失败后让 widget 重新取题求解**,
// 否则正常用户的重试会带着同一个已用过的解答、被判重放而失败。配 auto:'onload' 自动重解
// 体验最佳(用户无感)。
SingleUse bool
}
Options 配置一个 Verifier。
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier 签发并校验 ALTCHA 挑战。用 New 构造,并发安全。
func (*Verifier) ChallengeHandler ¶
func (v *Verifier) ChallengeHandler() http.HandlerFunc
ChallengeHandler 返回一个公开的 GET 处理器:每次签发一道新挑战,以 JSON 返回, 并带 Cache-Control: no-store(验证码挑战绝不能被浏览器/代理缓存)。
挂在一个无需鉴权的路径上,前端 widget 的 challenge URL 指向它。标准库:
mux.Handle("GET /auth/altcha/challenge", cap.ChallengeHandler())
gin:
r.GET("/auth/altcha/challenge", gin.WrapF(cap.ChallengeHandler()))
func (*Verifier) Mount ¶
Mount 把 ChallengeHandler 注册到 mux 的 path 上(便捷封装)。
cap.Mount(mux, "/auth/altcha/challenge")
func (*Verifier) NewChallenge ¶
NewChallenge 签发一道新挑战。即便未开启校验也照常出题(用空 key 签名,widget 仍能解)。 一般用不到——多数场景用 ChallengeHandler 直接挂端点即可。