Files
hzdyjs/docs/开发文档/统一身份认证接入说明.md
T
2026-09-22 19:22:18 +08:00

13 KiB
Raw Blame History

统一身份认证接入说明

文档说明

本文档描述校园管理系统(hzdyjs_xg)前端 「统一认证登录」 按钮的完整业务逻辑,涵盖 CAS OAuth2.0 授权流程、前后端协作方式、用户映射规则及常见问题排查。

相关文档:

涉及源码:

模块 文件路径
前端登录页 web/src/views/login.vue
前端路由守卫 web/src/permission.ts
后端 CAS 代理 backend/ruoyi-admin/src/main/java/org/dromara/web/controller/CasController.java

1. 概述

1.1 什么是统一身份认证

统一身份认证(CAS,Central Authentication Service)是学校提供的集中式登录平台。用户在校内 CAS 完成身份验证后,业务系统无需再校验密码,只需根据 CAS 返回的用户标识匹配本地账号,即可签发本系统的登录 Token。

1.2 与普通登录的区别

维度 普通登录 统一认证登录
认证方式 用户名 + 密码(AES+RSA 加密) CAS OAuth2.0 授权码模式
密码校验 后端校验 无,由 CAS 平台完成
用户识别 表单输入 username CAS profile 中的 CODE 字段
Token 来源 POST /auth/login GET /cas/accessToken
租户 表单 tenantId 固定 000000(忽略 CAS 返回的 TENANTID)
登录后行为 路由守卫拉取用户信息 + 动态路由 相同

1.3 协议与模式

  • 协议:OAuth 2.0 授权码模式(Authorization Code)
  • CAS 服务地址:http://td.hzdyjsxy.cn:19081
  • 前端回调地址:http://web.hzdyjsxy.cn:18081

2. 整体流程

sequenceDiagram
    participant User as 用户浏览器
    participant Web as 前端 (web)
    participant CAS as 统一认证 CAS<br/>td.hzdyjsxy.cn:19081
    participant Backend as 后端 (ruoyi-admin)
    participant DB as 本地用户库 sys_user

    User->>Web: 点击「统一认证登录」
    Web->>CAS: 跳转 /cas/oauth2.0/authorize<br/>?client_id=...&redirect_uri=...
    User->>CAS: 输入校内统一账号密码
    CAS->>Web: 302 回调 redirect_uri?code=ST-xxx
    Web->>Web: login.vue 检测到 code
    Web->>Backend: GET /cas/accessToken?code=ST-xxx
    Backend->>CAS: 用 code 换 access_token
    Backend->>CAS: 用 access_token 拉 profile
    CAS-->>Backend: 返回 attributes(CODE, TENANTID...)
    Backend->>DB: 按 CODE 查 sys_user.user_name
    Backend->>Backend: Sa-Token 生成本系统 token
    Backend-->>Web: { access_token: "..." }
    Web->>Web: setToken + 跳转首页
    Web->>Backend: 路由守卫拉 getInfo + 动态路由

3. 前端实现

3.1 入口:统一认证登录按钮

登录页 web/src/views/login.vue 提供两个并列按钮:

  • 登录 — 普通账号密码登录
  • 统一认证登录 — 跳转 CAS 授权页

从单点登录门户自动认证(2026-06-25 新增):

当用户从学校单点登录门户跳转到本系统登录页时,无需再点击「统一认证登录」,页面会自动跳转 CAS 完成认证。直接打开 /login(无 redirect)仍可使用账号密码登录。

单点门户典型入口 URL(无需 sso、from 等额外参数):

http://web.hzdyjsxy.cn:18081/login?redirect=/index

识别「来自 SSO / 外部入口」的条件(满足任一即可):

条件 说明
URL 带 redirect=/index 且 Referer 不是本站 单点门户固定链接;本地未登录为 redirect=/ 不会触发
Referer 来自 td.hzdyjsxy.cn、hzdyjsxy.cn 从统一认证门户点击链接触发
URL 参数 sso=1 或 from=sso / from=cas 可选,门户显式携带时同样生效

仅生产域名 web.hzdyjsxy.cn 启用自动跳转;本地 localhost 开发不会误触发 CAS 跳转到生产环境。

不自动跳转的场景:用户在本系统内操作导致会话过期,被重定向到 login?redirect=... 且 Referer 为 web.hzdyjsxy.cn 时,保留账号密码登录。

处理顺序:onMounted 时若 URL 已有 code 则直接换 Token;否则若判定为外部/SSO 入口则自动 handleUnifiedLogin()。

3.2 跳转 CAS 授权页

点击「统一认证登录」后,浏览器整页跳转到 CAS 授权地址:

