Go 后台接入 CAS 单点登录后,原来的权限怎么继续生效?

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 的完整协议选型。写工程方案时,把这个边界说清楚,比堆一串功能名有用。

一份可以直接抄走的验收清单

  1. CAS ticket 校验失败时返回明确错误,不创建本地用户。

  2. CAS 返回账号必须映射到本地 admin 用户,未绑定账号不能自动给默认管理员权限。

  3. 本地用户禁用后,即使 CAS 仍能认证,也不能进入后台。

  4. 登录后前端菜单、按钮权限要从后台重新拉取,不使用旧缓存。

  5. 后端敏感接口必须有权限中间件,不能只依赖菜单隐藏。

  6. 导出、批量删除、角色分配、用户禁用要分别测 401、403、200。

  7. 角色或权限变更后,要清理用户权限缓存或强制重新签发 token。

  8. 操作日志里要记录本地用户 ID、外部账号、权限码、接口路径和状态码。

  9. 回滚方案要保留原登录入口或管理员应急入口,避免统一登录故障时后台完全进不去。

  10. 文档里写清楚:CAS 负责认证,后台 RBAC 负责授权,不要让后来的维护者误删本地权限链路。

结论

CAS 接进 Go 后台以后,权限系统真正要保住的是"本地业务授权"。登录成功只能证明人是真的,不能证明他能导出用户、分配角色或批量删除数据。比较稳的做法是保留本地用户、角色、菜单和接口权限,用 CAS 账号做映射,再用 401、403、200 的请求把前端路由和后端中间件都验一遍。

核验时点:2026-08-30。动态事实以当日 GitHub Issue、源码路径和公开仓库状态为准;后续如果 CAS 支持方式、权限中间件路径或前端路由实现变化,需要重新读源码后再更新文档。

相关推荐
断点之下18 分钟前
C++类和对象:六个默认成员函数详解
开发语言·c++
吴声子夜歌24 分钟前
Java——类、对象及方法(二)
java·开发语言
2401_8906034026 分钟前
Python入门语法(一)
java·开发语言·python
小蒜学长27 分钟前
springboot党建云课堂学习与管理系统(代码+数据库+LW)
java·数据库·spring boot·后端·学习
烂蜻蜓2 小时前
Flask入门教程(十二):表单处理——从基础到Flask-WTF完整实践
后端·python·flask
Chester_19998 小时前
CSP202203C.计算资源调度器
开发语言·数据结构·c++·蓝桥杯
2601_962055979 小时前
跟据spring boot版本,查看对应的tomcat,并查看可支持的tomcat的版本范围
spring boot·后端·tomcat
EXI-小洲9 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word