Skip to content

密码管理

在 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 块:

yaml
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。

基于文件的持久化

yaml
authentication:
  method: in_memory
  user_config_path: /shared/path/to/user_config.jsonl

当多个服务实例共用一份用户名单,或者你想把名单放在标注结果之外的地方时,就指定一个路径。新注册和密码变更会随时写入。

把用户名单和标注输出放在一起。 标注者的目录以用户名命名,所以名单丢了而输出目录还在的研究,下一个输入该用户名的人就会继承别人的工作。Potato 会拒绝这次注册,这也意味着在你恢复该文件之前,那位标注者进不来。

在 v2.8.3 之前,除非显式设置了 user_config_path,否则 in_memory 什么都不写,账号也就撑不过一次重启。如果你在更早的版本上跑过研究,那些账号已经没了;标注结果还在。

数据库后端

SQLite(不需要额外依赖):

yaml
authentication:
  method: database
  database_url: "sqlite:///auth/users.db"

PostgreSQL(需要 psycopg2-binary):

yaml
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

从命令行重置密码:

bash
# Prompts for username and password
potato reset-password config.yaml
 
# Prompts for the password only
potato reset-password config.yaml --username annotator1

管理员 API

用管理员 API 密钥以程序方式重置:

bash
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?” 链接。

管理员也可以直接签发令牌:

bash
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)的任务,可以完全关掉密码:

yaml
require_password: false
 
authentication:
  method: in_memory

标注者输入任意用户名即可登录,不会出现密码输入框。涉及敏感数据或需要验证身份的任务不建议这样做。

详见免密登录。

完整参考

yaml
# 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-passwordGET, POST无申请重置令牌
/reset/<token>GET, POST无设置新密码
/admin/reset_passwordPOSTAPI 密钥管理员重置密码
/admin/create_reset_tokenPOSTAPI 密钥生成重置令牌

延伸阅读

实现细节见源文档。