先讲一个我最近常被问到的问题:公司里上了好几套系统,Git代码托管平台、工单系统、Wiki、CI/CD,每套都有自己的账号密码,员工入职离职还要挨个去开号销号,管理员光管账号就快疯了。所以很多团队都会考虑接一个统一认证中心,让所有内部系统都用同一套身份体系登入。我这次在GitPuk上集成soular,就是为了解决这个问题,让整个代码托管平台完全复用公司现有的统一身份账号,用户不用再单独记住一套Git仓库的密码。
这篇东西适合谁看?正好在折腾自建Git服务、又想把登录换成公司统一账号体系的运维和后台开发同学。你不需要提前深入了解soular的源码,只要大概知道OAuth2、OIDC是怎么一回事,跟着下面的步骤走,半小时内能把GitPuk和soular串起来。文章里我会把原理、配置、常见坑一口气讲清楚,尤其是几个官方文档里含糊的地方,我直接用实操结果说话。
1. 整体方案拆解:为什么要在GitPuk上接soular
1.1 GitPuk的定位与选型理由
GitPuk是一款轻量级的自托管Git代码托管平台,整体设计思路和Gitea这类产品比较接近:部署简单、资源占用低、开箱即用。它内置了仓库管理、Issue、Pull Request、Webhook、组织权限这些日常开发必需的模块,适合中小团队放在内网使用,也适合个人开发者在一台小机器上架起来当私有Git服务器。
我选择GitPuk的原因很直接:它部署包只有一个二进制文件,跑起来内存占用大概在几百兆级别,比一套完整的GitLab轻太多。内网团队人多的时候,GitLab动不动就要8G内存起步,我这台小服务器根本扛不住。GitPuk的页面响应速度也快,日常git push/pull走的是纯Git协议,体验上和GitHub没有明显差异。
它还内置了一套完善的权限模型,可以精确控制仓库级别的读写权限,团队内部想做到“开发只能推代码、主干分支只有维护者能合并”,这套权限体系能覆盖。加上它原生支持OAuth2认证协议对接,这就为后续接soular做了铺垫。
1.2 soular统一认证平台能做什么
soular可以理解成一个企业内部的统一身份认证服务,核心能力是账号管理、单点登录(SSO)、多因素认证,以及对上层应用提供标准的OAuth2/OIDC协议接口。它自己保存一份“权威用户库”,所有接进来的业务系统都不需要自己维护一套账号密码,登录时全部跳转到soular的统一登录页完成身份校验。
举个例子:你的公司有10个内部系统,每个系统都自己搞注册登录,意味着有10套用户表、10套密码策略,员工离职要跑10个地方消号,密码找回也要跑10个地方。接入soular之后,所有系统都指向同一个登录入口,用户只要记住一套账号密码;管理员在soular控制台里可以统一停用账号、统一做密码策略,安全审计也能集中处理。
GitPuk有自己的本地账号体系,但如果让它只接soular,用户就不用再单独注册GitPuk账号了。登录的时候会跳转到soular,认证完成后再自动跳回GitPuk,首次登录时GitPuk根据soular返回的身份信息自动创建本地用户,全程不需要人工干预。
1.3 为什么采用标准OAuth2协议而不是其他方案
很多人会问,能不能直接用LDAP对接?当然可以,GitPuk本身可能也支持LDAP。但LDAP对接有一个问题:它只解决“用户名密码校验”这一步,无法完成“用户在哪个系统、有什么角色权限”这层授权语义。OAuth2/OIDC的价值在于它是一套完整的授权协议,除了认证用户之外,还会返回用户的邮箱、姓名、唯一标识等标准化的身份声明(Claim),应用可以直接用这些信息创建账号和分配权限。
另一方面,OAuth2/OIDC是几乎所有现代应用都能识别的标准协议,今天你接GitPuk,明天接Confluence、Jenkins、Grafana,都是同一套对接模式,不需要为每个系统单独定制协议。企业里身份认证这件事,最重要的就是“标准统一、入口收敛”,用OAuth2/OIDC来做是最符合长期演进的路子。
2. 环境准备与前置部署
2.1 GitPuk快速安装与目录规划
我这边采用Docker方式部署GitPuk,主要原因是升级方便、环境隔离干净,不污染宿主机。如果后续要迁移也只是把数据卷拷过去。下面这份docker-compose配置是我在正式环境里用的模板:
version: "3" services: gitpuk: image: gitpuk/gitpuk:latest container_name: gitpuk restart: always ports: - "3000:3000" - "2222:22" volumes: - ./gitpuk-data:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro environment: - GITPUK__SERVER__DOMAIN=git.example.com - GITPUK__SERVER__ROOT_URL=https://git.example.com/ - GITPUK__SERVER__SSH_DOMAIN=git.example.com - GITPUK__SERVER__SSH_PORT=2222几个目录规划上的要点:
- 所有数据都挂在
./gitpuk-data下,包括仓库存储、数据库文件、配置文件、密钥,备份时只需要备份这一个目录。 - 宿主机端口用了3000映射Web、2222映射SSH,避免和宿主机上其他服务冲突。如果SSH想用标准22端口,要注意宿主机可能已经被占用了。
/etc/timezone和/etc/localtime是只读挂载,保证容器内时区和宿主机一致,不然Git提交记录的时间会出偏差,排查问题时很麻烦。
启动服务之后,浏览器打开http://服务器IP:3000,第一次访问会进入安装向导页面。需要填数据库类型、站点名称、管理员账号这些信息。我这边沿用默认的SQLite配置,因为团队规模不大,SQLite完全够用,还省掉一个MySQL实例的维护成本。
2.2 soular服务端部署与初始化
soular这边我采用Docker Compose部署,架构上包含一个认证服务端和一个PostgreSQL数据库。soular本身支持多种外部用户源(比如对接企业微信、飞书、LDAP等),但在我们的场景里,先直接把soular作为独立的用户库使用,管理员在soular控制台里手动维护账号。
启动好soular后,需要完成以下初始化工作:
- 创建一个管理员账号。
- 配置基础域名,比如
https://sso.example.com,这个地址就是后续所有系统跳转登录的统一入口。 - 设置客户端密钥加密密钥(或至少确认默认值能用),这个密钥用于加密存储每个客户端的Client Secret。
部署完成之后,通过sso.example.com能正常访问soular控制台,说明基础服务已经通了。这里不需要做太复杂的初始化配置,核心配置会在下一步创建应用的时候用到。
2.3 域名与HTTPS配置:OAuth2集成的前置条件
OAuth2的授权流程中,应用需要把用户重定向到sso.example.com/oauth2/authorize,认证完成后soular再把用户重定向回GitPuk的回调地址。这两个地址必须是公网可访问(内网场景下就是所有客户端都能访问)的HTTPS地址。
HTTP也不是完全不行,但强烈建议上HTTPS。有两个原因:第一,授权码(Authorization Code)和令牌(Access Token)在传输过程中如果被明文截获,账号安全就形同虚设;第二,现代浏览器对Cookie、第三方重定向的限制越来越严格,HTTPS可以避免很多奇怪的兼容性问题。
我这边统一用Nginx作为反向代理,分别代理sso.example.com和git.example.com。证书用Let's Encrypt自动续期就好,内网环境也可以用自签证书,但客户端那边需要把证书导入信任链,比较麻烦。能申请公网证书就申请公网证书,省事很多。
Nginx反向代理GitPuk的关键配置片段如下:
server { listen 443 ssl; server_name git.example.com; ssl_certificate /etc/letsencrypt/live/git.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }有一个细节要特别留意:X-Forwarded-Proto这个Header必须设置,GitPuk是靠它来识别外部请求是HTTP还是HTTPS的。如果漏掉这一步,GitPuk生成的回调URL可能会变成http://开头,和soular里配置的回调地址不一致,导致登录时直接报redirect_uri不匹配。这个坑我踩过,后面排查了一下午才找到原因。
3. 集成配置一步不落:从创建应用到参数填表
3.1 在soular中注册GitPuk客户端应用
统一认证服务一般都有一个管理端后台,在里面可以注册“客户端应用”(Client Application)。每个应用会分配一对密钥:Client ID和Client Secret。Client ID是公开的,相当于应用的唯一标识;Client Secret是机密信息,相当于应用向认证服务证明“我是我”的密码。
在soular控制台新建应用时,需要填以下关键信息:
- 应用名称:填GitPuk即可,方便以后在控制台里识别。
- 应用类型:选择Web应用(Web Application),因为GitPuk是服务端渲染的Web服务,走的是标准授权码流程。
- 回调地址(Redirect URIs):填GitPuk的OAuth2回调路径。GitPuk这类系统的回调URL通常有固定格式,我这边用的是:
https://git.example.com/user/oauth2/soular/callback注意路径中的soular对应后面在GitPuk中填写的认证源名称,两端必须保持完全一致。回调地址是OAuth2安全模型中非常关键的一环,soular只允许授权码回跳到这个白名单里的地址,其他地址一律拒绝,防止授权码被恶意截获。
- 授权范围(Scopes):至少勾选
openid、profile、email这三个。openid是OIDC协议的基础,用于获取ID Token;profile用于获取用户名、昵称;email用于获取邮箱地址。后面GitPuk要用邮箱来匹配或创建本地账号。
创建完成之后,soular控制台会显示一对Client ID和Client Secret,记下来填到GitPuk那边。Client Secret在有些系统里只会完整显示一次,务必当时就复制好,丢了就需要重新生成。
3.2 GitPuk添加认证源:OAuth2配置参数逐项对照
GitPuk这边需要在管理后台的“认证源”设置里,创建一个OAuth2类型的认证源,把soular给的参数填进去。不同版本的GitPuk界面可能略有差异,但核心配置项是这几项,直接看配置面板:
| GitPuk配置项 | soular对应值 | 示例 |
|---|---|---|
| 认证源名称 | 自定义,用于回调URL路径 | soular |
| 客户端ID | soular应用详情页 | 8f4a1c2e9d0b4f7a8c1b2d3e4f5a6b7c |
| 客户端密钥 | soular应用详情页 | 3e9a7c6b5d4f3a2b1c0d9e8f7a6b5c4d |
| 授权URL | soular授权端点 | https://sso.example.com/oauth2/authorize |
| 令牌URL | soular令牌端点 | https://sso.example.com/oauth2/token |
| 用户信息URL | soular用户信息端点 | https://sso.example.com/oauth2/userinfo |
| 作用域 | openid profile email | openid profile email |
在GitPuk的配置文件中,等价于以下几个键(如果用的是配置文件而非界面配置):
[oauth2_client] ENABLED = true ; 下面这些在界面上配置时会自动写入实际上GitPuk的管理界面通常提供了完整的表单,不需要手动改配置文件来加认证源。直接把上表格里的参数填进去即可,填完保存,页面上就会出现一个“使用soular登录”的按钮。
这里要强调一个容易搞错的点:授权URL和令牌URL必须精确匹配soular公开的端点路径。有些认证平台支持自定义端点,路径一改就得同步过来。如果这两项填错了,用户点击登录时要么跳转不过去,要么跳转之后报404,排查起来非常难受。
3.3 自动创建账号与权限映射策略
OAuth2认证源配置里通常会有一个“自动创建账户”的开关,建议打开。打开之后,当soular返回一个用户信息、而GitPuk本地找不到对应用户时,系统会自动创建新账号并直接登录。
自动创建账号的好处在企业内部场景特别明显:新员工在soular里开好账号之后,第一次访问GitPuk,点击“使用soular登录”,GitPuk会自动为他创建好本地账号,不需要管理员提前做任何操作。员工离职时,管理员在soular里停用该账号,之后他再想通过soular登录GitPuk就会被拒绝,账号体系实现了“一处禁用,全局生效”。
账号匹配的逻辑上,GitPuk通常会优先通过邮箱地址匹配本地用户。也就是说,如果一个用户的soular邮箱和他之前手动申请的GitPuk账号邮箱一致,那么他通过soular登录时会直接绑定到原有账号,不会产生一个重复的新账号。这就要求企业内部尽量统一邮箱格式,避免出现soular里是zhangsan@example.com、而GitPuk里是zs@example.com这种对不上的情况。
权限映射方面,默认登录后的用户都是普通成员权限,只能访问自己被授权的仓库。如果希望部分soular用户登录后自动成为GitPuk管理员,可以在认证源的配置里做组映射,或者在soular这边给用户打上某种角色标签,再通过GitPuk的角色同步策略来映射。我这边团队规模不大,管理员数量固定,就采用了最简单的方式:认证源对接完成之后,手动在GitPuk后台把需要当管理员的几个账号提权,不做复杂的动态同步。
4. 登录流程与核心环节实操验证
4.1 授权码模式完整流程拆解
整个登录流程走的是OIDC标准的Authorization Code Flow,理解了这个流程,后面排查问题才有方向。我按实际发生的顺序拆一遍:
- 用户在浏览器里访问GitPuk,点击页面上的“使用soular登录”。
- GitPuk将用户重定向到soular的授权端点,URL大致是:
https://sso.example.com/oauth2/authorize? client_id=8f4a1c2e9d0b4f7a8c1b2d3e4f5a6b7c& response_type=code& scope=openid%20profile%20email& redirect_uri=https%3A%2F%2Fgit.example.com%2Fuser%2Foauth2%2Fsoular%2Fcallback& state=随机字符串这个state参数很关键,它是一个随机会话标识,GitPuk发出去之后会在回调时校验它是否一致,防止CSRF攻击。有些同学自己对接OAuth2时没有带上state,这就是给攻击者留了一个伪造回调的口子。
- 用户在soular的登录页输入账号密码(如果之前登录过,soular可能直接跳过这一步,这就是单点登录的方便之处)。认证通过后,soular在后台确认这个用户确实给GitPuk授予了登录权限,然后浏览器被302重定向到GitPuk的回调地址,并带上一个授权码
code:
https://git.example.com/user/oauth2/soular/callback?code=abc123&state=随机字符串- GitPuk收到回调后,先校验state是否和之前发出去的一致,然后拿着授权码
code去请求soular的令牌端点。这一步是服务端到服务端的请求,用户看不到,需要带上Client ID和Client Secret做身份认证:code是临时且一次性的,换取成功后就失效,即使中途被截获也无法再次使用。
POST https://sso.example.com/oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=abc123 &redirect_uri=https%3A%2F%2Fgit.example.com%2Fuser%2Foauth2%2Fsoular%2Fcallback &client_id=8f4a1c2e9d0b4f7a8c1b2d3e4f5a6b7c &client_secret=3e9a7c6b5d4f3a2b1c0d9e8f7a6b5c4d- soular验证通过后返回一组JSON,里面有Access Token、ID Token和Refresh Token。Access Token是后续访问用户信息接口的凭证。
- GitPuk拿着Access Token去请求soular的用户信息端点
/oauth2/userinfo,拿到用户的邮箱、用户名、头像等信息。 - GitPuk根据拿到的邮箱去本地用户表里找账号,找到了就绑定登录,找不到且开启了自动创建账号,就新建一个账号并登录。
整个流程走下来不超过3秒,用户感知到的只有一个跳转soular登录页再跳回来的过程。
4.2 用curl手动模拟OAuth2流程定位问题
如果集成之后登录不通,最快的排查办法是用curl手动走一遍整个OAuth2流程,把问题缩小到具体是哪一步出的错。我来演示一遍我当时的排查操作。
首先,手动模拟浏览器访问授权端点:这一步不建议在命令行里完整走,因为中间有用户登录交互,但可以用curl -v看重定向行为:
curl -v "https://sso.example.com/oauth2/authorize?client_id=8f4a1c2e9d0b4f7a8c1b2d3e4f5a6b7c&response_type=code&scope=openid%20profile%20email&redirect_uri=https%3A%2F%2Fgit.example.com%2Fuser%2Foauth2%2Fsoular%2Fcallback&state=test123"正常情况下会被重定向到soular的登录页面。如果这一步返回405、404或者redirect_uri不匹配,基本是授权URL配置错了或回调地址没对上。
然后模拟授权码换token这一步,我账号密码登录之后拿到一个授权码,拼接成如下请求:
curl -X POST "https://sso.example.com/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=上一步拿到的code" \ -d "redirect_uri=https://git.example.com/user/oauth2/soular/callback" \ -d "client_id=8f4a1c2e9d0b4f7a8c1b2d3e4f5a6b7c" \ -d "client_secret=3e9a7c6b5d4f3a2b1c0d9e8f7a6b5c4d"这里有个很容易忽略的细节:换token时的redirect_uri必须和授权时的redirect_uri完全一致,包括路径、大小写、是否带尾部斜杠。GitPuk在调token接口时用的redirect_uri会自动从会话里取,但如果你在手动测试时用的地址和GitPuk配置的不一致,soular会直接拒绝这次token请求。
再继续拿Access Token请求用户信息接口:
curl -X GET "https://sso.example.com/oauth2/userinfo" \ -H "Authorization: Bearer 上一步拿到的access_token"这一步能确认soular返回的字段是否符合GitPuk的预期。GitPuk一般要从userinfo里拿到email和name或preferred_username字段。如果这里字段名对不上,比如soular返回的是user_email而GitPuk解析的是email,那就会导致登录时提示“无法获取邮箱”之类的错误。
4.3 首次登录验证与账号自动创建实测
配置完成之后,我特意用一个全新的soular测试账号走了一遍完整登录,记录一下实际操作的情况:
- 打开GitPuk首页,点击“使用soular登录”,页面跳转到
sso.example.com的登录界面。 - 输入测试账号密码之后,页面很快重定向回
git.example.com/user/oauth2/soular/callback?code=...&state=...。 - 浏览器最终落地到GitPuk主页,右上角已经显示了这个测试账号的用户名;看GitPuk后台的用户列表,确实多了一个新用户,和soular测试账号的邮箱一致。
- 我尝试在未登录状态下再访问一次GitPuk,点击仓库页面时被重定向到登录页,没有出现“重复创建账号”的情况。
至此,一个完整的“soular用户首次登录GitPuk自动建档”流程就验证通过了。后面又用同一个账号重新登录了几次,因为soular端已经有登录会话,GitPuk这边几乎都是秒登录,不需要再输一遍密码,这就是单点登录的效果。
5. 集成踩坑实录与安全加固建议
5.1 常见登录失败问题速查
我把自己在集成过程中遇到和预判到的典型问题整理成了一张速查表,按问题现象、可能原因、解决办法来组织,方便按图索骥:
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 点击soular登录跳转到404 | 授权URL配错,或soular域名无法解析 | 检查GitPuk认证源里的授权URL是否精确对应soular端点;检查sso.example.com是否可达 |
| 回调时报redirect_uri不匹配 | 回调地址两端不一致(大小写、路径、端口、HTTP/HTTPS差异) | 对比soular应用配置里的回调地址和GitPuk实际回调URL,逐字符核对 |
| 登录后提示无法获取邮箱 | userinfo返回的字段名和GitPuk预期不一致 | 用curl手动请求userinfo,确认字段名;检查scope是否包含email |
| 授权码换token失败 | Client Secret错误,或redirect_uri不一致 | 重新复制Client Secret;确保token请求里的redirect_uri和授权时一致 |
| 登录后一直转圈无法跳回 | 反向代理未传递X-Forwarded-Proto | 在Nginx配置中补上proxy_set_header X-Forwarded-Proto $scheme; |
| 已有本地账号但登录后变成新账号 | 本地账号邮箱与soular邮箱不一致 | 统一两边邮箱;或先在GitPuk后台把本地账号邮箱改成soular里相同的邮箱 |
| 登录成功后提示账号被禁用 | soular账号状态异常或被停用 | 在soular控制台检查该账号是否启用 |
5.2 回调和代理配置里的几个隐形坑
第一个隐形坑是HTTPS证书链不完整。有些内网环境用的证书只部署了站点证书,没有把中间证书链一并配置,浏览器访问没问题,但GitPuk后端去请求soular的token接口时会因为证书验证失败直接报错。排查方法很简单:在GitPuk服务器上用curl访问soular的token端点,如果curl都报证书错误,那就是证书链问题。
第二个坑是Nginx的proxy_redirect设置。如果GitPuk的反向代理配置里没有正确处理重定向,soular跳转回GitPuk时的Location头可能被改写。我的经验是保持proxy_redirect off或者在location里显式设置好跨域场景,避免多一跳诡异跳转。
第三个坑是Client Secret里的特殊字符。如果soular生成的密钥包含/、+、=这些URL敏感字符,在手动填配置或者通过环境变量注入时,要确认没有发生URL编码转义。GitPuk有些版本会把配置里的#当成注释符截断掉,真的遇见过,改成用管理界面填写就没事了。
第四个坑比较隐蔽:认证源名称不要用大写字母。我最初把认证源名称填成Soular,导致回调路径变成/user/oauth2/Soular/callback,在Linux下的路由匹配又是大小写敏感的,结果回调一直404。最后统一改成全小写的soular才正常。
5.3 安全加固建议:密钥、回调与权限检查
接完SSO功能,安全上的弦也要绷紧。这里分享几个我做完之后主动加上的安全措施:
- 妥善保管Client Secret,不要提交到Git仓库里。尤其注意GitPuk的配置文件在初始安装时会生成在数据目录里,如果整个数据目录被同步到网盘或者被备份到不安全的位置,密钥就泄露了。我这边把数据目录单独做了权限收紧,只有运行用户能读。
- 回跳地址白名单尽量收紧。soular控制台里配置的回调地址,只保留GitPuk这一个正式域名,不要为了图方便把
http://localhost:3000/user/oauth2/soular/callback这种地址也加进去。开发环境临时调试可以用,但生产环境一定要删掉。 - 开启登录审计。GitPuk和soular都有日志记录功能,建议都打开,尤其是记录登录失败、token颁发、账号锁定的日志,出了问题能追溯到底是谁在什么时间做了什么操作。
- 定期轮换Client Secret。密钥这东西用得久了就有泄露风险,我给自己定了一个半年轮换一次的定期任务。轮换时先在新密钥生效之前同时保留旧密钥一小段时间,避免业务中断。具体做法是在soular里生成新的Client Secret,更新到GitPuk配置里,确认登录正常后再在soular里删除旧密钥。
6. 这个方案后续还能怎么扩展
接完GitPuk和soular的统一登录之后,我发现这套集成方式本身是完全可以复制的。公司里其他需要接入统一认证的系统,比如jenkins、wiki、监控面板,都是同样的套路:在soular里注册一个Web应用,把Client ID和Secret配置到目标系统里,填上授权URL、令牌URL、用户信息URL这三个端点,回调地址指向目标系统的固定路径,搞定。
还有一个很实用的扩展方向:把soular自身的用户源接到企业的LDAP或企业微信上。这样soular就变成了一个中转层,公司组织架构里的人员变更可以自动同步到soular,而GitPuk这类应用只需要信任soular的认证结果就行。员工的入职、转岗、离职,在源头上一次处理,所有下游系统全部自动生效。
对我个人来说,这次集成最直接的收益是再也不用在GitPuk后台手工维护一堆临时账号了。以前同事离职了我得记得去禁用他的GitPuk账号,现在只要在soular里停用账号,GitPuk那边自然就进不来了。包括我自己日常在不同的内部系统之间跳转,也不用每个系统都输一遍密码,体感上和用企业内部其他平台的单点登录完全一致。
如果你也在折腾自托管Git服务,并且公司已经有了一套统一身份服务,不管它叫soular还是别的什么名字,只要支持OAuth2/OIDC协议,这套接入思路都是通用的。先把认证源接上,再把账号自动创建打开,后续权限细分的部分可以一边用一边调。踩坑的时候别慌,对照着上面那张速查表去看,大部分问题都出在回调地址和代理头设置这两块,提前避过去能省大量时间。