skillhub/docs/23-cas-integration.md
XiaoSeS 2c1bcf3b22 fix(auth): harden CAS identity link flow
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
2026-07-31 12:01:38 +08:00

7.8 KiB
Raw Permalink Blame History

CAS 2.0/3.0 接入指南

本文说明如何把一个 CAS Provider Instance 接入 SkillHub 统一身份核心。CAS 登录默认关闭, 配置不完整或 Authority 状态异常时不会出现在登录方法目录中。

1. 前置条件

  • SkillHub 的公开入口和 CAS Server 使用 HTTPS。

  • Redis 可用。CAS state 使用 Redis 原子消费,以支持多 Pod 和重放保护。

  • CAS Server 已允许下面的精确 Service URL:

    https://<skillhub-host>/api/v1/auth/cas/<provider-code>/callback
    
  • 已确认可作为稳定身份键的 CAS principal 或 immutable attribute。

provider-code 和 authority 是持久身份绑定的一部分。产生 Identity Binding 后不能通过 普通配置变更切换到另一个身份域。

2. 配置

环境变量 必填 默认值 说明
SKILLHUB_AUTH_CAS_ENABLED 是 false 显式启用 CAS
SKILLHUB_AUTH_CAS_PROVIDER_CODE 是 cas Provider Instance code,需长期稳定
SKILLHUB_AUTH_CAS_DISPLAY_NAME 是 CAS 登录页展示名
SKILLHUB_AUTH_CAS_AUTHORITY 是 空 稳定 CAS 身份域 ID
SKILLHUB_AUTH_CAS_SERVER_URL 是 空 CAS Server 根地址,例如 https://cas.example.com/cas
SKILLHUB_AUTH_CAS_SERVICE_URL 是 空 精确 SkillHub callback,不含 query/fragment
SKILLHUB_AUTH_CAS_PROTOCOL_VERSION 是 3.0 2.0 或 3.0
SKILLHUB_AUTH_CAS_SUBJECT_TYPE 是 cas_principal 统一身份核心中的 Subject type
SKILLHUB_AUTH_CAS_ATTRIBUTES_SUBJECT 否 空 为空时使用 CAS principal;否则使用唯一 immutable attribute
SKILLHUB_AUTH_CAS_ATTRIBUTES_DISPLAY_NAME 否 空 display name attribute
SKILLHUB_AUTH_CAS_ATTRIBUTES_EMAIL 否 空 email attribute,固定按 asserted 处理
SKILLHUB_AUTH_CAS_ATTRIBUTES_AVATAR_URL 否 空 avatar URL attribute
SKILLHUB_AUTH_CAS_CONNECT_TIMEOUT 否 PT5S 连接超时
SKILLHUB_AUTH_CAS_READ_TIMEOUT 否 PT10S 请求总超时
SKILLHUB_AUTH_CAS_STATE_TTL 否 PT5M 登录 state 有效期,最大 15 分钟
SKILLHUB_AUTH_CAS_MAX_RESPONSE_BYTES 否 1048576 validation 响应上限,1 KiB–1 MiB

CAS 返回的普通 email attribute 不是 verified email,不能用于 EMAIL_DOMAIN 准入或静默账号绑定。

Docker Compose

编辑 .env.release:

SKILLHUB_AUTH_CAS_ENABLED=true
SKILLHUB_AUTH_CAS_PROVIDER_CODE=cas-main
SKILLHUB_AUTH_CAS_DISPLAY_NAME=Corporate CAS
SKILLHUB_AUTH_CAS_AUTHORITY=corp-cas
SKILLHUB_AUTH_CAS_SERVER_URL=https://cas.example.com/cas
SKILLHUB_AUTH_CAS_SERVICE_URL=https://skills.example.com/api/v1/auth/cas/cas-main/callback
SKILLHUB_AUTH_CAS_PROTOCOL_VERSION=3.0
SKILLHUB_AUTH_CAS_SUBJECT_TYPE=cas_principal
SKILLHUB_AUTH_CAS_ATTRIBUTES_DISPLAY_NAME=displayName
SKILLHUB_AUTH_CAS_ATTRIBUTES_EMAIL=mail

启动前运行:

./scripts/validate-release-config.sh .env.release

Helm

auth:
  cas:
    enabled: true
    providerCode: cas-main
    displayName: Corporate CAS
    authority: corp-cas
    serverUrl: https://cas.example.com/cas
    serviceUrl: https://skills.example.com/api/v1/auth/cas/cas-main/callback
    protocolVersion: "3.0"
    subjectType: cas_principal
    connectTimeout: PT5S
    readTimeout: PT10S
    stateTtl: PT5M
    maxResponseBytes: 1048576
    attributes:
      subject: ""
      displayName: displayName
      email: mail
      avatarUrl: ""

