Git 的 HTTPS 凭据和 SSH 密钥

摘要

走哪一种由远程地址决定

GitHub 从 2021 年 8 月起,HTTPS 的 git push、git pull 不再接受账号密码,密码栏要填 Personal Access Token。每次手动粘贴令牌太麻烦,所以要么把令牌交给凭据助手保存,要么改用 SSH 密钥。

1
git remote -v

地址是 https://github.com/... 时走凭据助手。地址是 git@github.com:... 时走 SSH 密钥,不读凭据助手。同一台机器上两种可以并存,每个仓库按自己的远程地址选择。

不要把令牌写进地址

令牌也可以直接写在 HTTPS 地址里。@ 前面的用户名要填,GitHub 实际用令牌认证。这样 git 每次都带上令牌,不再提问:

1
git clone https://用户名:ghp_xxxxxx@github.com/用户名/仓库名.git

这种写法把令牌明文留在好几个地方:

  • 整条地址写入该仓库的 .git/config。打包或复制这个目录时,令牌一起带走。

  • git remote -v 会打印令牌原文,截图或贴日志时容易泄露。

  • 执行过的 git clone、git remote set-url 会留在 shell 历史里。

  • 令牌过期或被删除后,每个这样配置的仓库都要逐个改地址。凭据助手只要更新一条记录。

已经这样配置的仓库,把地址改回不带令牌的写法,再交给下面的凭据助手。这条改写该仓库的 .git/config。

1
2
git remote set-url origin https://github.com/用户名/仓库名.git
git remote -v

令牌已经出现在截图、日志或别人能读到的目录里时,只改地址不够。还要到 GitHub 删除这张令牌,重新生成一张,见 创建 GitHub Personal Access Token。

Git 2.37 起,可以禁止地址里带凭据。下面这条写入 ~/.gitconfig;之后 clone、fetch、push 遇到 remote.<name>.url 里有用户名和密码时,直接报错。取值 warn 只警告,默认的 allow 不检查。写在 remote.<name>.pushurl 里的凭据不在检查范围内。

1
git config --global transfer.credentialsInUrl die

HTTPS:凭据助手

令牌要有对应的权限

钥匙串里已经存了令牌,只说明 GitHub 认得出这张令牌。仓库操作还要看令牌上勾了什么。创建时怎么勾,见 创建 GitHub Personal Access Token。

  • 克隆、拉取、推送普通文件:细粒度令牌要有 Contents 的 write,只读仓库用 read。经典令牌勾 repo。

  • 推送 .github/workflows 下的文件:细粒度令牌还要 Workflows 的 write。经典令牌在 repo 之外再勾 workflow。

  • 推送被拒绝、报错里出现 workflow 或 403 时,先看这张令牌的权限。助手里有令牌,和这张令牌能改工作流文件,是两件事。

权限不够时,到 GitHub 上改这张令牌的权限,或另建一张再更新钥匙串。已经发出去的令牌字符串不会因为后来补勾权限而换掉。

选择助手

--global 写入 ~/.gitconfig,这个用户的所有仓库都会使用。设置后执行一次 git pull,按提示填写用户名和令牌;之后由助手提供,不再提问。

1
2
3
4
5
6
7
8
# macOS,存入钥匙串
git config --global credential.helper osxkeychain

# Linux,需要先安装 libsecret
git config --global credential.helper /usr/lib/git-core/git-credential-libsecret

# 用户名填 GitHub 用户名,密码填令牌
git pull

Windows 安装 Git Credential Manager 后,通常已经自动配置好助手。

store 把凭据以明文写进 ~/.git-credentials,任何能读这个文件的人都能拿到令牌。只在个人机器上、并且清楚这个风险时使用。

1
2
3
4
git config --global credential.helper store

# 首次输入用户名与令牌,之后从文件读取
git pull

从 store 换成钥匙串

git config --global credential.helper osxkeychain 只改 ~/.gitconfig 里的 credential.helper。~/.git-credentials 不会被删除或清空。助手改成 osxkeychain 之后,Git 不再读这个明文文件,但里面的令牌还在磁盘上。确认钥匙串里已经能推送之后,再自己删掉 ~/.git-credentials。

已有仓库不用逐个重配。钥匙串按协议和主机名保存,默认不区分仓库路径。同一个 https://github.com 只在钥匙串里还没有这项时问一次,之后这个主机上的其他 HTTPS 仓库共用这一条。Git 不会把 ~/.git-credentials 里的旧记录搬进钥匙串。

