自动化登录服务的实践
在公司内部基础设施建设中,我们计划自建代码托管服务,并选择了 GitLab 作为核心平台。同时,公司日常办公依赖飞书,因此希望实现 基于飞书账号的统一登录(SSO)。
背景与目标
当前环境:
- 自部署 GitLab 服务(带公网域名)。
- 公司统一使用飞书账号体系。
目标:
- 支持通过飞书账号登录 GitLab。
- 在飞书客户端中访问 GitLab 时,实现自动登录(无感知)。
- 避免用户手动点击“飞书登录”按钮。
GitLab OAuth 接入
GitLab 本身支持通过 OmniAuth 集成第三方 OAuth 登录。通过修改配置文件 /etc/gitlab/gitlab.rb 即可启用。
# 其他配置 ...
# 启用 OAuth
gitlab_rails['omniauth_enabled'] = true
# OAuth 方式列表
gitlab_rails['omniauth_allow_single_sign_on'] = ['oauth2_generic']
# OAuth 不会禁用自动创建的用户
gitlab_rails['omniauth_block_auto_created_users'] = false
# OAuth 认证方式列表
gitlab_rails['omniauth_providers'] = [
{
"name" => "oauth2_generic",
# 显示的登录按钮文本
"label" => "飞书登录",
"app_id" => "自建飞书应用的 APP ID",
"app_secret" => "自建飞书应用的 APP Secret",
"args" => {
"client_options" => {
# SSO 地址,这里是做了一个中间转发服务,用于统一用户系统入口
"site" => "https://open-api.example.com/",
# 认证的 URL
"authorize_url" => "/authen/v1/index",
# 获取 Token 的 URL
"token_url" => "/authen/v1/access_token",
# 获取用户信息的 URL
"user_info_url" => "/authen/v1/user_info"
},
# 用户信息响应的结构体
"user_response_structure" => {
"root_path" => ['data'], # 飞书用户在 data 里
"id_path" => "open_id", # 唯一ID(推荐用 open_id)
"attributes" => {
"name" => "name", # 用户名
"email" => "email", # 用户邮箱
"image" => "avatar", # 用户头像
}
},
"strategy_class" => "OmniAuth::Strategies::OAuth2Generic"
}
}
]
关于 user_response_structure 的更多配置,可以查看文档获取。
配置完毕后,执行 sudo gitlab-ctl reconfigure 重载配置即可。
回到 gitlab.example.com/users/sign_in,就可以在登录界面下方看到自定义的飞书登录了。
自动登录的分析
虽然 OAuth 接入完成,但用户在飞书客户端中访问 GitLab 时,仍需手动点击“飞书登录”按钮。
因为 GitLab 并未提供“自动跳转到某个 OAuth Provider”的能力,登录流程依赖用户主动触发表单提交,
因此需要一种方式来绕过点击行为,实现自动触发登录流程。
在分析 GitLab 登录页时,可以发现:
“飞书登录”本质是一个表单提交,而表单中包含 authenticity_token(CSRF 防护)。
因此,只要我们能获取 authenticity_token 就能构造并提交相同的表单请求。从而实现“模拟点击登录”。
实现自动登录
因为自部署的 GitLab 服务使用了 Nginx 作为反向代理。
因此可以使用 Nginx 增加一个自动化登录的路径。
Nginx 配置
location = /feishu/auto-login {
# 已登录则直接跳转
if ($cookie__gitlab_session != "") {
return 302 /;
}
# 非飞书客户端禁止访问
if ($http_user_agent !~* "(feishu|lark)") {
return 302 /404;
}
# 返回 HTML
default_type text/html;
charset utf-8;
return 200 '<HTML内容见下面>';
}
自动化页面代码
<!DOCTYPE html>
<html>
<body>
<p>正在通过飞书登录...</p>
<script>
fetch("/users/sign_in", { credentials: "include" })
.then(res => {
if (res.redirected) location.href="/";
return res;
})
.then(res => res.text()).then(html => {
const doc = new DOMParser().parseFromString(html, "text/html");
const token = doc.querySelector("input[name=\'authenticity_token\']").value;
const form = document.createElement("form");form.method = "POST";form.action = "/users/auth/oauth2_generic";
const input = document.createElement("input");
input.name = "authenticity_token";input.value = token;
input.type = "hidden";form.appendChild(input);
document.body.appendChild(form);
form.submit();
});
</script>
</body>
</html>
完成后,将自建应用的网页入口更换为 https://gitlab.example.com/feishu/auto-login 就可以实现自动跳转 OAuth,自动完成登录流程而无需手动点击按钮。
自动化登录服务的实践
https://www.inksha.com/archives/zi-dong-hua-deng-lu-fu-wu-de-shi-jian