Chart 在渲染阶段拒绝 HTTP endpoint、缺失 URL 和 provider code 不匹配的 callback。

Kustomize

修改 deploy/k8s/base/configmap.yaml 中的 auth-cas-* 字段,然后重新应用 overlay。 CAS 配置不包含 client secret;若企业扩展引入凭证,必须放入 Kubernetes Secret,不能放 在 ConfigMap。

3. 身份映射

默认映射:

primary subject = CAS principal
subject type    = cas_principal
email assurance = PROVIDER_ASSERTED
aliases         = none

如果 CAS principal 可能随用户名变更,必须配置 SKILLHUB_AUTH_CAS_ATTRIBUTES_SUBJECT 指向 CAS Server 明确定义且实际返回的 immutable attribute,并为该属性选择稳定的 subject-type。属性缺失、多值或空值时登录 fail closed。

不要把 email、display name 或临时用户名当作稳定 Subject。

4. 验证

  1. 匿名请求登录方法目录:

    curl -fsS 'https://skills.example.com/api/v1/auth/methods'
    

    只在 Provider 为 READY 时应出现 CAS_REDIRECT。

  2. 在浏览器点击 CAS 登录,确认重定向到 CAS /login,其 service 解码后与配置的 callback 加一次性 state 完全一致。

  3. 完成 CAS 登录,确认返回原始 returnTo 或默认页面,并能请求 /api/v1/auth/me。

  4. 再次请求相同 callback,必须得到 casInvalidState 或 CAS Ticket 验证失败,不能建立 第二个 Session。

  5. 检查应用日志中不包含 ST- Ticket、validation URL、上游完整响应或用户属性。

  6. 禁用 Provider 后,方法目录不再显示 CAS,且点击旧 URL 不应连接 CAS Server。

  7. 在账号安全页验证 CAS Identity Link:已绑定 CAS 可以完成 fresh reauthentication, READY intent 可以通过目标 CAS 创建 Binding;失败后应回到同一 intent,成功后不能重放。

建议分别验证 CAS 2 XML、CAS 3 JSON、CAS 3 XML fallback、无效 Ticket、错误 Service、 超时、TLS 失败、XXE、超大响应和 Redis 不可用。

CAS callback 按协议会在 query string 中携带一次性 ticket 和 state。官方 Web 镜像不记录 query string 或 Referer,应用请求日志会对它们进行隐藏;如果前面还有 Ingress、负载均衡、WAF、APM 或其他反向代理,必须将该 callback 路径的 query string 和 Referer 关闭记录或至少对 ticket、state 脱敏。上线前应使用唯一测试 Ticket 检查完整日志链路,确认没有原文残留。重复 state 会分类为 REPLAY_DETECTED 并写入 不含 state/Ticket 的安全审计。

5. 升级与回滚

  • 新配置默认关闭,不影响现有本地密码、GitHub、GitLab 或 OIDC 登录。
  • 本次接入不修改 PlatformPrincipal 或现有 Session 序列化结构。
  • 旧版本回滚后会忽略 CAS 环境变量;现有非 CAS 登录仍可使用。
  • CAS 已产生 Binding 后回滚会暂时失去 CAS 登录入口,但不会删除账号、Binding 或业务 数据。
  • 修改 CAS endpoint 可以保持同一 Authority;修改 Authority 必须走统一身份设计中的 显式 Authority 迁移流程。

6. 安全实现说明

实现阶段验证了 Spring Security CAS 所依赖的 Apereo Java CAS Client。其 4.1.1 AbstractUrlBasedTicketValidator 仍会在 DEBUG 记录包含 Ticket 的 validation URL 和完整 响应;CAS 2 自定义属性解析路径也没有满足本项目要求的全部 XML parser fail-closed 设置。因此当前实现没有调用该库的 URL validator,而是使用受限 JDK HTTP transport 和 严格 parser:

  • 禁止 redirect;
  • 明确连接、请求和响应大小上限;
  • XML 禁止 DOCTYPE、外部实体、外部 DTD 和外部 schema;
  • Ticket、完整响应和属性不写日志或异常;
  • CAS 3 优先 JSON,并支持受限 XML fallback。

上游参考:

如果上游版本修复这些问题,可以在保持错误分类、响应上限和无敏感日志测试的前提下重新 评估替换 transport/parser。