☰
CAS单点登录服务器端源码解析:票据生命周期与认证中心落地实践
2026/10/15 14:05:50 网站建设 项目流程

简介:这份CAS单点登录服务器端源码面向Java开发者与架构学习者,聚焦企业级SSO身份认证场景,帮助理解CAS框架的认证流程与定制化开发思路。压缩包共781个文件,约34.42MB,以510个Java源码为核心,辅以94个XML配置、39个JSP页面、33个属性文件及16个JAR依赖,另有证书、样式与脚本等资源,完整覆盖服务端工程结构。源码按模块拆分,包含cas-server-core中的AuthenticationManager、TicketRegistry、ServiceValidateController等核心组件,以及generic、jdbc、webapp、springsecurity、oauth、saml等扩展支持,便于研究票据管理、认证策略与协议集成。已有605人学习下载,适合希望深入掌握CAS工作原理、搭建统一认证服务或进行二次开发的技术人员参考。

1. CAS单点登录服务器端源码:从票据生命周期看一套认证中心怎么落地

很多团队第一次接触 CAS 单点登录服务器端源码,都是被一个很具体的场景逼出来的:内部已经有五六个后台系统,每个系统一套账号密码,运维改一次密码要通知一圈人,用户登录不同系统要反复输密码,审计时又拿不出一份统一的登录记录。这时候 CAS 单点登录服务器端源码就成了绕不开的东西——它要解决的不是“怎么登录一个系统”,而是“怎么让一套认证中心替所有系统发通行证”。

CAS 的核心模型其实不复杂:客户端带着 service 参数跳到服务器端,服务器端认证成功后发一张票据,客户端拿票据回服务器端校验,校验通过才建立本地会话。真正让一线工程师头疼的,是服务器端源码里票据的生成、存储、过期、销毁这条生命周期链路,以及 TGT、ST、PGT 这些票据在不同场景下的取舍。这套源码适合谁?适合需要自建统一认证、又不想把认证逻辑散落到每个业务系统里的团队。下面按“先立住模型、再动手复现、最后看坑”的顺序拆开讲。

2. CAS服务器端源码的票据模型与认证流程拆解

2.1 从一次跳转看服务器端到底做了什么

用户在浏览器访问某个业务系统,业务系统发现没有本地会话,于是把浏览器重定向到 CAS 服务器端,地址上带一个 service 参数,标明“我是谁、认证完把我送回哪”。服务器端收到请求后,先判断当前浏览器有没有携带有效的 TGT 会话 Cookie。没有,就渲染登录页;有,就直接进入票据签发环节。

登录页提交账号密码后,服务器端做三件事:认证凭据、创建 TGT、签发 ST。认证凭据这一步,源码里通常抽象成一个认证处理器链,数据库认证、LDAP 认证、静态账号认证都挂在这条链上。TGT 是服务器端自己的会话凭证,代表“这个浏览器已经登录过认证中心”。ST 是给某个具体 service 用的一次性票据,业务系统拿它回服务器端校验,校验通过后 ST 立即失效。

这里有个容易被忽略的点:TGT 存在服务器端,浏览器只拿一个 Cookie 引用它;ST 是明文拼在重定向 URL 上的,所以它必须是一次性的、短时效的。源码里票据的默认过期时间、是否允许重复使用、是否绑定客户端 IP,都是在这个环节配置的。理解了这个流程,再看源码里的 TicketRegistry、AuthenticationHandler、ServiceRegistry 这几个接口,就不会觉得是一团乱麻。

2.2 TGT、ST、PGT 三种票据的职责边界

TGT 的全称是 Ticket Granting Ticket,可以理解成“认证中心的会话票”。它由服务器端生成并保存,浏览器通过 Cookie 持有它的引用。TGT 的有效期决定了用户多久需要重新登录认证中心。源码里 TGT 默认是存在内存里的,重启就丢,生产环境一般要换成持久化存储。

ST 的全称是 Service Ticket,是服务器端发给某个具体业务系统的“入场券”。它有三个硬约束:一次性、短时效、绑定 service。业务系统拿 ST 去服务器端校验,校验接口会检查这张票是否属于这个 service、是否已经用过、是否过期。任何一条不满足,校验就失败。

PGT 的全称是 Proxy Granting Ticket,用在代理认证场景。比如业务系统 A 需要代表用户去访问业务系统 B,A 先拿 PGTIOU 换一张 PGT,再用 PGT 去申请 PT 访问 B。这个场景在微服务网关、后端服务互调里比较常见。源码里 PGT 的签发需要业务系统暴露一个回调地址,服务器端把 PGTIOU 回传过去,业务系统再拿 PGTIOU 换 PGT。这条链路比普通 ST 校验长,配置也更容易出错。

