bigywater.tech
Git · 75

credential helper 协议

git 在 HTTPS 上怎么拿到密码?credential helper 到底收到什么、返回什么、什么时候被通知保存和删除?这篇用 git credential 命令直接驱动 helper,再在本机起一个要求认证的 smart-http 服务,把整个认证流程走一遍。

get / store / erase
credential helper 要实现的三个命令,对应 git credential 的 fill / approve / reject
401 → get → store
真实 HTTP 认证里,git 先试一次无认证请求,收到 401 后才向 helper 要凭据,成功后再 store
明文 600
credential-store 把凭据以明文写进文件,只靠文件权限保护

协议

实测 · 本机执行输出
  git credential fill 的输出(host 等原样回显,helper 补上了 username 和 password):
    protocol=https
    host=example.com
    username=alice
    password=<已隐藏>
  helper 收到的调用(get)和它的 stdin:
    [lab] get
        stdin: protocol=https
        stdin: host=example.com
  helper 什么也不返回、又不允许提示终端时: 退出码 128;输出: fatal: could not read Username for 'https://example.com': terminal prompts disabled

git 与 helper 之间是极简的文本协议:stdin/stdout 上的 key=value 行,空行结束。helper 是 PATH 上的 git-credential-<名>。git credential fill 调用它的 get;没有 helper 能给出凭据又不允许提示终端时,fill 以 128 退出。

store 与 erase

实测 · 本机执行输出
    [lab] store
        stdin: protocol=https
        stdin: host=example.com
        stdin: username=alice
        stdin: password=<已隐藏>
    [lab] erase
        stdin: protocol=https
        stdin: host=example.com
        stdin: username=alice
        stdin: password=<已隐藏>

git credential approve 对应 helper 的 store(凭据可用,保存),reject 对应 erase(凭据被拒绝,删除)。get / store / erase 就是 helper 要实现的全部。

字段

实测 · 本机执行输出
  默认(不带 path):
    [lab] get
        stdin: protocol=https
        stdin: host=git.example.com
        stdin: username=bob
  credential.useHttpPath=true 之后:
    [lab] get
        stdin: protocol=https
        stdin: host=git.example.com
        stdin: path=org/repo.git
        stdin: username=bob

url 被拆成 protocol、host、username 等字段;默认不传 path,credential.useHttpPath=true 才传,用来区分同一主机上的不同仓库。新版 git 还会传 capability[] 和服务器的 wwwauth[] 质询。

真实流程

实测 · 本机执行输出
  正确的凭据:git ls-remote 退出码 0
  服务器看到的请求:
    GET /repo.git/info/refs auth=无 要求认证
    GET /repo.git/info/refs auth=有 通过
    POST /repo.git/git-upload-pack auth=有 通过
  helper 被调用:
    [lab] get
        stdin: capability[]=authtype
        stdin: capability[]=state
        stdin: protocol=http
        stdin: host=127.0.0.1:9811
        stdin: wwwauth[]=Basic realm="git"
    [lab] store
        stdin: protocol=http
        stdin: host=127.0.0.1:9811
        stdin: username=alice
        stdin: password=<已隐藏>
  helper 给了错误的密码:退出码 128;fatal: Authentication failed for 'http://127.0.0.1:9811/repo.git/'
  服务器看到的请求:
    GET /repo.git/info/refs auth=无 要求认证
    GET /repo.git/info/refs auth=有 拒绝
  helper 被调用(注意最后的 erase——认证被拒绝后 git 通知 helper 删掉这条凭据):
    [lab] get
        stdin: capability[]=authtype
        stdin: capability[]=state
        stdin: protocol=http
        stdin: host=127.0.0.1:9811
        stdin: wwwauth[]=Basic realm="git"
    [lab] erase
        stdin: protocol=http
        stdin: host=127.0.0.1:9811
        stdin: username=alice
        stdin: password=<已隐藏>
        stdin: wwwauth[]=Basic realm="git"

git 第一个请求不带认证,服务器回 401 后 git 才调用 helper 的 get,拿到凭据重发请求;成功就 store,被拒绝就 erase 并报 Authentication failed。

多个 helper

实测 · 本机执行输出
  第一个 helper(lab)没给凭据,第二个给了: protocol=https | host=example.com | username=备用用户 | password=<已隐藏>
    [lab] get
        stdin: protocol=https
        stdin: host=example.com
    [second] get
  approve 时(store)两个 helper 都被通知:
    [lab] store
        stdin: protocol=https
        stdin: host=example.com
        stdin: username=备用用户
        stdin: password=<已隐藏>
    [second] store

取凭据按配置顺序询问,第一个给出完整用户名和密码的赢;store 和 erase 通知所有 helper。

自带的 helper

实测 · 本机执行输出
  credential-store 写下的文件(明文!): https://alice:<密码>@example.com | 权限 600
  之后 fill 不再需要提示: protocol=https | host=example.com | username=alice | password=<已隐藏>
  reject 之后文件内容: ''

credential-store 以明文把 https://用户:密码@主机 写进文件(权限 600);credential-cache 放内存里并设超时;macOS 的 osxkeychain 用钥匙串。协议相同,安全性不同。

分步演示

credential helper
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。全部实验都在隔离的 HOME 和实验专用 helper 下进行,没有碰真实的钥匙串或 ~/.git-credentials;HTTP 服务是我写的几十行 Python,把请求交给 git http-backend 并在前面加 Basic 认证;helper 是记录型 shell 脚本;日志里的密码做了隐藏
    • 没有验证:credential-cache 的超时与守护进程;osxkeychain、Git Credential Manager 等真实 helper;OAuth/Bearer 方式(authtype 能力的实际用法);ssh 认证(与此协议无关);http.proactiveAuth;helper 的 quit 字段与多行属性的处理细节
    京ICP备2026067502号-1京公网安备11010502063487号