我第一次往 GitHub 上推代码的时候,卡了整整一个下午。项目写好了,git init跑了,commit也做了,结果倒在最后一步——git push。弹窗让我输账号密码,输完告诉我认证失败;换成 SSH 又说 Permission denied;最后误打误撞用了 Token 方式推上去了,结果第二天再 push 又提示 token expired。当时我一度以为 GitHub 在针对我。后来把 SSH 和 Token 两条路从头到尾都走了一遍,才明白每个报错背后都是一个非常具体的原因,而且绝大多数坑,踩过一次之后基本不会再踩第二次。
这篇文章不打算写成那种"复制粘贴就能用"的流水账教程,我会把两种上传方式各自的原理、操作链路、常见报错的完整排查思路都讲清楚,适合刚接触 Git 和 GitHub 的开发者,也适合那些已经会 push 但每次遇到认证问题都要现搜解决方案的人。看完你应该能彻底理解:为什么要用 SSH、为什么要用 Token、什么时候选哪个,以及遇到报错时怎么一步步定位问题。
1. SSH 与 Token:先弄懂 GitHub 认证的底层差异
1.1 为什么密码认证已经被 GitHub 废弃
很多人第一次用 GitHub 时会觉得很奇怪:为什么git push的时候输入账号密码总是失败?原因很简单——GitHub 在 2021 年 8 月 13 日正式停止了对账户密码的 Git 操作认证支持。这不是临时策略,而是永久性的变更。所以你在网上搜到的老教程里写"输入 GitHub 账号和密码就能 push",那些内容已经过时了,照着做必然会踩坑。
GitHub 官方给出的替代方案就是今天要讲的两种方式:基于 SSH 密钥的认证,以及基于 Personal Access Token(个人访问令牌)的 HTTPS 认证。理解这两种方式的底层逻辑,比死记命令重要得多。因为只有理解了原理,你才能在任何一台新电脑上快速配置,而不是永远靠复制粘贴别人的命令碰运气。
1.2 SSH 的公私钥模型和日常类比
SSH 认证的核心是非对称加密。你会在本地生成一对密钥:一个公钥、一个私钥。公钥可以理解为一把锁,你把它放到 GitHub 服务器上;私钥是唯一能打开这把锁的钥匙,必须好好保存在你自己的电脑里。
每次通过 SSH 连接 GitHub 时,服务器会发送一个随机挑战,你的电脑用私钥对它进行签名,GitHub 用你预先上传的公钥来验证签名。验证通过,就确认"确实是本人",连接建立成功。整个过程不需要传输密码,私钥永远不会离开你的电脑。
生活里最常见的类比就是门锁和钥匙:公钥就是锁芯,任何人往 GitHub 上挂一把锁都不怕被拿走,锁本身不泄露任何敏感信息;私钥是钥匙,钥匙丢了别人就能进你家门,所以私钥文件一定要保护好。SSH 密钥里还有一个可选的 passphrase(口令短语),相当于给钥匙再加一道保险,即使私钥文件被别人拷走了,没有 passphrase 也解不开。
1.3 Token 的设计思路和常见误区
Token 的思路和 SSH 完全不同。Token 本质上是一串具有特定权限的授权字符串,它有点像是你住酒店时前台给的那张房卡——只能打开你被允许进入的房间(对应仓库),并且通常有一个有效期,可以随时作废。
你在 GitHub 后台生成 Token 时,需要勾选它拥有哪些权限,比如读写仓库、删除仓库、操作 workflow 等。这样做的好处是权限最小化:某个 Token 泄露了,只要去后台把那个 Token 删掉,其他 Token 不受影响,你的账号密码也不会暴露。
很多人对 Token 有两个误区:第一,以为 Token 就是密码。不是的,Token 在使用时是作为密码字段输入的,但它不是账号密码,它拥有独立的权限范围和生命周期。第二,以为 Token 配置一次就一劳永逸。Token 会过期,过期后需要重新生成并更新配置(Git 凭据管理器里的记录也要更新),这是它和 SSH 密钥在使用体验上最大的区别。
2. 本地环境准备:Git 安装、身份配置与仓库初始化
2.1 三个平台安装 Git 的关键点
系统里还没有 Git 的话,先把环境搭好。Windows 用户直接去 git-scm.com 下载安装包,一路 Next 就行。安装过程中有两个选项稍微留意一下:
- 选择默认编辑器:默认是 Vim,不熟悉 Vim 的可以改成 VS Code 或者 Notepad++,不然以后写 commit message 时误入 Vim 会一脸懵。
- 调整 PATH 环境变量:选默认的"Git from the command line and also from 3rd-party software"就好,这样 Git 可以在 CMD、PowerShell、Git Bash 里都能用。
macOS 用户最简单的方式是brew install git,Linux 用户根据发行版用apt install git或者yum install git。装完用一个命令验证:git --version,能输出版本号就说明安装成功了。
Windows 上安装完 Git 之后,强烈建议用Git Bash来执行本教程里的命令,而不是用原生的 CMD 或 PowerShell。原因很简单:Git Bash 模拟了 Linux 环境,ls、cat、pbcopy之类的命令行为和我们平时看过的绝大多数教程一致,踩坑概率小很多。
2.2 全局身份配置与常见疑问
Git 安装好之后,第一步是配置你的身份信息,否则 commit 的时候 Git 不知道你是谁:
git config --global user.name "你的名字" git config --global user.email "你的邮箱"这里的邮箱建议和 GitHub 账号上绑定的邮箱保持一致。如果不一致,你的 commit 在 GitHub 上可能不会关联到你的账号,提交列表里显示的是灰色匿名头像。很多人忽略这个小细节,结果提交了一堆代码,GitHub 贡献图上却什么都没有。
配置完成可以用git config --list查看当前所有配置。这里有个容易弄混的点:这个user.name和user.email是 Git 提交时用于记录作者信息的,和 GitHub 登录账号、和后面要讲的 SSH key 没有直接关系。
2.3 GitHub 上新建仓库,注意三个坑
登录 GitHub 后点击右上角的 "New repository",填写仓库名,选择 Public(公开)或 Private(私有)。这里有一个新手非常容易踩的坑:如果你的本地项目已经存在,不要在创建仓库时勾选 "Add a README file"、"Add .gitignore" 或 "Choose a license"。
为什么?因为一旦勾选,GitHub 会在远端仓库里生成一个初始化的 commit。本地项目此时也有自己的 commit,两边都有代码但历史不关联,接下来 push 时 Git 会拒绝合并,提示"远端包含你本地没有的工作",然后你就得先git pull --rebase origin main再 push。对新手来说,这又是一道坎。最简单粗暴的办法:本地项目已经有了,就创建一个纯空白仓库,一个文件都不要勾,创建完直接把地址复制下来就行。
如果远端仓库不小心勾了 README,解决办法也不复杂:
git pull --rebase origin main git push -u origin main--rebase会把你的本地提交变基到远端提交之上,让历史保持线性,看起来比较清晰。
3. SSH 上传:密钥生成到 push 成功的完整链路
3.1 生成密钥:ed25519 优于 rsa 的理由
SSH 方式的完整链路第一步,是在本地生成一对密钥。打开 Git Bash 或终端,执行:
ssh-keygen -t ed25519 -C "你的邮箱"参数解释一下:-t指定密钥类型,ed25519是目前推荐使用的算法,比传统的 RSA 更安全、密钥更短、生成速度也更快。GitHub 官方文档也是推荐使用 ed25519。只有当你需要连接一些老旧的服务器(比如某些还不支持 ed25519 的 Linux 机器)时,才考虑用ssh-keygen -t rsa -b 4096生成 RSA 密钥。
执行过程中会让你选择密钥保存位置,默认是~/.ssh/id_ed25519,直接回车即可。接着会提示输入 passphrase,这相当于给私钥加一层保护口令,可以留空直接回车。从安全角度建议设置一个,但从使用便捷角度,很多个人开发机器为了方便会留空。我个人的建议是:个人电脑上日常开发可以留空,公司电脑或多人共用的机器上务必设置 passphrase。
密钥生成后,~/.ssh目录下会出现两个文件:
id_ed25519:私钥,绝对不能泄露,也不能提交到任何代码仓库。id_ed25519.pub:公钥,可以放心上传到 GitHub。
3.2 将公钥注册到 GitHub
先查看公钥内容:
cat ~/.ssh/id_ed25519.pub输出是一长串以ssh-ed25519开头、以你的邮箱结尾的字符串。复制它,然后去 GitHub 页面:右上角头像 → Settings → SSH and GPG keys → New SSH key。Title 随便填一个方便自己识别的名字,比如 "My MacBook Pro",Key 区域粘贴刚才复制的公钥,最后点击 Add SSH key 即可。
如果用的是 Windows Git Bash,复制公钥更方便的方式是:
clip < ~/.ssh/id_ed25519.pub3.3 Windows 用户最常踩的 config 权限坑
公钥注册完之后,先做一个连接测试:
ssh -T git@github.com第一次执行会提示确认主机的指纹,输入yes回车。如果看到类似这样的输出,说明 SSH 认证链路已经通了:
Hi 你的用户名! You've successfully authenticated, but GitHub does not provide shell access.但很多 Windows 用户在这一步会遇到一个极其折磨人的报错:
Bad owner or permissions on C:\Users\xxx/.ssh/config这个报错的意思是:你~/.ssh目录下的config文件权限配置不正确。Windows 的 OpenSSH 对配置文件有严格的权限要求:config 文件只能由当前用户完全控制,不能有其他账户的访问权限。这个坑通常出现在你从别的电脑拷贝 config 文件过来、或者用 Git 从仓库里同步了 config 文件的情况下——文件的属主和权限继承了原来的使用者,Windows 检查时就不认账了。
解决方法有两个:
方式一,如果 config 文件里没有重要内容,直接删掉或重命名,问题立刻解决。很多人根本不需要配置文件,里面顶多有一两行残留,删了不影响使用。
方式二,如果 config 里确实有多个平台的主机配置,需要手动修复权限:右键点击 config 文件 → 属性 → 安全 → 高级 → 点击"禁用继承" → 在弹出的窗口选择"将已继承的权限转换为此对象的显式权限" → 然后逐个删除除了当前用户以外的所有条目 → 确定。完成后当前用户应该拥有完全控制权限,重新执行ssh -T git@github.com验证即可。
3.4 关联远程仓库并完成首次 push
SSH 测试通过后,剩下的就是常规 Git 操作了。进入本地项目目录,关联远程仓库:
git remote add origin git@github.com:你的用户名/你的仓库名.git注意这里的地址用的是 SSH 格式,以git@github.com:开头,而不是https://github.com/。你可以在 GitHub 仓库页面的 Code 按钮下拉菜单里切换到 "SSH" 标签,直接复制对应地址。
接下来把本地代码推上去:
git add . git commit -m "Initial commit" git branch -M main git push -u origin maingit branch -M main是把当前分支重命名为main,因为 GitHub 默认主分支叫main而不是master,这一步能避免分支名不一致带来的困惑。-u参数把本地分支和远程分支建立关联,以后直接git push就能推送。
首次 push 之后,SSH 方式的好处就体现出来了:以后每次git push都不需要再输入任何账号密码或令牌,体验和无密码一样顺滑。
4. Token 上传:Personal Access Token 的创建与使用
4.1 Token 的创建步骤与 scope 选择
Token 方式的完整链路,第一步是在 GitHub 后台生成一个 Personal Access Token。路径是:GitHub 右上角头像 → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token。
GitHub 目前提供两种 Token:Tokens (classic)和Fine-grained personal access tokens。Classic 是传统方式,权限粒度相对粗;Fine-grained 是后来推出的新版本,可以精确到某个仓库、某种操作,还可以设置更灵活的有效期。对普通个人项目来说,用 classic 就够了。
创建时需要填写这些字段:
| 字段 | 说明 | 建议 |
|---|---|---|
| Note | Token 的名称备注 | 写清楚用途,比如 "my-laptop" |
| Expiration | 有效期 | 建议 30 天到 90 天,尽量短 |
| Select scopes | 权限范围 | 至少勾选repo(完全控制私有仓库) |
如果你的项目仓库是 Public 且只需要拉代码(clone),其实不需要任何 scope;如果你需要 push 代码,无论公开还是私有仓库,repo这个 scope 都要勾上。如果你用 GitHub Actions 且 workflow 里需要向仓库写入文件,还需要额外勾选workflow。
一个必须强调的细节:Token 只会完整显示一次。生成之后页面会显示一段字符串,关闭页面或刷新之后就再也看不到了。所以生成后要立刻复制,保存到密码管理器或本地安全的位置,不要提交进代码仓库。
4.2 三种使用 Token 的方式与安全对比
Token 拿到之后,使用方式有三种,安全程度不一样。
第一种,把 Token 直接拼在 remote URL 里:
git remote add origin https://用户名:TOKEN@github.com/用户名/仓库名.git这种方式最直接,但也是最不推荐的——Token 会出现在git remote -v的输出里,也会出现在 shell 历史记录中(如果直接粘贴执行的话)。一旦这个项目被分享截图、上传日志,Token 就等于泄露了。
第二种,使用普通 HTTPS 地址,push 时按提示输入:
git remote add origin https://github.com/用户名/仓库名.git git push -u origin main执行 push 后 Git 会提示输入用户名和密码,用户名填 GitHub 用户名,密码字段粘贴 Token(不是你的登录密码)。输入成功后,Git 会把凭据保存到系统凭据管理器里(Windows 凭据管理器 / macOS 钥匙串),下次 push 不再弹出输入框。
第三种,手动配置 credential helper:
git config --global credential.helper store这个配置会把凭据以明文形式保存到~/.git-credentials文件里。Windows 上更推荐用manager作为 helper(默认配置),它把凭据保存在 Windows 凭据管理器中,安全性更好。
对比来看,我个人的建议是:日常个人开发用第三种方式中的manager(Windows 默认就是),配合短有效期的 Token,既能自动记住凭据,又能在 Token 泄露时将损失控制在一定范围内。
4.3 高频 Token 报错与处置方案
Token 方式最常见的几个报错,这里逐个拆解:
报错一:remote: Support for password authentication was removed.
这个报错说明你输入的是账号密码而不是 Token。2021 年之后 GitHub 只接受 Token 或 SSH key 作为 Git 操作认证凭据。解决方案:在密码框里粘贴 Token,或者改用 SSH 方式。
报错二:remote: Permission to user/repo.git denied to xxx
大概率是 Token 的权限范围不够。检查一下生成 Token 时是否勾选了repo这个 scope,以及 Token 是否已经过期。如果 Token 是在另一个账号下生成的,也会出现类似提示。
报错三:Your access token could not be refreshed. Please log out and sign in again.
这个报错主要出现在 VSCode 或 GitHub Desktop 这类工具里。意思是缓存在本地的认证信息已经失效,Token 无法自动刷新了。解决办法:在 VSCode 里按Ctrl+Shift+P,输入GitHub: Sign out退出登录,然后再GitHub: Sign in重新走一遍浏览器授权流程。
报错四:sign-in could not be completed token exchange failed: token endpoint returned status 403 forbidden: country
这类报错通常和当前网络环境有关——GitHub 的 Token endpoint 做了区域风控,当出口 IP 所在地属于被限制区域时会返回 403。遇到这个问题,先确认浏览器能否正常打开 GitHub 并登录,如果浏览器可以但工具不行,尝试清掉 VSCode 的缓存登录态后重新走浏览器认证。
5. push 失败排障实录:一条完整排查链路的复盘
5.1 从"输入密码失败"到"改用 Token"的完整经过
这一节用一条真实的场景线索,把上面提到的坑串起来走一遍,让你了解出问题时应该怎么思考。
假设你今天刚在新电脑上配置好 Git,项目写完了,执行git push,Git 弹窗要求输入用户名和密码。你输入了 GitHub 的登录密码,结果报错:
remote: Support for password authentication was removed. Please use a personal access token instead.这时你的第一反应不应该是"换个密码再试",而是应该意识到:GitHub 不再支持密码认证了。正确路径是二选一:要么走 SSH 方式,要么用 Token。如果你现在时间紧,想快点把代码推上去,最快的办法是去后台生成一个 Token,用上文的第二种方式输入进去。这一步能立刻解决问题,适合临时应急。
但要记住,Token 方式推上去之后,你不应该就此打住。因为 Token 有有效期,假设你生成了 90 天有效的 Token,三个月之后你再次 push 时,Git 会提示认证失败,你可能会陷入"为什么昨天还好好的今天不行了"的困惑。
5.2 第二天 push 又失败:从 Token 过渡到 SSH
接上面的场景。假如你当时为了方便把 Token 粘贴进 remote URL,那么下一次 push 可能也会出问题——不是 Token 过期了,而是你的 remote URL 里带着一个已经失效的 Token。每次 push 都尝试用这个死掉的 Token 去认证,自然一直失败。
这时候正确操作是:先把 remote 重置为干净的 HTTPS 地址,然后重新触发认证流程:
git remote set-url origin https://github.com/用户名/仓库名.git git push随后按提示输入新的用户名和 Token,Git 会重新把凭据保存到本机。如果你发现每次 push 都弹框、且输入正确 Token 仍然失败,可以去 Windows 凭据管理器里找到git:https://github.com相关的记录,手动删掉旧的凭据,再重新 push 输入一次。
在这个反复和 Token 搏斗的过程中,很多人会意识到:SSH 方式一旦配置好,就没有这些续期、失效、刷新的问题。于是你决定切换到 SSH。
5.3 SSH 权限报错的根因定位路径
切换到 SSH 方式,你按部就班地生成密钥、注册公钥,然后执行ssh -T git@github.com测试,结果报错:
Permission denied (publickey).这时候不要慌,按下面的顺序排查:
第一步,确认 ssh-agent 里有没有加载私钥:
eval "$(ssh-agent -s)" ssh-add -l如果输出是The agent has no identities.,说明私钥没有加载,执行ssh-add ~/.ssh/id_ed25519加载。
第二步,用调试模式看看具体卡在哪一步:
ssh -vT git@github.com输出里会显示它尝试读取了哪些 key 文件、服务器返回了什么信息。如果看到load pubkey "/c/Users/xxx/.ssh/id_ed25519": invalid format,说明密钥格式有问题,重新生成一对密钥即可。
第三步,如果报错是前面提过的Bad owner or permissions on C:\Users\xxx/.ssh/config,先按 3.3 节的权限修复方式处理,再测试。
把这条路径走通之后,SSH 的git push才能像"无感"一样顺畅。整个过程本质上是:先确认认证链路的每一环都通畅,再去执行业务操作。很多新手之所以被这类问题折磨,是因为跳过了中间验证步骤,直接在git push失败后开始乱改配置。
5.4 高频 git push 报错速查表
| 报错信息 | 常见原因 | 解决方向 |
|---|---|---|
| Permission denied (publickey) | SSH 私钥未加载或公钥未注册 | 用ssh-add -l和ssh -T git@github.com逐段排查 |
| Repository not found | 仓库名写错 / 没有该仓库权限 / 账号不对 | 检查 remote URL 拼写、账号身份 |
| Updates were rejected | 远端有本地没有的提交 | 先git pull --rebase origin main再 push |
| Support for password authentication was removed | 输入了密码而不是 Token | 改用 Token 或 SSH 认证 |
| token expired | Token 超期 | 去 GitHub 后台重新生成 Token 并更新凭据 |
| fatal: The current branch master has no upstream branch | 没建立本地分支和远端分支的关联 | 用git push -u origin master或git push -u origin main |
这张表建议收藏。每一个报错背后都有明确的触发条件,对照排查比凭空猜测高效得多。
6. SSH 还是 Token:我的选型方法和长期习惯
6.1 两种方式的核心对比
两种方式没有绝对的优劣,关键是匹配使用场景。一句话总结:SSH 适合长期、高频、个人电脑;Token 适合临时、多变、共享环境。
| 维度 | SSH 密钥 | Token |
|---|---|---|
| 配置复杂度 | 首次配置稍复杂(生成密钥、注册公钥、测试连接) | 创建简单但每隔一段时间要更换 |
| 使用体验 | 配置好后长期无感,push 无需任何输入 | 过期后需重新生成和更新凭据 |
| 安全模型 | 私钥文件保护,可以设 passphrase | Token 可设权限范围和有效期,可即时作废 |
| 泄露风险 | 私钥泄露影响大,需要重新生成一对 | 单点泄露影响范围小,删掉该 Token 即可 |
| 适用场景 | 自己的电脑、长期项目 | 公司机器、公用机器、CI/CD 自动化 |
从 GitHub 官方角度,两种方式都完全支持。你完全可以根据自己的情况混用:比如个人电脑用 SSH,公司电脑用 Token。
6.2 按场景选型的四条建议
第一,个人开发机、主力电脑,我建议使用 SSH。配置一次大约 10 分钟,之后每天几十次 push 都不用想认证的事。这也是我自己最常用的方式。
第二,公司分配的电脑,我建议使用 Token + 短有效期(比如 30 天),因为公司电脑可能存在安全合规要求,不适合把私钥长时间留在机器上,而且离职时收回权限也方便。
第三,CI/CD 自动化部署场景,Token 是主流方案,但更推荐使用 GitHub 官方的 Deploy Key 或者直接在 Actions 里用内置的 secrets 机制,避免把 Token 写死在配置文件里。
第四,维护多个代码托管平台的开发者,可以在~/.ssh/config里为不同平台配置不同的私钥文件,用 Host 别名做区分。比如 GitHub 用默认密钥,Gitee 用另一把密钥。这也是 SSH 方式的一个隐藏优势——一个机器管理多个平台账号非常清晰。
6.3 一些日常使用的小习惯
最后分享几个我在实际使用中养成的小习惯,每一个都有过教训:
Token 一定要进密码管理器。我有个朋友把 Token 直接写在项目根目录的 README 里(用的时候图方便),结果仓库公开后 Token 被爬虫抓走,GitHub 直接发了安全告警邮件。Token 泄露之后唯一补救措施就是立刻吊销并重新生成,整个流程非常麻烦。别图省事,密码管理器不贵也不难用。
不定期检查 remote URL。执行git remote -v,确认 remote 地址里没有拼接 Token。如果发现以前手误把 Token 加进去了,用git remote set-url origin立刻改回干净地址,然后再吊销那个 Token 重新生成。
push 之前想清楚再 commit。热搜词里经常会有人搜git commit --amend,确实也是一项实用操作——当你发现上一次 commit 的 message 写错了或者漏掉了某个文件,可以在 push 之前用:
git commit --amend -m "新的提交信息"来修改最近一次提交。但要注意,这条命令只能改还没有 push 出去的提交;一旦已经推送到远端,amend会导致本地历史与远端不一致,再 push 就需要强制推送,会带来不少麻烦。
我现在所有个人项目的远程仓库统一走 SSH,公司的机器上则配了有效期很短的 Token,每次换新环境都从头走一遍完整链路。坦白说,第一次配 SSH 花了一个下午,第二次在新电脑上配只花了十分钟,再后来已经完全变成了肌肉记忆。这也是我为什么花这么多篇幅把原理讲清楚的原因——你真正理解了 SSH 和 Token 各自的运作方式、适用边界和常见坑,以后遇到任何认证相关的问题,都能快速定位到对应的环节,而不是在报错信息面前干瞪眼。