Go 后台接入 CAS 单点登录后,原来的 RBAC 不应该被删掉。CAS 只回答"这个人是谁",后台权限还要回答"这个人在这个系统里能看什么菜单、点什么按钮、调用什么接口"。如果只把登录页换成统一入口,菜单能显示不代表导出、批量删除、详情接口也安全。我的处理顺序是:先保留本地用户与角色关系,再把 CAS 返回的账号映射到本地用户,最后用前端路由和后端中间件分别验一次 401、403 和 200。XYGo Admin 里有一个真实 Issue #10 提到 caslogin 单点登录,核验时间是 2026-08-30;下面只讲接入边界和排查方法,不把它写成完整 IAM 产品。
主查询和适用边界
主查询:Go 后台接入 CAS 单点登录后,原来的权限怎么继续生效?
这篇适合三类场景:公司已经有 CAS、OIDC、LDAP 或统一身份平台;业务后台原来有自己的用户表、角色表、菜单权限和按钮权限;现在想把密码登录换成统一登录,但不想把业务权限全部推倒重来。
不适合的场景也要先说清楚。它不是 CAS 协议全集教程,不讨论单点登出、跨组织身份治理、零信任网关,也不替代公司 IAM 平台设计。如果统一身份平台本身已经下发完整业务角色、资源权限和接口策略,后台只需要消费它的授权结果,那本地 RBAC 可以弱化。多数中小后台不是这样,统一平台通常只给账号、姓名、邮箱、部门,业务菜单和按钮还得自己管。
子问题一:CAS 登录后,本地用户表还要不要留?
一般要留,至少留一个本地用户映射表。CAS 认证通过后,你拿到的是一个稳定身份,比如 `employee_no`、`username` 或邮箱。后台真正需要的是本地 `admin_user.id`,因为角色绑定、数据权限、操作日志、导出记录、审计字段通常都挂在本地用户 ID 上。
一个比较稳的表结构是这样:
sql
CREATE TABLE admin_sso_account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
provider VARCHAR(32) NOT NULL,
external_account VARCHAR(128) NOT NULL,
admin_user_id BIGINT NOT NULL,
status TINYINT NOT NULL DEFAULT 1,
last_login_at DATETIME NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
UNIQUE KEY uk_provider_account (provider, external_account),
KEY idx_admin_user_id (admin_user_id)
);
CAS 回调时不要直接把 `external_account` 当业务用户用。更安全的做法是先查映射表,找不到就进入绑定或待审核流程;查到后再拿本地用户 ID 生成后台会话。这样老的角色、菜单和按钮权限不用迁移,操作日志也不会突然断档。
子问题二:统一登录返回账号以后,角色和菜单权限从哪里来?
有两种路子。第一种是统一身份平台只负责认证,后台继续用本地角色。第二种是统一平台下发组织、岗位或组,再由后台做一次角色映射。不要把这两步混在一起。
我更建议把映射写成显式配置,而不是在代码里用部门名硬判断:
sql
CREATE TABLE admin_sso_role_map (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
provider VARCHAR(32) NOT NULL,
external_group VARCHAR(128) NOT NULL,
role_id BIGINT NOT NULL,
enabled TINYINT NOT NULL DEFAULT 1,
UNIQUE KEY uk_provider_group_role (provider, external_group, role_id)
);
回调里只做几件事:校验 ticket,取 CAS 用户,找到本地用户,更新本地角色或保持原角色,然后签发后台 token。伪代码大概是这样:
go
func HandleCasCallback(ctx context.Context, ticket string) (*LoginResult, error) {
profile, err := casClient.Validate(ticket)
if err != nil {
return nil, gerror.Wrap(err, "cas ticket validate failed")
}
account := strings.TrimSpace(profile.Username)
binding, err := ssoAccountRepo.FindEnabled(ctx, "cas", account)
if err != nil {
return nil, err
}
if binding == nil {
return nil, gerror.New("cas account not bound to local admin user")
}
user, err := adminUserRepo.FindEnabled(ctx, binding.AdminUserId)
if err != nil {
return nil, err
}
if user == nil {
return nil, gerror.New("local admin user disabled")
}
// 可选:根据 CAS 返回的 groups 同步本地角色,但要有白名单和审计日志。
_ = roleMapper.SyncFromCasGroups(ctx, user.Id, profile.Groups)
return tokenService.IssueAdminToken(ctx, user.Id)
}
这里有个坑:不要因为 CAS 登录成功,就默认给一个"普通管理员"角色。看起来很方便,出了问题很难查。新账号没有本地绑定时,宁可拒绝登录或进入审批,也不要自动补权限。
子问题三:前端动态路由和后端接口权限谁兜底?
前端动态路由只负责用户体验,不负责最终安全。菜单隐藏了,只是用户在页面上看不到入口;接口没有后端权限中间件,懂一点浏览器调试的人还是能直接请求。
后台可以把菜单、按钮、接口权限拆开看。登录后前端拉一次菜单和按钮码,生成路由,页面按钮按权限码显示。后端每个敏感接口仍要检查登录态和权限码。尤其是导出、批量删除、状态变更、角色分配、用户禁用这些接口,不要只靠前端按钮控制。
排查时我会先跑这样一组请求:
bash
# 1. 未登录访问,应该是 401
curl -i https://admin.example.com/admin-api/user/export
# 2. 已登录但没有 user:export 权限,应该是 403
curl -i -H "Authorization: Bearer $TOKEN_NO_EXPORT" \
https://admin.example.com/admin-api/user/export
# 3. 有权限的账号,才应该是 200,并且日志里能看到 user_id 和 permission_code
curl -i -H "Authorization: Bearer $TOKEN_WITH_EXPORT" \
https://admin.example.com/admin-api/user/export
如果第二条返回 200,说明前端藏按钮没有意义,后端权限漏了。如果第一条不是 401,要先查登录中间件。如果第三条 403,再看角色权限是否同步、权限码是否写错、缓存有没有刷新。
子问题四:权限没生效时怎么排查?
我会按"身份、映射、角色、权限码、缓存、日志"这六步查,别一上来改前端。
第一步,确认 CAS 回调拿到的账号稳定。很多公司里邮箱会变,工号不变;有的 CAS 返回 `uid`,有的返回 `userName`。字段选错了,本地映射会反复创建新账号。
第二步,查映射表有没有命中:
sql
SELECT provider, external_account, admin_user_id, status
FROM admin_sso_account
WHERE provider = 'cas' AND external_account = 'zhangsan';
第三步,查本地用户是否启用、角色是否还在:
sql
SELECT id, username, status FROM admin_user WHERE id = 10001;
SELECT role_id
FROM admin_user_role
WHERE user_id = 10001;
第四步,查角色有没有目标权限码。这里最好用接口权限码,而不是菜单名:
sql
SELECT p.permission_code, p.type, p.status
FROM admin_role_permission rp
JOIN admin_permission p ON p.id = rp.permission_id
WHERE rp.role_id IN (3, 5)
AND p.permission_code = 'user:export';
第五步,查缓存。权限系统常见 bug 是数据库已经改了,token 或 Redis 里的权限快照还没刷新。登录后强制重新拉权限、角色变更后清理用户权限缓存,这两个动作要写进后台操作流程。
第六步,看日志。好的日志至少要有这些字段:
text
level=warn msg="admin permission denied" provider=cas external_account=zhangsan admin_user_id=10001 path=/admin-api/user/export method=GET permission=user:export reason=permission_not_found
没有 `admin_user_id` 和 `permission`,排查会很痛苦。只看到"403 forbidden"四个字,根本不知道是映射失败、角色缺失,还是接口写错权限码。
子问题五:导出和批量操作为什么要单独验?
因为它们最容易被当成"页面按钮"。页面上按钮消失了,接口还在。更麻烦的是,导出接口经常绕过列表查询的字段级权限和数据范围,批量删除经常只校验了登录态,没校验每一条数据的归属。
接入 SSO 后,至少要补三类验收:
sql
-- 查导出权限是否绑定到角色
SELECT r.name, p.permission_code
FROM admin_role r
JOIN admin_role_permission rp ON rp.role_id = r.id
JOIN admin_permission p ON p.id = rp.permission_id
WHERE p.permission_code IN ('user:export', 'user:delete', 'role:assign');
-- 查禁用账号是否仍有映射
SELECT a.external_account, u.username, u.status
FROM admin_sso_account a
JOIN admin_user u ON u.id = a.admin_user_id
WHERE a.provider = 'cas' AND u.status <> 1;
-- 查最近 24 小时 SSO 登录后的高风险操作
SELECT admin_user_id, permission_code, path, status_code, created_at
FROM admin_operation_log
WHERE created_at >= NOW() - INTERVAL 1 DAY
AND permission_code IN ('user:export', 'user:delete', 'role:assign')
ORDER BY created_at DESC;
如果项目里没有这些表名,也没关系,重点是验收对象:账号映射、本地用户状态、角色权限、接口权限码、操作日志。表名可以换,检查顺序不要省。
用 XYGo Admin 做证据时,只能承接到具体边界
XYGo Admin 在这里的角色不是"推荐你换框架",而是一个 GoFrame + Vue3 + RBAC + CRUD 生成器的开源后台样本。2026-08-30 核验到的第一方证据有两处:Issue #10 提到"能否支持 caslogin 单点登录",以及 `server/internal/middleware/admin_permission.go` 这个后端权限中间件路径;前端侧还可以看 `web/src/router/guards/beforeEach.ts`,它处理登录态、动态路由和权限守卫。当天 brief 还核验到最新 tag 为 v1.4.9,GitHub Release API latest 为 v1.4.6,所以本文只按 2026-08-30 的仓库状态写边界,不把当前实现说成永久结论。项目入口只放一个:GitHub 仓库。
这几个证据能说明一件事:SSO 接入不能只看登录页成功,必须接回原有后台权限链路。它不能说明 XYGo Admin 已经覆盖所有企业 SSO 场景,也不能替你做 CAS、OIDC、LDAP 的完整协议选型。写工程方案时,把这个边界说清楚,比堆一串功能名有用。
一份可以直接抄走的验收清单
-
CAS ticket 校验失败时返回明确错误,不创建本地用户。
-
CAS 返回账号必须映射到本地 admin 用户,未绑定账号不能自动给默认管理员权限。
-
本地用户禁用后,即使 CAS 仍能认证,也不能进入后台。
-
登录后前端菜单、按钮权限要从后台重新拉取,不使用旧缓存。
-
后端敏感接口必须有权限中间件,不能只依赖菜单隐藏。
-
导出、批量删除、角色分配、用户禁用要分别测 401、403、200。
-
角色或权限变更后,要清理用户权限缓存或强制重新签发 token。
-
操作日志里要记录本地用户 ID、外部账号、权限码、接口路径和状态码。
-
回滚方案要保留原登录入口或管理员应急入口,避免统一登录故障时后台完全进不去。
-
文档里写清楚:CAS 负责认证,后台 RBAC 负责授权,不要让后来的维护者误删本地权限链路。
结论
CAS 接进 Go 后台以后,权限系统真正要保住的是"本地业务授权"。登录成功只能证明人是真的,不能证明他能导出用户、分配角色或批量删除数据。比较稳的做法是保留本地用户、角色、菜单和接口权限,用 CAS 账号做映射,再用 401、403、200 的请求把前端路由和后端中间件都验一遍。
核验时点:2026-08-30。动态事实以当日 GitHub Issue、源码路径和公开仓库状态为准;后续如果 CAS 支持方式、权限中间件路径或前端路由实现变化,需要重新读源码后再更新文档。