票据类型持有方存储位置典型有效期是否一次性
TGT浏览器 Cookie 引用服务器端数小时到数天否
ST业务系统服务器端数十秒是
PGT业务系统服务器端数小时否
PT目标业务系统服务器端数十秒是

2.3 认证处理器链与凭据校验的扩展点

服务器端源码里,认证逻辑不是写死的,而是通过一组 AuthenticationHandler 串起来。每个处理器负责一种凭据类型,比如用户名密码、客户端证书、令牌。请求进来后,处理器链依次尝试,直到有一个返回成功或全部失败。

扩展认证方式时,常见做法是实现一个新的处理器,注册到配置文件里,并声明它支持的凭据类型。处理器内部要做的事包括:提取凭据、校验凭据、返回认证结果和属性。属性会挂到 TGT 上,后续业务系统校验 ST 时可以拿到这些属性做授权判断。

这里有个参数容易踩坑:处理器的顺序。如果通用处理器排在专用处理器前面,可能会把本该由专用处理器处理的请求拦截掉。源码里一般通过 order 字段控制顺序,数值越小越先执行。调整顺序时,最好在测试环境把每种登录方式都走一遍,确认没有互相覆盖。

3. 用最小配置在本地跑通CAS服务器端

3.1 环境准备与源码构建

本地复现一套 CAS 服务器端,最省事的方式是拿官方提供的覆盖模板,而不是从零写一个 Web 项目。覆盖模板里已经包含了服务器端的核心依赖、默认配置和打包插件。你需要准备 JDK 和构建工具,版本要和源码要求的保持一致,否则编译阶段就会报错。

下面是一个典型的构建命令,假设你已经把源码拉到本地,并进入了项目根目录:

# 清理并打包,跳过测试以加快首次构建 ./gradlew clean build -x test # 如果项目用的是 Maven,则执行 # mvn clean package -DskipTests

构建完成后,产物通常是一个 war 包,位于 build/libs 或 target 目录下。这个 war 包可以直接丢进 Servlet 容器运行,也可以用源码里自带的嵌入式容器启动。首次构建建议跳过测试,因为部分集成测试依赖外部服务,本地不一定具备条件。

参数说明:clean 清理旧产物,build 触发编译和打包,-x test 跳过测试任务。如果构建过程中报依赖下载失败,检查构建工具的镜像配置,不要急着改源码里的依赖版本。

3.2 关键配置文件与最小可运行参数

CAS 服务器端的配置集中在几个文件里:application.properties 或 cas.properties 管通用参数,services 目录下的 JSON 文件管业务系统注册。最小可运行配置需要关注下面几项:

# 服务器端对外暴露的地址,影响票据里的 service 校验 cas.server.name=https://localhost:8443 cas.server.prefix=${cas.server.name}/cas # 票据存储方式,默认是内存,生产环境要换 cas.ticket.registry.name=DefaultTicketRegistry # TGT 和 ST 的超时时间,单位秒 cas.ticket.tgt.timeout=7200 cas.ticket.st.timeout=30 # 认证方式,这里先用静态账号做本地验证 cas.authn.accept.users=admin::admin123

这几项里,cas.server.name 最容易配错。如果服务器端实际访问地址和这个值不一致,业务系统拿到的 ST 校验会失败,因为服务器端会校验 service 参数是否匹配。本地开发用 localhost 没问题,但要注意端口和协议要和实际访问一致。

静态账号认证只适合本地验证,它把用户名密码写在配置里,不连数据库。cas.authn.accept.users 的格式是“用户名::密码”,多个账号用逗号分隔。这个配置项在较新版本里可能被标记为不推荐,但本地跑通流程足够用。

3.3 注册一个业务系统并完成首次登录

业务系统要在 CAS 服务器端注册,才能被允许发起认证和校验票据。注册方式是在 services 目录下放一个 JSON 文件,内容大致如下:

{ "serviceId": "http://localhost:8081/.*", "name": "本地演示系统", "description": "用于本地验证 CAS 登录流程", "evaluationOrder": 10, "id": 1001, "logoutUrl": "http://localhost:8081/logout" }

serviceId 是正则表达式,匹配业务系统的访问地址。evaluationOrder 决定匹配优先级,数值越小越先匹配。id 是唯一标识,不要和已有服务重复。logoutUrl 用于单点登出时通知业务系统清理本地会话。