查看已保存的账号

钥匙串和 ~/.git-credentials 都要看。git credential-osxkeychain 不能列出全部记录。打开「钥匙串访问」,搜索 github.com(或其他主机名)。每条的帐户是用户名,勾选「显示密码」才看得到令牌。

知道主机名时,可以先只看用户名。带 -g 的那条,以及 get、cat,会把令牌打在终端上。

1
2
3
4
5
6
7
8
# 只打印 github.com 这条的用户名
security find-internet-password -s github.com | grep '"acct"'

# -g 打印密码,系统可能弹出授权框
security find-internet-password -s github.com -g

# 输出里有 username 和 password
printf "protocol=https\nhost=github.com\n\n" | git credential-osxkeychain get

store 留下的文件还在时,一行一条,格式是 https://用户名:令牌@主机:

1
cat ~/.git-credentials

gh auth status 只列出 gh 登录的账号和存放位置,不打印令牌。

清掉旧凭据

没有提问就报 Authentication failed 时,钥匙串里多半已有旧的 github.com 条目。清掉后再操作一次,就会重新提问。这条只删除钥匙串里的对应项,不动 ~/.git-credentials。

1
printf "protocol=https\nhost=github.com\n\n" | git credential-osxkeychain erase

和 gh 的关系

以前执行过 gh auth setup-git 时,github.com 的 HTTPS 可能仍由 gh 提供凭据,钥匙串那一条不会被用到。在仓库里看实际生效的助手和它所在的文件:

1
git config --show-origin --get-regexp 'credential\..*helper'

SSH:密钥

生成密钥

推荐 ed25519,密钥短、速度快。生成的是 ~/.ssh/id_ed25519(私钥)和 ~/.ssh/id_ed25519.pub(公钥)。已有同名文件时,命令会询问是否覆盖;覆盖后,旧公钥对应的服务器都要重新添加。

1
2
# -C 只是备注,便于在服务器上辨认这把密钥,不必是邮箱
ssh-keygen -t ed25519 -C "your_label"

服务器只支持 RSA 时:

1
ssh-keygen -b 4096 -t rsa -C "your_label"

把公钥加到服务器

把 ~/.ssh/id_ed25519.pub 的整行复制到服务器的 SSH 公钥设置页。私钥 ~/.ssh/id_ed25519 留在本机,不要上传。

  • GitHub:头像 → Settings → SSH and GPG keys → New SSH key。

  • Coding:个人设置 → SSH 公钥。

  • Gitee:设置 → 安全设置 → SSH 公钥。

逐个测试。GitHub 成功时输出 Hi 用户名! You've successfully authenticated。

1
2
3
ssh -T git@github.com
ssh -T git@e.coding.net
ssh -T git@gitee.com

账号密钥没有分项权限

加在个人账号 SSH and GPG keys 里的认证密钥,能做的事和这个账号在网页上能做的 Git 操作相同。账号对仓库有写权限,用这把密钥就可以 git push,包括 .github/workflows。密钥上不能再勾 Contents、workflow 这种分项。要按接口收权限,用 创建 GitHub Personal Access Token 里的令牌。

ssh-keygen 时设置的口令只锁本机私钥。GitHub 不读取这句口令,也不用它区分只读和可写。

只想让一把密钥访问一个仓库时,把公钥加到该仓库的 Settings → Deploy keys,不要加到个人账号。

  • 不勾 Allow write access:只能克隆和拉取。

  • 勾上之后:可以往这一个仓库推送。

  • 同一把公钥不能同时作为多个仓库的 Deploy key。

  • Deploy key 仍然只区分这一个仓库的只读和可写,不能拿来调 REST API。

使用 SSH 地址

新克隆时使用 git@ 开头的地址:

1
git clone git@github.com:hanqunfeng/reactive-redis-cache-annotation-spring-boot-starter.git

已经用 HTTPS 克隆的仓库,改远程地址即可。这条写入该仓库的 .git/config。

1
2
git remote set-url origin git@github.com:用户名/仓库名.git
git remote -v

ssh -T 能打出用户名,只说明这把密钥被服务器接受。推送仍然失败时,先看 git remote -v 的地址是不是 git@ 开头,以及这个账号对仓库有没有写权限。