GET http://td.hzdyjsxy.cn:19081/cas/oauth2.0/authorize
    ?response_type=code
    &client_id=0i7tcT5tULqvmmbbV8
    &redirect_uri=http%3A%2F%2Fweb.hzdyjsxy.cn%3A18081

当前写死的配置项(login.vue):

配置项 值 说明
CAS_BASE http://td.hzdyjsxy.cn:19081 CAS 服务器地址
CAS_CLIENT_ID 0i7tcT5tULqvmmbbV8 OAuth2 客户端 ID
CAS_CLIENT_SECRET 68c4f7641523452aaf5ef6d0432869f7 客户端密钥(仅后端使用,前端不应暴露)
FRONTEND_REDIRECT_URI http://web.hzdyjsxy.cn:18081 CAS 授权成功后的回调地址

注意:client_secret 在前端代码中有定义但未在跳转 URL 中使用;实际换 token 时由后端携带。

3.3 CAS 回调与 code 提取

用户在 CAS 登录成功后,浏览器被重定向到:

http://web.hzdyjsxy.cn:18081?code=ST-xxxxxxxx

login.vue 在 onMounted 时自动检测 URL 中的 code,支持三种提取方式:

  1. 直接从 route.query.code 获取
  2. 从 redirect 参数中解析(如 redirect=/?code=ST-xxx)
  3. 从 route.fullPath 中正则匹配

回调路径说明:若用户先落到 / 且无 Token,permission.ts 路由守卫会将其重定向到 /login?redirect=...,code 会保留在 redirect 参数中,登录页仍能正确解析。

3.4 通过后端代理换取 Token

前端不直接请求 CAS(避免浏览器 CORS 限制),而是调用后端代理接口:

GET {VITE_APP_BASE_API}/cas/accessToken?code={code}

收到响应后,从以下字段之一提取 Token:

  • body.data.access_token
  • body.access_token
  • body.data.accessToken
  • body.accessToken

提取成功后:

  1. 调用 setToken() 持久化 Token
  2. 更新 userStore.token
  3. 跳转到 redirect 参数指定页面或首页 /

3.5 登录后的权限加载

CAS 登录只写入 Token,不会调用 userStore.login()。进入系统后,permission.ts 路由守卫检测到「有 Token 但 roles 为空」时,会自动:

  1. 调用 getInfo() 拉取用户信息与权限
  2. 调用 generateRoutes() 生成动态菜单路由
  3. 进入目标页面

因此,CAS 登录与普通登录在进入系统之后的行为完全一致。


4. 后端实现

4.1 接口信息

项目 值
接口地址 GET /cas/accessToken
权限要求 无需认证(@SaIgnore)
Controller CasController.java
请求参数 code(必填,CAS 授权码)

4.2 处理步骤

步骤 1:用 code 换取 CAS access_token

GET http://td.hzdyjsxy.cn:19081/cas/oauth2.0/accessToken
    ?client_id=0i7tcT5tULqvmmbbV8
    &client_secret=68c4f7641523452aaf5ef6d0432869f7
    &code={code}
    &redirect_uri=http%3A%2F%2Fweb.hzdyjsxy.cn%3A18081

CAS 可能返回 JSON 或 query-string 格式,后端兼容两种解析方式:

  • JSON:{"access_token":"xxx","expires_in":3600}
  • Query-string:access_token=xxx&expires_in=3600

步骤 2:用 access_token 获取用户信息

GET http://td.hzdyjsxy.cn:19081/cas/oauth2.0/profile?access_token={access_token}

从返回 JSON 的 attributes 数组中解析:

CAS 字段 用途 本系统映射
CODE 用户唯一标识 → sys_user.user_name
TENANTID 租户标识 当前被忽略,强制使用 000000

步骤 3:匹配本地用户并签发 Token

  1. 在租户 000000 上下文中,按 user_name = CODE 查询 sys_user
  2. 校验用户存在且状态非禁用
  3. 调用 loginService.buildLoginUser(user) 构建 LoginUser
  4. 使用默认客户端 e5cd7e4891bf95d1d19206ce24a7b32e 配置超时等参数
  5. 调用 LoginHelper.login() 通过 Sa-Token 签发本系统 Token

步骤 4:返回登录结果

响应示例:

{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiJ9...",
    "expire_in": 7200,
    "client_id": "e5cd7e4891bf95d1d19206ce24a7b32e"
  }
}

4.3 错误处理

场景 返回信息
code 为空 code 不能为空
无法解析 access_token 无法解析 CAS access_token,原始响应: ...
profile 无用户信息 CAS profile 未包含用户信息
本地用户不存在 user.not.exists
用户已禁用 user.blocked
CAS 请求异常 请求 CAS accessToken 失败: ...

5. 配置清单