注册完成后,启动服务器端,浏览器访问业务系统的受保护页面,应该会被重定向到 CAS 登录页。输入静态账号登录,服务器端签发 ST,浏览器带着 ST 回到业务系统,业务系统校验通过后建立本地会话。整个链路跑通,说明服务器端的最小配置是正确的。

提示:本地验证时,浏览器地址、cas.server.name、serviceId 三者的协议和端口要保持一致,否则会出现票据校验失败但日志不明显的现象。

4. 票据存储与集群部署的选型与配置

4.1 内存存储为什么不能上生产

默认的 DefaultTicketRegistry 把票据存在 JVM 内存里。单机单实例时没问题,但一旦部署多个实例,用户第一次请求落在实例 A,TGT 存在 A 的内存里;第二次请求被负载均衡打到实例 B,B 找不到这张 TGT,就会把用户踢回登录页。用户看到的现象是“刚登录完又让我登录”,而且刷新几次可能又正常了,非常玄学。

内存存储的另一个问题是重启丢票。服务器端发版重启,所有在线用户的 TGT 全部失效,业务系统虽然本地会话还在,但需要续期或重新校验时会失败。对于内部系统,这可能只是让用户重新登录一次;对于对外服务,就是一次可感知的故障。

所以生产环境必须把票据存储换成共享存储。常见选择是 Redis、数据库、或者 CAS 自带的 Hazelcast 集群方案。选哪个取决于团队已有的基础设施和运维能力。

4.2 切换到 Redis 存储的配置步骤

Redis 是比较常见的选择,读写快、有过期机制、运维成熟。切换步骤如下:

第一步,引入 Redis 相关的依赖。如果用的是覆盖模板,在构建文件里加上对应的 starter 或模块。

第二步,修改票据注册表配置:

# 使用 Redis 作为票据存储 cas.ticket.registry.redis.host=127.0.0.1 cas.ticket.registry.redis.port=6379 cas.ticket.registry.redis.password= cas.ticket.registry.redis.database=0 # 票据在 Redis 里的过期策略,一般交给 Redis 的 TTL cas.ticket.registry.redis.timeout=5000

第三步,确认 Redis 的连接参数和实际环境一致。password 为空时不要留空格,否则会被当成空字符串密码去连接。database 建议单独分配一个库,避免和其他业务键冲突。

第四步,启动两个服务器端实例,用同一个 Redis,验证登录后多次刷新页面不会被踢回登录页。验证方法是观察两个实例的日志,看票据读写是否都落到了 Redis。

参数说明:host 和 port 是 Redis 地址,timeout 是连接超时毫秒数。如果 Redis 开启了保护模式或绑定了特定网卡,要确保服务器端实例能访问到。

4.3 集群下的会话粘性与票据一致性

即使票据存到了 Redis,集群部署仍然要注意会话粘性。CAS 服务器端本身在登录过程中会往浏览器写 Cookie,如果负载均衡把同一个浏览器的请求打到不同实例,Cookie 的路径、域名、加密密钥必须一致,否则会出现“登录成功但下一个请求又未认证”的情况。

常见做法是给负载均衡配置基于 Cookie 的会话保持,或者确保所有实例使用同一套加密密钥和相同的 cas.server.name。加密密钥用于签名和加密 Cookie,不一致时实例之间无法互相识别会话。

票据一致性方面,Redis 方案本身是共享的,但要注意时钟同步。如果两个实例的系统时间差太大,票据的过期判断会出现偏差。建议集群内所有节点开启时间同步服务。

存储方案适用规模优点注意事项
内存单机开发零依赖、启动快不能集群、重启丢票
Redis中小集群读写快、运维成熟注意网络延迟和键冲突
数据库对持久化要求高数据可靠、易审计读写压力大、需要索引优化
Hazelcast自带集群方案与 CAS 集成度高网络配置复杂、排错门槛高

5. CAS服务器端源码改造中的避坑与排查

5.1 票据校验失败但日志只报 invalid ticket

现象:业务系统拿 ST 回服务器端校验,返回失败,服务器端日志只有一句 invalid ticket,没有更多上下文。

原因:ST 是一次性票据,可能已经被校验过一次;或者 ST 绑定的 service 和业务系统实际访问地址不一致;或者服务器端和业务系统的时间差导致票据被判定过期。

解决:先确认业务系统是否重复使用了同一张 ST,比如页面刷新时重复提交。再检查 serviceId 的正则是否覆盖了实际访问地址,包括协议、端口、路径。最后检查两端时间是否同步。可以在服务器端开启票据校验的调试日志,观察具体是哪一项校验没通过。

