密码管理
在 Potato 中配置 PBKDF2-SHA256 密码哈希、管理员 CLI 与 API 重置、用户自助的令牌重置链接,以及 SQLite 或 PostgreSQL 凭据存储。
v2.4.0 新增
Potato 的认证系统使用 PBKDF2-SHA256,迭代 10 万次,并为每个用户配一个独立的 salt,这也是 NIST 推荐的密码存储做法。本页讲的是密码如何存储、如何重置,以及怎样让凭据在服务器重启后继续有效。
这些内容适用于 in_memory 和 database 两种方法。在 OAuth 下密码归身份提供方管,所以除了混合模式里的本地账号之外,本页内容都不适用。
安全实现
密码以 salt$hash 的格式存储:
- 32 个字符的十六进制 salt,每个用户各不相同
- 64 个字符的十六进制摘要,由 PBKDF2-HMAC-SHA256 以该 salt 对密码迭代 100,000 次导出
- 通过
hmac.compare_digest做恒定时间比较,以防时序攻击
迭代次数才是关键。对加了 salt 的密码只做一次 SHA-256 运算,快到足以被大规模暴力破解,所以这里的导出过程要跑 100,000 次。
user_config.json 文件里已有的明文密码会在 Potato 加载时自动用各自独立的 salt 重新哈希,不需要手动迁移。
默认配置
Potato 默认使用内存认证。require_password 是顶层键,不属于 authentication 块:
require_password: true
authentication:
method: in_memory
user_config:
users:
- username: "annotator1"
password: "initial-password" # will be hashed on first load
- username: "annotator2"
password: "initial-password"持久化凭据
通过注册表单创建的账号会写入一个 JSONL 文件,并在下次启动时读回来。在 in_memory 下,除非你另外指定,这个文件就是输出目录旁边的 user_config.json。
基于文件的持久化
authentication:
method: in_memory
user_config_path: /shared/path/to/user_config.jsonl当多个服务实例共用一份用户名单,或者你想把名单放在标注结果之外的地方时,就指定一个路径。新注册和密码变更会随时写入。
把用户名单和标注输出放在一起。 标注者的目录以用户名命名,所以名单丢了而输出目录还在的研究,下一个输入该用户名的人就会继承别人的工作。Potato 会拒绝这次注册,这也意味着在你恢复该文件之前,那位标注者进不来。
在 v2.8.3 之前,除非显式设置了 user_config_path,否则 in_memory 什么都不写,账号也就撑不过一次重启。如果你在更早的版本上跑过研究,那些账号已经没了;标注结果还在。
数据库后端
SQLite(不需要额外依赖):
authentication:
method: database
database_url: "sqlite:///auth/users.db"PostgreSQL(需要 psycopg2-binary):
authentication:
method: database
database_url: "postgresql://user:password@localhost:5432/potato_auth"Potato 会在首次启动时创建 users 表,并让 SQLite 以 WAL 模式运行,以便更好地支持并发读取。database_url 必须以 sqlite:/// 或 postgresql:// 开头。你也可以在环境中设置 POTATO_DB_CONNECTION,两者同时存在时 database_url 优先。
注意:method: database 和 user_config_path 互斥,只能选一种持久化方案。两者都配置时 Potato 会报错。
重置密码
管理员 CLI
从命令行重置密码:
# Prompts for username and password
potato reset-password config.yaml
# Prompts for the password only
potato reset-password config.yaml --username annotator1管理员 API
用管理员 API 密钥以程序方式重置:
curl -X POST http://localhost:8000/admin/reset_password \
-H "X-API-Key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username": "annotator1", "new_password": "new-secure-password"}'密钥按这个顺序取:顶层的 admin_api_key 设置、POTATO_ADMIN_API_KEY 环境变量,或者任务目录下自动生成的 admin_api_key.txt。
用户自助的令牌重置
忘了密码的标注者可以自己重置。他们访问 /forgot-password 并输入用户名,Potato 会生成一个有效期 24 小时的一次性令牌。重置链接显示在页面上,由管理员转交;Potato 不发邮件。标注者打开 /reset/<token> 设置新密码,令牌随之作废。
这个流程不需要任何配置。设置了 require_password: true 时,登录页上会出现一个 “Forgot Password?” 链接。
管理员也可以直接签发令牌:
curl -X POST http://localhost:8000/admin/create_reset_token \
-H "X-API-Key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username": "annotator1"}'
# {"reset_link": "http://localhost:8000/reset/abc123...",
# "token": "abc123...", "expires_in_hours": 24}想换一个过期时间,在请求体里传 "ttl_hours": 48。
免密模式
课堂演示、快速研究,或者使用外部认证(MTurk、Prolific)的任务,可以完全关掉密码:
require_password: false
authentication:
method: in_memory标注者输入任意用户名即可登录,不会出现密码输入框。涉及敏感数据或需要验证身份的任务不建议这样做。
详见免密登录。
完整参考
# Top-level: require a password at login (default: true)
require_password: true
# Top-level: key for the admin endpoints
admin_api_key: ${POTATO_ADMIN_API_KEY}
authentication:
# in_memory (default) | database | clerk | oauth
method: in_memory
# in_memory only, mutually exclusive with method: database
user_config_path: users.jsonl
# database only; sqlite:/// or postgresql://
# database_url: "sqlite:///auth.db"
user_config:
users:
- username: "researcher"
password: "secure-passphrase"
- username: "annotator1"
password: "initial-pass"| 端点 | 方法 | 认证 | 说明 |
|---|---|---|---|
/forgot-password | GET, POST | 无 | 申请重置令牌 |
/reset/<token> | GET, POST | 无 | 设置新密码 |
/admin/reset_password | POST | API 密钥 | 管理员重置密码 |
/admin/create_reset_token | POST | API 密钥 | 生成重置令牌 |
延伸阅读
- SSO 与 OAuth 认证 — 用 Google、GitHub 或机构 SSO 登录
- 免密登录 — 开放任务的纯用户名访问
- 生产环境部署 — HTTPS 与反向代理配置
- 管理后台 — 管理标注者账号
实现细节见源文档。