5.1 CAS 平台侧(需运维/信息中心配置)

  • 注册 OAuth2 应用,获取 client_id 和 client_secret
  • 配置授权回调地址 redirect_uri 与代码一致
  • 确认 profile 接口返回 attributes 中包含 CODE 字段
  • 确认 CAS 服务器 td.hzdyjsxy.cn:19081 对后端服务器网络可达

5.2 本系统侧

  • sys_user 表中存在与 CAS CODE 一致的 user_name 记录
  • 用户状态为正常(非禁用)
  • 前端 redirect_uri 与 CAS 注册地址一致
  • 后端 CasController 中 CAS 地址、client 配置与 CAS 平台一致
  • 生产环境部署时,将硬编码 IP 改为实际域名/地址

5.3 当前硬编码配置汇总

前端(web/src/views/login.vue):

CAS_BASE              = http://td.hzdyjsxy.cn:19081
CAS_CLIENT_ID         = 0i7tcT5tULqvmmbbV8
FRONTEND_REDIRECT_URI = http://web.hzdyjsxy.cn:18081

后端(CasController.java):

CAS_BASE              = http://td.hzdyjsxy.cn:19081
CLIENT_ID             = 0i7tcT5tULqvmmbbV8
CLIENT_SECRET         = 68c4f7641523452aaf5ef6d0432869f7
REDIRECT_URI          = http://web.hzdyjsxy.cn:18081
DEFAULT_CLIENT_ID     = e5cd7e4891bf95d1d19206ce24a7b32e
DEFAULT_TENANT_ID     = 000000

6. 常见问题排查

6.1 点击按钮后无法跳转 CAS

  • 检查浏览器能否访问 http://td.hzdyjsxy.cn:19081
  • 检查 CAS 平台是否正常运行
  • 检查 client_id 是否已在 CAS 注册

6.2 回调后停留在登录页 / 登录失败

  • 检查 URL 是否携带 code 参数
  • 检查 code 是否已被使用(授权码一次性有效,刷新页面会失败)
  • 打开浏览器控制台查看 /cas/accessToken 请求响应

6.3 提示「用户不存在」

  • CAS profile 中 CODE 值与 sys_user.user_name 不一致
  • 需在系统管理中预先创建对应用户,或建立 CAS 用户与本地用户的同步机制

6.4 提示「用户已禁用」

  • 检查 sys_user.status 字段是否为正常状态

6.5 登录成功但无菜单 / 权限

  • Token 已写入但 getInfo() 失败,检查后端 /auth/getInfo 接口
  • 检查用户是否已分配角色和菜单权限

6.6 CORS 相关

  • 前端不应直接请求 CAS,必须通过后端 /cas/accessToken 代理
  • 若后端也报连接失败,检查后端服务器到 CAS 的网络连通性

7. 后续优化建议

  1. 配置外置化:将 CAS 地址、client_id、client_secret、redirect_uri 移至 application.yml,避免硬编码(后端 secret 仍待外置)
  2. 密钥安全:前端不应保留 CAS_CLIENT_SECRET(已删除;换票仅走后端;CAS 相关日志已脱敏,禁止打印 secret/token/profile)
  3. 租户映射:如需支持多租户,可恢复对 CAS TENANTID 的解析逻辑
  4. 用户自动注册:CAS 用户首次登录时自动创建本地账号(需评估权限与安全策略)
  5. 移动端接入:mobile 工程可参考相同 OAuth2 流程实现统一认证登录
  6. 退出登录:考虑对接 CAS 单点登出(SLO)接口

8. 附录:关键代码索引

8.1 前端 — 跳转 CAS

// web/src/views/login.vue
const handleUnifiedLogin = () => {
  const authorizeUrl = `${CAS_BASE}/cas/oauth2.0/authorize?response_type=code&client_id=${CAS_CLIENT_ID}&redirect_uri=${FRONTEND_REDIRECT_URI_ENCODED}`;
  window.location.href = authorizeUrl;
};

8.2 前端 — 回调处理

// web/src/views/login.vue — onMounted
const code = extractCodeFromRoute(router.currentRoute.value);
if (code) {
  fetchAccessToken(code);
}

8.3 后端 — 用户映射与登录

// CasController.java
LoginUser loginUser = TenantHelper.dynamic(finalTenantId, () -> {
    SysUserVo user = userMapper.selectVoOne(
        new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, finalUsername));
    // 校验存在性、状态...
    return loginService.buildLoginUser(user);
});
LoginHelper.login(loginUser, model);

文档版本:v1.1
创建日期:2026-06-10
更新日期:2026-06-25 — 新增从 SSO 门户进入登录页时自动统一认证
维护说明:CAS 配置变更或登录逻辑调整时,请同步更新本文档及 用户认证登录API。