5.2 单点登出后业务系统会话仍然有效

现象:用户在 CAS 服务器端登出,但业务系统里仍然处于登录状态,访问受保护页面不需要重新认证。

原因:单点登出需要服务器端主动通知各个业务系统清理本地会话。如果业务系统没有实现登出通知接口,或者注册信息里没有配置 logoutUrl,服务器端就不知道要通知谁。

解决:在业务系统注册的 JSON 里补上 logoutUrl,并确保业务系统暴露了对应的登出处理接口。服务器端登出时会向这些地址发送请求,业务系统收到后销毁本地会话。如果业务系统是前后端分离架构,还要注意登出通知到达后端后,前端的状态也要同步清理。

5.3 自定义认证处理器不生效

现象:按照源码结构写了一个新的 AuthenticationHandler,注册到配置里,但登录时仍然走默认认证,新处理器没有被调用。

原因:处理器注册的 order 值太大,排在默认处理器后面;或者处理器声明的 supports 方法没有正确匹配凭据类型;或者配置文件没有被加载。

解决:检查处理器的 order 值,确保它排在需要覆盖的处理器前面。检查 supports 方法的返回值,确认它对当前请求返回 true。检查配置文件路径是否在服务器端启动时被扫描到,必要时在启动日志里搜索处理器名称,确认它被注册。

5.4 票据序列化失败导致 Redis 存储异常

现象:切换到 Redis 存储后,登录时报序列化错误,或者票据写入 Redis 后读出来是乱码。

原因:票据对象里包含了不可序列化的字段,或者 Redis 的序列化器和票据对象的类型不匹配。

解决:确认票据类实现了序列化接口,并且所有字段都是可序列化的。检查 Redis 客户端的序列化配置,确保写入和读取使用一致的序列化方式。如果票据里挂了自定义属性,属性对象也要可序列化。升级源码版本时,注意票据类的字段变化,旧数据可能无法反序列化,必要时清空 Redis 里的票据键。

5.5 登录页样式和提示信息不生效

现象:修改了登录页的模板文件,重启后页面没有变化,或者提示信息仍然是默认文案。

原因:CAS 服务器端的页面渲染依赖模板引擎和消息资源文件。只改 HTML 不改消息文件,提示信息不会变;改错了主题目录,服务器端仍然加载默认主题。

解决:确认修改的模板文件位于当前激活的主题目录下。消息文案在 messages 资源文件里,按语言区分。修改后清理构建产物重新打包,避免旧文件被缓存。如果用了自定义主题,检查主题配置是否指向了正确的目录。

6. 从源码出发做二次开发:扩展点与验证习惯

服务器端源码的二次开发,最值得先摸清的是几个扩展点:认证处理器、票据注册表、服务注册表、登出处理器。这四个点覆盖了“谁能登录、票据存哪、哪些系统能接入、登出怎么通知”四条主线。把这四个接口的默认实现读一遍,再动手改,比直接改核心流程安全得多。

我一般会先写一个最小验证用例,再改源码。比如要加一种新的认证方式,先在测试环境用静态账号跑通登录,再把静态账号换成新处理器,确认票据签发和校验链路没有断。要换票据存储,先在单实例上验证读写,再上两个实例验证共享。每次只改一个变量,出问题时才能定位到具体环节。

验证票据生命周期时,有个习惯是打开服务器端的调试日志,观察 TGT 创建、ST 签发、ST 校验、票据销毁这几个时间点。日志里能看到票据 ID、绑定的 service、过期时间,对照浏览器里的 Cookie 和重定向地址,基本能还原整个链路。遇到玄学问题时,先看时间戳,再看 service 匹配,最后看存储读写,大部分问题都出在前两项。

二次开发还要注意版本升级的兼容性。票据类的字段、配置项的名称、默认行为在不同版本之间可能有变化。升级前先在测试环境跑一遍完整登录和登出流程,确认旧配置仍然可用。如果用了自定义的票据注册表或认证处理器,升级后要重新验证它们的接口签名是否变化。

最后说一个具体技巧:把服务器端的票据校验接口单独拿出来压测。用脚本模拟业务系统拿 ST 回服务器端校验,观察在不同并发下的响应时间和失败率。这个接口是认证链路的咽喉,它的稳定性直接决定所有接入系统的登录体验。压测时重点关注 Redis 或数据库的连接池配置,连接数不够时,票据校验会排队,表现为登录变慢甚至超时。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询