# 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: ```text https:///api/v1/auth/cas//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`: ```dotenv 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 ``` 启动前运行: ```bash ./scripts/validate-release-config.sh .env.release ``` ### Helm ```yaml 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. 身份映射 默认映射: ```text 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. 匿名请求登录方法目录: ```bash 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。 上游参考: - [Spring Security CAS reference](https://docs.spring.io/spring-security/reference/servlet/authentication/cas.html) - [Apereo validator logging](https://github.com/apereo/java-cas-client/blob/cas-client-4.1.1/cas-client-core/src/main/java/org/apereo/cas/client/validation/AbstractUrlBasedTicketValidator.java) - [Apereo CAS 2 attribute parser](https://github.com/apereo/java-cas-client/blob/cas-client-4.1.1/cas-client-core/src/main/java/org/apereo/cas/client/validation/Cas20ServiceTicketValidator.java) 如果上游版本修复这些问题,可以在保持错误分类、响应上限和无敏感日志测试的前提下重新 评估替换 transport/parser。