CAS单点登录架构详解
目录
- 概述
- 核心概念
- TGC (Ticket Granting Cookie)
- TGT (Ticket Granting Ticket)
- ST (Service Ticket)
- 架构设计
- 整体架构
- 存储架构
- 安全机制
- 工作流程
- 完整登录时序
- 流程步骤详解
- 技术实现
- 会话管理
- 数据同步问题
- 最佳实践
- 参考资料
概述
CAS (Central Authentication Service) 是一个企业级单点登录解决方案,为多个应用系统提供统一的身份认证服务。通过CAS,用户只需要在认证中心登录一次,即可访问所有受保护的应用系统,无需重复登录。
核心特性
- 单点登录:用户只需登录一次即可访问所有授权应用
- 安全可靠:基于票据机制,确保认证过程的安全性
- 集中管理:统一的用户认证和会话管理
- 应用解耦:应用系统无需关心具体的认证逻辑
适用场景
- 企业内部多个应用系统的统一登录
- 微服务架构中的身份认证
- 跨域应用的单点登录需求
- 需要集中用户管理的复杂系统
核心概念
CAS采用基于票据的认证机制,通过三种不同类型的票据来实现安全可靠的单点登录功能。
TGC (Ticket Granting Cookie)
TGC是CAS会话的唯一标识符,是连接用户浏览器和CAS服务器的桥梁。
基本特性
- 本质:CAS服务器session的唯一标识(sessionId)
- 作用:作为TGT的key,用于查找对应的用户会话信息
- 存储位置:以cookie形式保存在用户浏览器中
- 域限制:只在CAS服务器域下有效(如:sso.company.com)
- 传输方式:每个HTTP请求自动携带
技术实现
TGC → sessionId (key)
TGT → session content (value)
关系:TGC:TGT = key:value
TGT (Ticket Granting Ticket)
TGT是CAS为用户签发的登录凭证,是验证用户登录成功的核心票据。
存储机制
重要说明:TGT本身不是存储在cookie中,而是存储在CAS服务器端。
- 服务器端存储:TGT存储在CAS服务器端,具体存储位置取决于配置
- Cookie关联:通过TGC作为key来查找对应的TGT
- 安全考虑:TGT包含敏感的用户信息,不能直接暴露在客户端
数据结构
// 浏览器Cookie中
TGC = "CASTGC-xxxx-yyyy-zzzz" // 仅仅是一个sessionId// CAS服务器端
TGT = {user: "zhang_san",roles: ["admin", "user"],permissions: [...],expireTime: 1640995200,services: ["app1", "app2"]
}// 关联关系
服务器缓存[TGC] → TGT对象
设计原理
为什么TGT不能存储在Cookie中?
- 安全性:TGT包含敏感的用户信息和权限数据
- 大小限制:Cookie有4KB大小限制,TGT数据可能超出限制
- 篡改风险:客户端存储容易被篡改,服务器端存储更安全
- 集中管理:服务器端存储便于统一管理和控制
ST (Service Ticket)
ST是用户访问特定服务时提供的服务票据,是CAS安全架构的核心组件。
核心作用
ST实际上是一个临时通行证,用于解决以下关键问题:
- 安全隔离:TGT只能在CAS服务器内部使用,不能直接传递给各个应用系统
- 服务授权:每个ST只对特定的服务有效,实现了服务级别的访问控制
- 防止重放攻击:一次性使用特性,防止票据被截获后重复使用
设计原理
为什么需要ST而不直接用TGT?
- 如果直接传递TGT给应用系统,TGT可能被恶意应用窃取
- TGT一旦泄露,攻击者可以访问用户的所有授权服务
- ST将TGT的权限范围限制在单个服务内
TGT(全局权限) → ST(单服务权限) → 应用系统验证↓ ↓不离开CAS服务器 可以安全传输
生命周期
- 生成时机:用户访问服务时发现没有有效的本地会话
- 获取流程:302重定向到CAS服务器获取ST
- 使用方式:携带ST重定向回目标服务
- 验证过程:应用系统将ST发送给CAS服务器验证
- 销毁时机:验证完成后立即销毁,确保一次性使用
实际应用场景
假设用户要访问邮箱系统:
- 用户已通过CAS登录(拥有TGT)
- 访问邮箱系统时,CAS为邮箱系统专门生成ST-mail
- 邮箱系统收到ST-mail后,向CAS验证
- CAS确认ST-mail有效且是为邮箱系统生成的
- 邮箱系统获得用户身份信息,建立本地会话
- ST-mail被销毁,无法再次使用
票据关系对比
特性 | TGT | ST |
---|---|---|
作用范围 | 全局,所有服务 | 单个服务 |
存储位置 | CAS服务器内部 | URL参数传输 |
生命周期 | 用户会话期间 | 一次性使用 |
安全等级 | 高,不离开CAS | 中,可传输但限制使用 |
用途 | 证明用户身份 | 授权访问特定服务 |
架构设计
整体架构
┌─────────────────────────────────────┐
│ CAS服务器 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 认证模块 │ │ 票据管理 │ │
│ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 会话管理 │ │ 安全策略 │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────┘│├─────────────────────────┐│ │
┌─────────────▼─────────────┐ ┌─────────▼─────────────┐
│ 应用系统A │ │ 应用系统B │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ CAS客户端 │ │ │ │ CAS客户端 │ │
│ └─────────────────┘ │ │ └─────────────────┘ │
└───────────────────────────┘ └───────────────────────┘
存储架构
CAS系统采用多层存储架构,包括浏览器Cookie存储和多个服务器端Session存储。
完整存储架构
浏览器Cookie存储 服务器端Session存储
─────────────────────────────────────────────────────────────────CAS域 (sso.company.com): CAS服务器端:
├── TGC="CASTGC-xxxx-yyyy" ├── 缓存[TGC] → TGT对象
└── 作用:全局登录状态标识 └── 包含:用户信息、权限、有效期应用A域 (mail.company.com): 应用A服务器端:
├── JSESSIONID="APP-A-session" ├── 缓存[JSESSIONID] → ApplicationSession
└── 作用:邮箱系统本地会话标识 └── 包含:用户信息、应用数据、权限映射应用B域 (oa.company.com): 应用B服务器端:
├── JSESSIONID="APP-B-session" ├── 缓存[JSESSIONID] → ApplicationSession
└── 作用:OA系统本地会话标识 └── 包含:用户信息、应用数据、权限映射
应用端会话管理
除了CAS域的TGC,每个应用系统也会在自己的域下维护独立的会话:
应用域Cookie的作用:
- 本质:应用系统自己的session cookie
- 域名:存储在应用系统的域下(如:mail.company.com)
- 内容:应用系统的本地会话标识
- 目的:维护用户在该应用中的登录状态
应用服务端存储内容:
// 应用服务端Session存储示例
ApplicationSession = {sessionId: "APP-A-session-id", // 对应cookie中的值userId: "zhang_san", // 从CAS获取的用户身份userInfo: { // 从CAS获取的用户信息name: "张三",email: "zhangsan@company.com",roles: ["admin", "user"]},appSpecificData: { // 应用特定的会话数据currentPage: "/inbox",preferences: {...},businessContext: {...}},permissions: ["read_mail", "send_mail"], // 应用内权限loginTime: 1640995200,lastAccessTime: 1640995800,expireTime: 1640999400
}
存储关系对比
组件 | Cookie存储 | 服务器端存储 | 存储内容 |
---|---|---|---|
CAS | TGC (sessionId) | TGT对象 | 用户身份、全局权限、有效期 |
应用A | JSESSIONID | ApplicationSession | 用户信息+应用特定数据 |
应用B | JSESSIONID | ApplicationSession | 用户信息+应用特定数据 |
安全机制
- 票据时效性:所有票据都有明确的有效期
- 一次性使用:ST只能使用一次,防止重放攻击
- HTTPS传输:所有敏感信息通过HTTPS传输
- 会话管理:完善的会话超时和清理机制
- 域隔离:不同域名的cookie自然隔离
- 权限分级:全局权限(TGT)和服务权限(ST)的分离
工作流程
完整登录时序
用户 → 应用系统(SP) → CAS服务器 → 应用系统(SP) → 用户│ │ │ │ ││ ①访问受保护资源 │ │ ││ │ │ │ ││ ②重定向到CAS │ │ ││ │ │ │ ││ ③请求登录页面────────→ │ ││ │ │ │ ││ ④返回登录表单←──────── │ ││ │ │ │ ││ ⑤提交登录信息────────→ │ ││ │ │ │ ││ ⑥验证并生成TGT │ │ ││ │ │ │ ││ ⑦返回ST并重定向──────→ │ ││ │ │ │ ││ ⑧验证ST请求─────────────────────────→ ││ │ │ │ ││ ⑨返回验证结果←────────────────────── ││ │ │ │ ││ ⑩建立会话并访问──────────────────────────────→
流程步骤详解
1. 用户访问应用系统
用户通过浏览器访问受保护的应用系统(Service Provider, SP)。
2. SP检测未登录状态
SP检测到用户未登录,重定向用户到CAS服务器进行身份验证。
3. 用户请求登录页面
用户的浏览器向CAS服务器发送请求,请求登录页面。
4. CAS提供登录表单
CAS服务器响应,返回包含登录表单的HTML页面给用户。
5. 用户提交登录信息
用户在表单中输入用户名和密码,提交表单信息回CAS服务器。
6. CAS验证用户凭据
CAS服务器验证用户的用户名和密码。验证成功后:
- 生成TGT(Ticket-Granting Ticket)
- 将TGT存储在服务器的票据存储中
- 将TGT对应的cookie(TGC)发送给用户浏览器
7. 重定向回SP并携带service ticket
用户浏览器被重定向回SP,URL中携带一个service ticket(ST)。ST是基于当前服务生成的,用于一次性使用。
8. SP验证service ticket
SP接收到ST后,向CAS服务器发送验证请求,包含ST以及SP自己的标识信息。
9. CAS验证service ticket有效性
CAS服务器验证ST的有效性(检查TGT和ST的一致性等),确认无误后,向SP返回验证结果,通常包含用户的身份信息或确认验证成功的标志。
10. SP建立用户会话
SP根据CAS的验证结果,为用户建立会话,允许用户访问受保护的资源。
11. 用户访问受保护资源
用户可以直接与SP交互,访问受保护的内容,直到会话结束或超时。
多应用访问流程
用户在完成首次登录后,访问其他应用的流程会更加简化:
-
首次访问应用A:
- 检查mail.company.com域下的cookie → 无会话
- 重定向到CAS进行认证
-
CAS认证成功:
- 在sso.company.com域下设置TGC
- 生成ST重定向回应用A
-
应用A验证ST:
- 验证ST成功后,在mail.company.com域下设置自己的session cookie
- 建立用户在应用A的本地会话
-
后续访问应用A:
- 直接检查mail.company.com域下的cookie
- 有有效会话则直接访问,无需再次认证
-
访问应用B:
- 检查oa.company.com域下的cookie → 无会话
- 重定向到CAS,CAS检查TGC → 已登录
- 直接生成ST重定向回应用B(无需重新输入密码)
技术实现
会话管理
应用会话的特点
重要澄清:应用服务器的Session不是CAS的缓存,而是包含两部分数据:
- 从CAS获取的用户信息(一次性获取)
- 应用自己产生的业务数据(持续更新)
数据获取方式
// ST验证时,CAS返回的用户信息(一次性)
CASValidationResponse = {userId: "zhang_san",attributes: {name: "张三",email: "zhangsan@company.com", roles: ["admin", "user"]}
}// 应用服务器基于此信息创建自己的Session
ApplicationSession = {// 从CAS获取的用户信息(不变)userId: "zhang_san",userInfo: {...},// 应用自己产生的数据(持续变化)currentPage: "/inbox", // 用户当前页面unreadCount: 5, // 未读消息数preferences: {...}, // 用户偏好设置businessContext: {...}, // 业务上下文lastOperation: "send_mail", // 最后操作// 应用自己的会话管理loginTime: 1640995200,lastAccessTime: 1640995800,expireTime: 1640999400
}
应用与CAS的交互模式
应用服务器 ←→ CAS服务器的交互:
1. ST验证时:获取用户信息(一次性)
2. 单点登出时:通知会话失效(事件驱动)
3. 正常运行时:相互独立,无实时交互应用服务器内部的Session管理:
1. 基于CAS用户信息创建会话
2. 独立管理应用业务数据
3. 独立控制会话超时和清理
数据同步问题
同步延迟现象
如果用户在CAS中修改了信息(角色、权限、邮箱等),应用服务器不会立即知道这些变化。
问题原因
时间线:
T1: 用户通过ST验证,应用获得用户信息快照
T2: 用户在CAS中修改了角色(admin → user)
T3: 应用服务器仍然认为用户是admin
T4: 直到用户重新登录或会话超时,应用才会获得新信息
实际影响
// 场景:用户权限被管理员撤销
CAS系统:
用户角色: admin → user (已修改)应用服务器Session(仍是旧信息):
ApplicationSession = {userId: "zhang_san",roles: ["admin"], // 旧信息!permissions: ["admin_all"] // 旧权限!
}// 结果:用户仍然可以执行admin操作
利弊分析
优点(设计考虑):
- 性能优化:避免每次请求都查询CAS
- 系统稳定:应用不依赖CAS的实时可用性
- 网络效率:减少CAS服务器压力
缺点(安全风险):
- 权限延迟:权限变更不能立即生效
- 信息过期:用户信息可能过期
- 安全隐患:被撤销权限的用户可能继续操作
最佳实践
1. 会话超时策略
// 设置较短的会话超时时间
ApplicationSession.expireTime = 30分钟; // 强制重新认证
2. 实时权限验证
// 关键操作时重新验证权限
if (criticalOperation) {// 向CAS或权限系统重新查询当前权限currentPermissions = permissionService.getCurrentPermissions(userId);
}
3. 消息推送机制
// CAS推送用户信息变更事件
@EventListener
public void onUserInfoChanged(UserInfoChangedEvent event) {// 更新或清除相关用户的SessionsessionManager.invalidateUserSessions(event.getUserId());
}
4. 定期同步机制
// 定期从CAS同步用户信息
@Scheduled(fixedRate = 300000) // 5分钟
public void syncUserInfoFromCAS() {// 批量同步活跃用户信息
}
5. 实施建议
- 敏感操作实时验证:重要操作前重新验证权限
- 合理的会话超时:平衡性能和安全性
- 监控和审计:记录权限变更和访问日志
- 分层权限设计:区分长期权限和临时权限
6. 票据管理
- TGT的生命周期管理
- ST的一次性使用控制
- 票据的安全存储和检索
7. 会话同步
- 多应用系统间的会话状态同步
- 单点登出的实现
- 会话超时处理
8. 安全考虑
- 防止会话劫持
- 票据伪造防护
- 传输加密保护
9. 性能优化
- 票据缓存策略
- 数据库连接池管理
- 负载均衡支持
参考资料
技术文档
- CAS官方文档
- CAS协议规范
实现案例
- CAS登录原理和实现
- CAS单点登录详解
- CAS架构设计与实现
相关技术
- Spring Security CAS集成
- SAML vs CAS比较
本文档详细介绍了CAS单点登录的完整架构设计和实现要点,涵盖了从基础概念到技术实现的全过程。通过理解CAS的票据机制、存储架构和工作流程,可以更好地设计和实现企业级的单点登录解决方案。