
Git Clone 报 Authentication failed这行红字干掉了多少人的周五下午我不太想统计。尤其是当你的仓库接入了 Authentik 做 SSO 单点登录之后Authentication failed 几乎成了必经之坑浏览器里能登录、能看到仓库命令行一拉就翻车而且翻得很彻底连个像样的提示都没有。这篇文章我把这个问题的来龙去脉、方案选型、Authentik 对接配置、命令行克隆的正确姿势以及我在生产环境里踩过的各种坑一次性讲清楚。适合刚把自建 Git 服务器接入 SSO、或者正在排查 git clone 认证问题的朋友对照着操作。1. 先还原现场这个报错到底发生在哪一步1.1 报错现象与典型环境这类问题最常见的现场是这样的团队把代码仓库从裸奔模式切到了 SSO 单点登录仓库系统用的 Gitea身份源用的是自建的 Authentik。一切在网页端都很正常打开 Gitea 登录页会跳转到 Authentik输入账号密码后回到仓库系统该看的代码、该提的 MR 都没问题。但到了终端气氛就开始不对了$ git clone https://git.example.com/team/project.git Cloning into project... Username for https://git.example.com: zhangsan Password for https://git.example.com: remote: Unauthorized fatal: Authentication failed for https://git.example.com/team/project.git有些环境还会直接给出大写的 Authentication failed看起来像是用户名密码输错了。但最迷惑人的是你把 Authentik 里的密码原封不动输进去它照样拒绝你。还有一种变体是仓库是公开的但你要 push 或者拉私有仓库这时候 Git 会弹窗让你输凭证输什么都不对。很多人第一反应是我密码忘了于是跑去 Authentik 改密码改了还是不行。位置完全错了。1.2 根因Git 的认证方式和浏览器 SSO 根本不是一回事关键要搞清楚一个底层逻辑Git 通过 HTTPS 拉取仓库时走的是 HTTP Basic Authentication。也就是客户端把用户名:密码经过简单编码塞进请求头里的Authorization: Basic xxx服务器拿到之后去比较用户名和密码是否匹配。而 SSO 单点登录的链路完全不是这样。浏览器访问仓库的时候服务器发现没有登录态会返回 302 重定向到 Authentik 的登录页。用户在 Authentik 输入凭证、完成 MFAAuthentik 再通过回调把用户引导回仓库系统仓库系统用拿到的身份信息建立本地会话种下 Cookie。这条链路上密码从来没有直接交给仓库系统仓库系统也不认识你的 Authentik 密码。问题就出在这两套逻辑的错位。仓库系统开启 SSO 之后很多部署方案会把本地密码认证入口关掉或者本地用户根本没有密码只有从 Authentik 同步过来的身份。这时候你拿着 Authentik 的密码去走 https 的 Basic Auth仓库系统看到的是一个本地不存在的密码当然直接回 401 / Authentication failed。你可以把这件事理解成换门禁系统以前是刷卡进门现在是扫码加人脸。你还在用旧卡去刷新机器门当然不开。报错不是说你密码错了而是说这个认证方式根本不适用。1.3 先有一个正确认知SSO 只解决网页登录这个认知确立之后很多问题就能想通了。SSO 解决的是 Web 场景下的身份认证和会话管理它不直接替代 Git 协议层的认证。Git 命令行没有浏览器引擎没法跟着 302 跳转到 Authentik 去完成一轮完整的 OAuth2/OIDC 流程更没法处理 Authentik 页面里的验证码、MFA 这些交互。所以你在命令行里看到的现象本质上是 Git 客户端和仓库系统之间在做 Basic Auth而 SSO 那套机制根本没有机会参与进来。你要做的不是跟 SSO 死磕而是换一种 Git 协议层能识别的认证方式。这也是为什么自建 Git 服务在做完 SSO 集成之后工程师的正常工作流会退化成两种要么用 SSH 公钥要么用 Personal Access Token也就是 PAT。后面我会把这两条路都拆开讲。2. 破局思路三条路怎么选2.1 三条路对比在 Authentik 已经接入仓库系统的前提下命令行拉取私有仓库有三条可行的路方案认证原理配置成本日常体验适合场景SSH 公钥服务器校验客户端私钥签名与 SSO 无关低一次生成最好clone 不需要输任何东西开发者日常使用HTTPS PAT用户名 访问令牌走 Basic Auth低网页生成即可较好配合 credential helper 也顺手开发者、CI、脚本OAuth2 设备授权流客户端引导浏览器授权发令牌给 Git高依赖仓库系统支持最接近 SSO 原生体验部分 SaaS Git 平台第三种设备授权流Device Flow在很多商业 Git 平台已经支持比如 GitLab 的gitlab-rails生态但 Gitea 这一层目前主要认 SSH 和 PAT。如果你用的是 Gitea 或者 Forgejo直接在前两条里面选别在第三条上浪费时间。2.2 为什么默认推荐 HTTPS PAT我的建议是开发者日常用 SSHCI 和临时脚本用 PAT。但如果只选一个先用 PAT。理由很实在。第一PAT 不需要动服务器端口。很多内网环境只开放了 80/44322 端口被防火墙按在地上SSH 反而需要额外申请放行。第二HTTPS 的 443 端口是仓库系统本来就开着的基本不会遇到网络不通的尴尬。第三PAT 可以精细控制权限过期时间而且被泄漏之后能立刻在网页端撤回。操作上它跟原来用密码的习惯几乎一样只是把密码换成了令牌。你只需要在 Gitea 的设置页面生成一个 Access Token然后在 Git 提示输密码的时候粘贴进去就行。这个切换过程大多数开发者十分钟内就能学会。2.3 什么时候该考虑 SSH什么时候该考虑 Device FlowSSH 的优势是长期稳定。公钥放到仓库系统之后基本上两年内不用再碰它哪怕你换了电脑只要把私钥拷过去git 命令完全不用改。适合团队里长期维护、高频提交的核心开发者。代价是一旦私钥泄漏影响面非常大所以私钥一定要设口令最好把ssh-agent用起来。Device Flow 适合那种仓库系统原生支持、且你希望完全绕开令牌管理的场景。GitLab 支持个人访问令牌也支持 OAuth 设备授权流用户跑git push的时候终端打印一个 URL 和授权码你打开浏览器完成认证令牌自动回填到客户端缓存。体验最顺滑但对仓库系统的版本和配置要求高很多自建实例没开启这个能力。我之前在一个小团队里试过帮所有人统一走 Device Flow最后被 Gitea 的版本限制卡住改造回调地址搞了一下午。后来回归到 SSH PAT半小时收工。经验就是优先用平台成熟支持的能力不要为了看起来更 SSO去造轮子。3. Authentik 与 Gitea 的 SSO 对接实战3.1 在 Authentik 侧创建 OAuth2 Provider 和 Application假设你的 Authentik 已经跑起来了域名是auth.example.comGitea 的域名是git.example.com。第一步进入 Authentik 管理后台左侧菜单找 Resources然后进 Providers点击 Create选择 OAuth2/OIDC Provider。字段注意几个Name 填一个可识别的名字比如gitea-oidc。Authorization Flow 一般选默认的default-provider-authorization-implicit-consent意思是用户在同意页确认后直接通过。Client Type 选 Confidential这样会生成 Client ID 和 Client SecretGitea 那边需要这两个值。Redirect URIs 是重点要填 Gitea 的 OAuth2 回调地址格式是https://git.example.com/user/oauth2/{认证源名称}/callback。注意这个{认证源名称}必须和后面你在 Gitea 里填的认证源名称完全一致大小写敏感。比如认证源叫authentik回调地址就是https://git.example.com/user/oauth2/authentik/callback。Signing Key 保留默认的自动生成即可RS256 签名足够用。Scopes 确认勾选默认的openid profile emailGitea 做 OpenID Connect Auto Discovery 的时候要用。保存之后系统会显示 Client ID 和 Client SecretSecret 只会出现一次立刻复制保存。然后创建 ApplicationResources → Applications → Create。Name 填GiteaSlug 填giteaLaunch URL 填https://git.example.com下面的 Provider 选择刚创建的gitea-oidc。这里有个很多人忽略的细节Provider 必须绑定到一个 Application 上OIDC 的 discovery 端点才会真正生效。绑定后https://auth.example.com/application/o/gitea/.well-known/openid-configuration这个地址才能访问。3.2 在 Gitea 侧添加 OAuth2 认证源进入 Gitea 管理后台站点管理 → 认证源 → 添加认证源类型选 OAuth2。关键字段认证名称填authentik必须和上面回调地址里的路径词一致。回调地址会自动计算保存后能看到。OAuth2 Provider选 OpenID Connect。Client ID / Client Secret填 Authentik 给的。OpenID Connect Auto Discovery URL填https://auth.example.com/application/o/gitea/.well-known/openid-configuration。Gitea 会自动读取这个地址里的授权端点、令牌端点、用户信息端点。勾选允许自动创建账户和允许自动更新账户信息这样用户第一次通过 SSO 登录时会自动映射到 Gitea 本地账户。保存后去 Gitea 的登录页看一眼应该会多出一个通过 Authentik 登录的按钮。点它跳转到 Authentik完成登录回到 Gitea如果能看到正常的仓库列表说明 Web SSO 链路已经通了。3.3 验证 Web SSO 登录链路是否通这里有一个很容易踩的验证误区直接在浏览器里登录成功只能说明 SSO 的 Web 流程通了但它完全不能证明 Git 协议层能用。很多团队卡在这一步觉得网页能进去为什么 git clone 不行原因就在这儿。我更建议验证的时候多做一个动作用浏览器开发者工具或者直接在地址栏访问 Gitea 的 API比如https://git.example.com/api/v1/user看看返回里用户名是不是 Authentik 里的那个用户邮箱是否同步过来。如果 API 都能正常返回说明身份映射没毛病。接下来再去处理命令行的问题。另外在 Authentik 里你也可以打开日志看登录事件有没有被 Gitea 正确触发。如果日志里出现了客户端请求但 Gitea 那边没有建立会话通常就是回调地址或者 Client ID/Secret 的问题优先查这两项。4. 命令行克隆的三种正确姿势4.1 姿势一SSH 公钥一劳永逸SSH 方案和 SSO 完全解耦它不关心你是谁只关心你的私钥有没有被仓库系统登记过。操作步骤$ ssh-keygen -t ed25519 -C zhangsanwork一路回车生成~/.ssh/id_ed25519.pub然后把这个文件的内容整段复制到 Gitea 的 设置 → SSH Keys → 添加密钥。如果 Gitea 的 SSH 服务跑在非默认端口比如 2222直接 clone 的地址要带上端口$ git clone ssh://gitgit.example.com:2222/team/project.git想省事的话可以在~/.ssh/config里写Host git.example.com HostName git.example.com Port 2222 User git IdentityFile ~/.ssh/id_ed25519这样git clone gitgit.example.com:team/project.git也能正常走。验证连接用这条命令$ ssh -T gitgit.example.com -p 2222 Hi zhangsan! Youve successfully authenticated...看到成功提示后SSH 拉取就稳了。这个方案对开发者最友好因为后续任何 clone、push、pull 都不会再有认证交互脚本也省心。4.2 姿势二PAT 令牌兼容 HTTPS 习惯PAT 的生成路径在 Gitea登录后点头像 → 设置 → 应用 → 生成新令牌。填一个用途备注勾选权限至少要有read:repositorypush 的话再加write:repository。保存后令牌会显示一次立刻复制走后面再也看不到。使用 PAT 拉代码两种方式。第一种是标准交互式输入$ git clone https://git.example.com/team/project.git Username for https://git.example.com: zhangsan Password for https://git.example.com: # 粘贴 PAT而不是 SSO 密码注意用户名填的是 Gitea 里的登录名不是邮箱也不是 Authentik 里的显示名。Gitea 同步过来的用户用户名往往是 Authentik 里配置的 username 映射过去的字段可以在设置页确认。第二种是把令牌直接拼进 URL$ git clone https://zhangsan:你的PATgit.example.com/team/project.git这种方式很直观但我不推荐把它写进任何会进 shell history 的地方。临时用一下可以用完整理干净。想确认令牌是否有效可以先打一个 API 探一下$ curl -u zhangsan:你的PAT https://git.example.com/api/v1/repos/team/project返回 JSON 包含仓库信息就说明令牌没问题可以去 clone 了。4.3 姿势三credential helper把令牌管起来大家都不想在 Git 每次操作时手动粘贴令牌尤其是 HTTPS 方案。这时候要用到 Git 的 credential helper。它的作用是把凭证交给系统钥匙串保存后续 Git 自动读取不再提示。Linux 上最常用的是 libsecret先装依赖$ sudo apt install libsecret-1-0 gnome-keyring然后注册 helper$ git config --global credential.helper libsecretWindows 上 Git for Windows 已经自带 manager-core基本不需要额外配置。首次 clone 时弹出窗口里填一次用户名和令牌以后就静默通过了。macOS 用 osxkeychain$ git config --global credential.helper osxkeychain这里有个细节Git 提示输密码的时候你输入的 PAT 会被保存到系统钥匙串并不是明文存在.git-credentials里安全性比store模式高一个量级。如果你看到网上有人让你用git config --global credential.helper store至少要明白那是在明文保存凭证多人共用机器的时候绝对不要这么干。另外一个小技巧如果你不想在 URL 里出现用户名可以在全局配置里写别名替换$ git config --global url.https://git.example.com/.insteadOf https://git.example.com/配合 credential helper日常使用体验已经非常接近 SSH 了。4.4 补充CI/CD 里怎么安全传令牌CI 场景里没有交互终端不能用交互式输入所以令牌要么拼在 URL 里要么通过环境变量传递。后者是正路。以 Gitea Actions 为例在仓库的 Settings → Secrets 里添加GIT_TOKEN然后在 workflow 里这样用- name: Checkout run: git clone https://zhangsan:${{ secrets.GIT_TOKEN }}git.example.com/team/project.git要留意两点第一日志里别把 secret 打出来这不用多解释第二CI 用的令牌建议单独生成独立账号权限只给需要的仓库和操作别用某个工程师的个人令牌跑流水线。否则这个人离职、令牌被清理所有流水线一起挂那场面非常酸爽。5. 从报错到真相问题排查速查实录5.1 八个高频报错与解法我把这段时间帮同事排查和自己在 NuGet 上查到的各种问题汇总成一张表排在前面的都是高频报错或现象真正原因解法fatal: Authentication failed密码用的是 SSO 密码不是 PAT用用户名 PAT 重新认证remote: Unauthorized令牌权限不足或过期重新生成令牌勾选仓库读写权限could not read Username ... terminal prompts disabledCI 无交互终端URL 带 PAT 或配置 credential helperredirecting to https://auth.example.com/...服务端要求 SSO 会话Basic Auth 不认改走 PAT 或 SSHinvalid redirect_uriAuthentik 回调地址和 Gitea 认证源名不一致核对/user/oauth2/{认证源名}/callback401 Unauthorized令牌被吊销或用户被禁用检查 Gitea 用户状态重新发令牌403 Forbidden令牌权限不够在生成令牌处补权限connection refused见 5.2 节往往是环境变量残留检查 git 配置和环境变量排查顺序有个讲究先确认网络通不通再确认证书最后确认认证。很多人一上来就重新生成令牌折腾半天发现是网络问题白忙一场。5.2 诡异现象git clone 去连 127.0.0.1 被拒有个报错频率不低但非常隐蔽git clone failed to connect to 127.0.0.1 port 7890: connection refused。你明明克隆的是远程仓库地址Git 却跑去连本机的一个端口然后被拒绝。这种情况九成是环境变量或全局配置里残留了指向本机端口的转发设置。Git 启动时会读http.proxy、https.proxy、ALL_PROXY这些配置如果它们指向127.0.0.1:7890而本机那个端口上并没有服务在监听就会报 connection refused。排查命令$ git config --global --get http.proxy $ git config --global --get https.proxy $ env | grep -i proxy如果查出来有值按你的实际情况清掉$ git config --global --unset-all http.proxy $ git config --global --unset-all https.proxy环境变量的话在 shell 配置文件里找export ALL_PROXY...这类行删掉然后重新打开终端。这个报错和 Authentication failed 经常像双胞胎一样一起出现。原因是 Git 先去连本地端口失败然后部分客户端会把这层失败包装成认证失败。所以遇到 Authentication failed第一件事不是改密码是先跑一遍上面的代理检查。我实际排查过的案例里至少有三成是环境变量惹的祸根本不是认证问题。5.3 时间漂移与回调地址两个冷门坑还有两个冷门坑日志里通常不提示但后果很严重。第一个是时钟漂移。OAuth2/OIDC 流程里的 JWT 令牌带有iat签发时间和exp过期时间Gitea 验证 id_token 时会比对时间。任何一端服务器的时间漂移超过几十秒验证就会失败表现是Web 登录能跳转但回调时报错或者在 clone 时莫名 401。解决办法是让 Authentik 和 Gitea 的服务器都开启 NTP 同步$ timedatectl set-ntp true $ timedatectl status这个坑我之前在虚拟机里踩过一次宿主机休眠了一段时间虚拟机时间偏了十几分钟OAuth 回调直接崩。修好时间同步之后问题立刻消失。第二个是回调地址的精确匹配问题。OIDC 的 redirect_uri 必须和 Provider 里登记的一模一样协议、域名、端口、路径、末尾斜杠都不能差。Gitea 的认证源名称改了一个字符回调地址就全变了但 Authentik 那边还登记着旧的。这种问题表面上看是登录后跳回 Gitea 报页面错误 500实际上就是 URL 不匹配。排查时不要凭感觉把 Authentik 的日志打开搜索redirect_uri错误信息会明确告诉你期望值是什么。5.4 踩坑后的安全习惯最后说一点安全上的心里话。PAT 的本质是账号的钥匙比密码权限更精准但也同样敏感。我见过有同事把 PAT 直接贴进群聊里求助然后大家都能 clone 他的私有仓库画面太美不敢看。最基本的习惯是第一令牌一旦泄漏立刻去 Gitea 设置里删除并重新生成不要想着先凑合用。第二不同场景用不同的令牌比如 CI 一个、日常开发一个、临时脚本一个这样单个令牌安全告警时不影响全部流程。第三不要在服务器上执行来路不明的安装脚本尤其是那种先从/usr/bin下判断 curl 存在、再从远程地址下载并执行的脚本。虽然这种写法在一些正规安装包里也常见但你在终端按下回车之前至少应该把人家的脚本内容从头到尾看一遍。这些安全习惯建立起来之后Authentik SSO 带来的不是麻烦而是可以量化的收益账号统一、MFA 覆盖、权限回收路径清晰。我在实际操作中的体会是遇到这类问题不能只盯着认证两个字。先把网络链路、环境配置、时间同步这些基础项排干净再谈令牌和 SSO思路会顺得多。你配置完成后如果团队里还有人拿 SSO 密码往 git clone 里输别急着嘲讽把这篇文章转给他就行。