Project
统一身份认证
为多个 Halo 站点提供“身份中心 + 接入站”的单点登录能力。一个站点作为身份中心统一承接登录与授权,其它 Halo 站点作为接入站跳转到身份中心完成认证,并在回调后建立本站登录态、绑定本地用户、同步用户资料和映射本地角色。slug: halo-plugin-sso
owner: liuyiwuqing
repo: halo-plugin-sso
统一身份认证
为多个 Halo 站点提供“身份中心 + 接入站”的单点登录能力。一个站点作为身份中心统一承接登录与授权,其它 Halo 站点作为接入站跳转到身份中心完成认证,并在回调后建立本站登录态、绑定本地用户、同步用户资料和映射本地角色。
插件介绍
halo-plugin-sso 适合一组 Halo 站点共用同一套账号体系的场景,例如主站、文档站、社区站、资源站分别部署,但希望用户只使用一个中心账号登录。
插件采用 Authorization Code + PKCE 的登录闭环:
- 接入站发起登录。
- 用户被跳转到身份中心授权端点。
- 身份中心确认用户登录状态和授权条件。
- 身份中心向接入站回调授权码。
- 接入站使用授权码换取 Token,拉取用户身份信息。
- 接入站绑定或创建本地 Halo 用户,映射角色并建立本地登录态。
当前能力包括:
- 身份中心模式:管理接入站、签发授权码、提供 Token 和 UserInfo、下发中心标准角色。
- 接入站模式:跳转身份中心登录、处理回调、绑定或创建本地用户、同步用户资料、映射本地角色。
- 登录入口控制:接入站可选择自动跳转 SSO 登录,也可保留 Halo 默认登录页并手动选择“统一身份认证”。
- 动态认证提供者信息:接入站登录页展示的
displayName、description、logo、website会从身份中心站点基础信息同步。 - 角色映射:身份中心下发中心角色,接入站按映射规则转换为本站本地角色。
- 审计日志:记录接入站登录成功、失败、失败原因,并支持筛选、聚合、手动清理和自动清理。
- 用户绑定:记录中心账号与接入站本地用户的绑定关系。
适用环境
- Halo
>= 2.25.0 - Java 21+
- 一个可被接入站访问的身份中心站点地址,例如
https://auth.example.com - 接入站需要有稳定外部访问地址,例如
https://blog.example.com
生产环境建议使用 HTTPS。开发调试时可在插件设置中临时开启“开发环境允许 HTTP localhost”,但别把这个当生产方案,真上生产这么干就有点硬莽了。
快速开始
完整接入至少需要两个 Halo 站点:
- 站点 A:身份中心。
- 站点 B:接入站。
下面用:
- 身份中心:
https://auth.example.com - 接入站:
https://blog.example.com
1. 安装插件
在身份中心站点和每个接入站点都安装并启用本插件。
构建插件:
./gradlew build
构建完成后,插件 JAR 位于:
build/libs/
将 JAR 上传到 Halo 后台插件管理并启用。
2. 配置身份中心
进入身份中心站点后台:
控制台 -> 插件 -> 统一身份认证 -> 设置
在“基础设置”中配置:
| 配置项 | 建议值 | 说明 |
| --- | --- | --- |
| 运行模式 | 身份中心模式 | 当前站点负责统一登录和授权 |
| 中心标准角色 | subscriber、author、editor 等 | 只有选中的中心角色会下发给接入站 |
| 要求邮箱已验证 | 开启 | 未验证邮箱的中心用户不允许跨站登录 |
然后进入插件控制台页面:
控制台 -> 系统工具 -> 统一身份认证
在“接入站”区域创建一个接入站:
| 字段 | 示例 | 说明 |
| --- | --- | --- |
| 接入站名称 | 我的博客 | 控制台展示用 |
| 站点地址 | https://blog.example.com | 接入站外部访问地址 |
| Redirect URI | https://blog.example.com/apis/public.sso.muyin.site/v1alpha1/client/callback | 接入站回调地址 |
| 启用状态 | 开启 | 关闭后该接入站不能继续登录 |
创建成功后会得到:
Client IDClient Secret
Client Secret 只展示一次,复制保存好。这个东西丢了就重新生成或重建接入站,别指望页面再掏出来给你看。
3. 配置接入站
进入接入站后台:
控制台 -> 插件 -> 统一身份认证 -> 设置
在“基础设置”中配置:
| 配置项 | 示例 | 说明 |
| --- | --- | --- |
| 运行模式 | 接入站模式 | 当前站点负责跳转登录和建立本地登录态 |
| 身份中心地址 | https://auth.example.com | 身份中心站点外部地址 |
| Client ID | 从身份中心复制 | 对应身份中心创建的接入站 |
| Client Secret | 从身份中心复制 | 服务端换取 Token 使用 |
| 默认本地角色 | guest 或其它低权限角色 | 未命中角色映射时使用 |
| 登录时同步用户资料 | 按需开启 | 同步邮箱、用户名、展示名称和头像 |
| 自动跳转 SSO 登录 | 按需开启 | 开启后访问 /login 自动跳转身份中心 |
接入站保存配置后,插件会定期从身份中心同步认证提供者信息。同步来源是身份中心 Halo 系统基础设置:
| AuthProvider 字段 | 身份中心来源 |
| --- | --- |
| displayName | 站点标题 |
| description | 站点副标题 |
| logo | 站点 Logo,未配置时使用 favicon,再未配置时使用插件默认图标 |
| website | 站点外部访问地址 |
如果身份中心临时不可用,接入站会保留上一次同步成功的本地认证提供者信息,不会把登录入口改成空壳。
4. 配置角色映射
进入接入站插件控制台:
控制台 -> 系统工具 -> 统一身份认证 -> 角色映射
创建映射关系:
| 中心角色 | 本地角色 | 说明 |
| --- | --- | --- |
| subscriber | guest | 普通用户 |
| author | contributor | 投稿或作者 |
| editor | editor | 编辑 |
规则说明:
- 身份中心只下发“中心标准角色”中允许的角色。
- 接入站只按启用状态的映射规则转换角色。
- 未命中映射时使用“默认本地角色”。
- 已有本地用户登录时,插件会追加缺失角色,不会清空用户已有本地角色。
登录教程
自动 SSO 登录
适合希望接入站完全交给身份中心登录的场景。
- 在接入站插件设置中开启“自动跳转 SSO 登录”。
- 用户访问接入站
/login。 - 插件自动跳转到身份中心授权页。
- 用户在身份中心登录。
- 登录成功后回到接入站,并建立接入站本地登录态。
如果需要临时访问接入站本地登录页,可以访问:
/login?sso_local=1
手动选择 SSO 登录
适合接入站需要同时保留本地登录和 SSO 登录的场景。
- 在接入站插件设置中关闭“自动跳转 SSO 登录”。
- 到 Halo 的认证提供者配置中启用
muyin-sso。 - 用户访问接入站
/login。 - 用户在登录页选择“统一身份认证”。
- 插件跳转身份中心完成登录。
手动入口由插件声明的 AuthProvider 提供:
metadata.name: muyin-sso
authenticationUrl: /apis/public.sso.muyin.site/v1alpha1/client/login
指定登录后返回地址
接入站登录入口支持 return_url 参数:
/apis/public.sso.muyin.site/v1alpha1/client/login?return_url=/archives/demo
登录完成后会回到指定地址。插件会校验返回地址,只允许本站相对路径,避免被拿去做开放重定向。这个地方不校验就等于给钓鱼链接递刀,不能省。
运行时临时状态与稳定性保护
插件会在内存中短暂保存 OAuth 授权码和接入站发起登录时的 state 会话,这两类数据都只用于一次登录闭环,不作为长期状态保存。
- 授权码有效期为 5 分钟,
/oauth/token成功消费后立即从内存删除;过期授权码会在签发新授权码或消费授权码时清理。 - 未完成登录会话有效期为 10 分钟,接入站回调消费
state后立即从内存删除;过期会话会在发起登录或消费state时清理。 - 授权码和未完成登录会话均设置最大容量
10000,容量达到上限时会拒绝继续创建临时状态,避免异常流量导致内存持续增长。
审核验证可重点查看:
./gradlew test --tests site.muyin.sso.oauth.AuthorizationCodeManagerTest --tests site.muyin.sso.clientlogin.ClientLoginSessionManagerTest
./gradlew build
审计日志与清理
插件会记录接入站登录过程中的关键结果:
- 登录成功。
- 登录失败。
- Client 校验失败。
- Token 换取失败。
- UserInfo 拉取失败。
- 用户创建或绑定失败。
- 角色映射结果。
在插件控制台的“审计日志”中可以:
- 按结果筛选:
success/failure - 按 Client ID 筛选。
- 按关键词搜索 subject、email 或 message。
- 查看最近失败原因聚合。
- 按保留天数执行干跑预览或真实清理。
- 查看最近一次清理状态和清理历史。
插件设置中可以开启“自动清理过期审计日志”。默认关闭,开启后后台任务会定期按保留天数清理过期日志。
公共接口
接入站和身份中心之间主要使用以下接口。
身份中心 OAuth API:
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | /apis/public.sso.muyin.site/v1alpha1/oauth/authorize | OAuth 授权端点 |
| POST | /apis/public.sso.muyin.site/v1alpha1/oauth/token | 使用授权码换取 Token |
| GET | /apis/public.sso.muyin.site/v1alpha1/oauth/userinfo | 获取中心用户信息和中心角色 |
| GET | /apis/public.sso.muyin.site/v1alpha1/oauth/notice | 授权拒绝提示页 |
公共辅助 API:
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | /apis/public.sso.muyin.site/v1alpha1/metadata | 获取身份中心认证提供者元信息 |
| GET | /apis/public.sso.muyin.site/v1alpha1/roles/list | 获取身份中心可下发角色列表 |
| GET | /apis/public.sso.muyin.site/v1alpha1/client/login | 接入站发起 SSO 登录 |
| GET | /apis/public.sso.muyin.site/v1alpha1/client/callback | 接入站处理 SSO 回调 |
Console API 主要由插件后台页面调用,覆盖接入站管理、角色映射、用户绑定、审计日志和设置读取。开发调试时可查看:
api-docs/openapi/v3_0/ssoApis.json
常见问题
接入站登录后回调失败
优先检查身份中心中登记的 Redirect URI 是否和接入站实际地址完全一致。协议、域名、端口、路径都要一致。
标准回调路径为:
https://接入站域名/apis/public.sso.muyin.site/v1alpha1/client/callback
访问接入站 /login 没有跳转身份中心
检查接入站插件设置:
- 运行模式必须是“接入站模式”。
- 身份中心地址、Client ID、Client Secret 必须填写。
- “自动跳转 SSO 登录”必须开启。
如果你关闭了自动跳转,需要在 Halo 认证提供者中启用 muyin-sso,然后在默认登录页手动选择“统一身份认证”。
手动 SSO 登录入口没有展示
检查 Halo 认证提供者配置中是否启用了 muyin-sso。插件声明 AuthProvider 不等于 Halo 一定展示它,展示与否由 Halo 的认证提供者启用状态控制。
接入站登录页展示的名称和 Logo 不对
接入站会从身份中心 /metadata 同步认证提供者信息。请检查:
- 身份中心插件是否为“身份中心模式”。
- 接入站能否访问身份中心地址。
- 身份中心 Halo 系统基础设置中是否配置了标题、副标题、Logo、外部访问地址。
- 身份中心站点外部地址是否正确,尤其是反向代理后的
https地址。
用户登录成功但角色不符合预期
检查三处:
- 身份中心“中心标准角色”是否包含该用户角色。
- 接入站“角色映射”是否配置并启用。
- 接入站“默认本地角色”是否符合预期。
身份中心退出后接入站没有退出
这是当前设计,不是 bug。插件不做统一退出登录。身份中心退出只影响身份中心本地会话,不会强制接入站本地会话失效。
开发
本地开发依赖:
- Java 21+
- Node.js 18+
- pnpm
启动 Halo 开发服务:
./gradlew haloServer
前端开发:
cd ui
pnpm install
pnpm dev
运行测试:
./gradlew test
构建插件:
./gradlew build
生成的插件 JAR 在:
build/libs/
许可证
GPL-3.0 © Lywq
评论