news 2026/9/29 16:55:17

CAS统一身份认证实战:从票据原理到单点登录落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CAS统一身份认证实战:从票据原理到单点登录落地

公司里那几套系统各自为政的日子,我印象太深了。财务系统一套账号,OA一套账号,项目管理系统又一套账号,每个人桌面上贴着一排便利贴,上面全是不同系统的密码。IT部门每天收到最多的工单就是“密码又忘了”“账号被锁了”。后来我们决定上CAS(Central Authentication Service),也就是统一身份认证服务,把登录这件事彻底收口。当时所有人都觉得这就是个“登录页改造”,真正做完才发现,CAS不只是省了一次输入密码的事,它直接改变了整个系统群的边界和安全隐患。

这篇文章就围绕CAS的方案选型、原理拆解、实际接入过程和线上运维经验来写,给正准备搞统一登录、或者已经在集成CAS路上踩坑的同行一些参考。文章不挑具体框架版本,重点讲思路和关键动作,哪怕你用的是别的语言、别的中间件,这套逻辑也基本通用。

1. 老系统各自为政的日子,到底疼在哪里

1.1 用户端与运维端的双重折磨

先说痛点,不然你体会不到为什么非要搞CAS。

以前我们有三套Web系统,都是不同时期外包或者不同团队做的。A系统是Java写的,B系统是PHP写的,C系统甚至是某个老同事用ASP.NET搭的。每套系统的用户表都是独立的,A系统里有张三,B系统里也有张三,但密码各改各的。用户新入职的时候,IT要挨个在三套系统里建账号;离职的时候,又要挨个去禁用。漏掉一个账号,可能就成了安全隐患。

用户端的体验更糟。一个人要记三套密码,而且每套系统的密码策略还不一样,A系统要求8位含特殊字符,B系统要求6位纯数字,C系统干脆不允许改密码。密码忘了只能找IT重置,IT重置又要先确认这个人是不是本人,一来一回半小时没了。

运维端的麻烦更大。你想想,三套系统的登录日志是分开的,安全审计的时候根本没法回答“张三今天登录过哪些系统”这种问题。出了问题想追溯,得登录三台服务器分别查日志。而且每套系统对密码存储方式都不一样,有的用MD5,有的明文存,想想都后怕。

有人说,那不就上个LDAP或者直接统一认证吗?道理是这个道理,但老系统改造最忌讳“推倒重来”。你不可能让三套系统重新做一遍用户体系,也不一定有权限去改别人的代码。这时候CAS的价值就出来了:它不要求你改系统内部的用户模型,只需要在应用前面加一道“认证拦截”,把你原来那套登录逻辑替换成跳转到CAS认证中心。

1.2 为什么不能靠“复制用户表”来凑合

有人可能会想,既然各系统都有用户表,那我写个脚本,每天把主用户库的数据同步到各个系统里不就行了?账号一致了,密码让他们都换成一样的,不就等于统一登录了吗?

这个思路我劝你趁早放弃。第一,密码同步这件事,意味着你需要在多个系统里保存同一份密码数据,任何一个系统被拖库,所有系统都跟着遭殃。你的安全性不是由最强的系统决定的,而是由最弱的那套决定的。第二,同步是单向的还是有向的?改密码在哪边改?如果用户在A系统改了密码,B系统要不要跟着改?这里面的冲突处理、失败重试、延迟问题,足够你写半年。

CAS走的完全是另一条路:密码只存在认证中心这一处,各业务系统不再自己验证密码。用户登录业务系统时,业务系统发现没有登录凭证,就302跳转到CAS登录页;用户输入账号密码后,认证通过,CAS给浏览器发一个全局票据,浏览器拿着这个票据回业务系统,业务系统再用票据去CAS后台换用户信息。这套流程下来,业务系统本身根本不接触密码,自然也不用存储密码。

这才是“翻身”的关键所在——不是把老系统推翻,而是让老系统把“认证”这个包袱交出去,自己专心做业务。就跟一个团队里多了个靠谱的行政,大家不用再操心订会议室、订机票的事,各自干各自的活儿就行。

2. CAS到底靠什么解决“一次登录,全网通行”

2.1 一张票据背后的两次跳转

CAS的核心思想,用一句话说就是:认证逻辑集中化,业务系统只认票据不认人。

我第一次看CAS流程图的时候,觉得有点绕,后来拿现实里的场景类比就通了。你去一个园区办事,门口有个总服务台,你在总服务台刷身份证办一张临时通行证,然后拿着这张通行证去园区里各个楼。每栋楼的保安不会让你再刷一遍身份证,只认你手里的通行证。CAS就是那个总服务台,通行证就是CAS颁发的票据。

具体到一个业务请求,走的是这么几步。

第一步,你访问业务系统A的一个受保护页面,比如/user/info。业务系统的客户端过滤器发现你本次会话里没有登录用户信息,就返回一个302重定向,把浏览器带到CAS服务器的登录页,地址长这样:

https://cas.example.com/cas/login?service=https://app1.example.com/user/info

注意后面那个service参数,这个参数是回调地址,CAS登录成功后要带着票据跳回哪去。

第二步,你在CAS登录页输入账号密码,CAS校验通过后,在浏览器端写入一个TGC(Ticket-Granting Cookie),同时生成一个ST(Service Ticket),然后302跳回刚才的service地址,把ST附加在URL后面,类似:

https://app1.example.com/user/info?ticket=ST-20240513-xxxxx

第三步,业务系统A拿到ticket参数后,不能直接信它,因为任何人都可以伪造一个ticket塞在URL里。系统A的后端要带着这个ticket和它自己的信息,再次请求CAS服务器的一个校验接口,比如/cas/serviceValidate。CAS确认这个ticket是自己发出的、没过期、对应的service确实是系统A,就把用户信息以XML或JSON形式返回给系统A。

第四步,系统A收到用户信息后,把用户放进自己的Session里,然后跳回用户原本要访问的/user/info页面。这个时候,用户再点系统B的链接,系统B也执行同样的流程,但CAS发现你浏览器里已经有TGC了,就不会再让你输入账号密码,而是直接发一个新的ST给系统B,全程无感。

这四次交互里,最关键的就是那张TGC。它相当于CAS发给你的一张贴在浏览器上的“已办证”标签,CAS看到这个标签,就不再重复问你身份信息。而ST是短期的、一次性的,专门给某个业务系统用的入场券。

2.2 TGT、ST、Service URL:三个名词理清楚

我见过不少同事刚开始接CAS,代码没看明白,先被这几个英文缩写劝退了。其实这些词没那么玄,我一个个捋。

  • TGT(Ticket-Granting Ticket):这是你在CAS登录成功后,CAS服务器通过TGC Cookie关联的一份服务端票据。它代表“你已经通过认证了”,有效期通常跟着TGC走,默认可能是一天或更长。你在同一个浏览器里访问任何接入CAS的系统,TGT都会发挥作用,保证你不会被反复要求登录。
  • ST(Service Ticket):这是CAS临时签发给某个具体业务系统的票据,用来完成那一次“验票”动作。ST是一次性的,用完了就失效,而且有时效,一般默认几十秒到几分钟。想想演唱会门口的检票员,你递过去票,他撕个角,这张票就废了,不会有人拿同一张票从另一个门再进一次。
  • Service URL:也就是业务系统的回调地址。这个地址必须提前在CAS服务端登记,否则CAS会拒绝回调。原因很简单,CAS要防止别人拿着合法票据去钓鱼,比如攻击者伪造一个回调地址,诱导CAS把票据发过去,那票据就可能泄露。所以白名单必须配好。

理解了这三个概念,CAS的整个交互就通了。你回头看那些报错、死循环、登录不上的问题,大多都能归结到这三个东西没配对:要么ST过期了,要么Service URL没登记,要么TGC时间太短导致频繁重新登录。

3. 动手接入CAS:服务端与客户端的完整落地过程

3.1 服务端部署:最简配置跑起一个CAS Server

CAS本身有官方实现,最常用的是Apereo CAS,它是个基于Spring Boot的应用,所以部署方式跟普通Java应用一样,打war包扔进Tomcat,或者直接打成可执行jar跑起来都行。如果你只是想体验一把,我建议先用最简单的方式:

  1. 下载官方overlay工程,它可以让你在不修改核心源码的前提下覆盖配置。
  2. 修改application.yml,把服务端口、应用名称、认证方式配置到位。
  3. 选择认证源。默认可能支持静态账号,正式环境一般接JDBC、LDAP或者OAuth。初期测试,先用内存用户跑通流程。

以一个简单的内存用户配置为例,关键配置长这样:

cas: authn: accept: users: admin::123456

这是最傻的认证方式,永远只有admin和123456这一对账号能登录。但它的价值在于让你先把流程跑通,别一上来就接LDAP或者数据库,流程没通之前排查问题会非常痛苦。

服务端跑起来之后,访问https://cas.example.com/cas/login能看到登录页,就算成功了一半。

3.2 客户端接入:让Java应用认识CAS

客户端接入的方式取决于你用的技术栈。

  • Java/Spring Boot:官方有cas-client-core,同时Spring Security也支持CAS集成。最省事的办法是用Spring Security的CasAuthenticationFilter,配置一下CAS服务器地址、service地址、以及ticket校验地址就行。如果用的是Shiro或者自己写的拦截器,那就纯手写一个Filter,逻辑无非是:检查Session里有没有用户 -> 没有就跳CAS登录页 -> 拿到ticket调校验接口 -> 解析用户信息 -> 放入Session。

  • PHP/Node/Go:CAS只是一个协议,你完全可以自己用HTTP请求实现客户端。核心就两步:一是构造登录跳转链接,二是调用/serviceValidate接口并解析返回结果。CAS支持JSON格式返回,比解析XML省心很多。

我自己写过一个最小的Java过滤器,核心逻辑不到一百行,大概是:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; // 1. 如果session里已经登录,直接放行 if (request.getSession().getAttribute("user") != null) { chain.doFilter(req, res); return; } // 2. 如果没登录,看看URL上有没有ticket参数 String ticket = request.getParameter("ticket"); if (ticket == null) { // 没有ticket,跳转到CAS登录页 String loginUrl = "https://cas.example.com/cas/login?service=" + URLEncoder.encode(currentUrl, "UTF-8"); response.sendRedirect(loginUrl); return; } // 3. 有ticket,去CAS后端校验并换取用户信息 String xml = httpGet("https://cas.example.com/cas/serviceValidate" + "?service=" + currentUrl + "&ticket=" + ticket); String user = parseUserFromXml(xml); if (user != null) { request.getSession().setAttribute("user", user); // 去掉URL上的ticket,避免浏览器里残留票据 response.sendRedirect(currentUrlWithoutTicket); } else { response.getWriter().write("CAS验证失败"); } }

这段代码只是演示思路,生产环境里你要处理并发、超时、异常场景。但它说明一个道理:CAS客户端不复杂,复杂的是你想把每个边界情况都处理好。

3.3 前端登录流程对接:从点击到回调的完整链路

后端接完之后,前端不需要做什么特殊开发,依旧是普通的页面跳转。但有一个体验细节我提醒一下:业务系统跳转CAS登录页的时候,一定要带一个service参数,而且这个参数必须是“用户当前正在访问的完整URL”。如果不带,或者只带一个固定的首页地址,用户登录完之后就会被扔到首页,而不是他本来想看的那个页面,体验非常割裂。

更稳妥的做法是,在后端保存一个redirectUrl,跳CAS之前把当前地址存到Session里,登录成功后从Session里取出来再跳回去。CAS的service参数只负责让CAS知道该回哪,真正的精确回跳地址最好由业务系统自己保管。

另外还有一个前后端分离场景的坑。如果你前端是Vue/React,后端是纯API,不能用传统的302跳转做登录。因为前端页面和后端API可能不同域,浏览器拿到302跳转之后,CORS、Cookie归属都会乱套。这种情况下一般让前端直接拿着window.location.href跳去CAS登录页,登录成功后CAS再带着票据跳回前端页面,由前端把票据交给后端换取用户信息。整体思路不变,但要特别关注跨域下Cookie的传递。

4. 集成中最容易翻车的几个细节,我替你踩过了

4.1 回调地址没配白名单,登录成功却被拒之门外

我们第一次部署测试环境,配好了CAS服务端,也写好了客户端过滤器,点登录跳转到CAS登录页,输入账号密码,结果CAS报错,提示类似“未授权的服务”。我当时第一反应是客户端代码写错了,查了半天,最后发现是CAS服务端配置文件里的service白名单没加我们那个测试应用的回调地址。

Apereo CAS对service的校验是默认开启的,凡是没配在serviceRegistry里的地址,一律拒绝签发ST。这个设计是好事,防止票据到处乱飞。但对初次使用者来说,很容易忽略。解决办法很直接,在服务端注册这个service:

https://app1.example.com/**

配好之后重启CAS,问题自然消失。这里要特别提醒:生产环境不要配成**全局通配,否则白名单就形同虚设了。要精确到具体的域名或者上下文路径。

4.2 票据过期与二次重放引起的死循环

还有一个经典问题:用户点进去一个页面,被跳去CAS登录,CAS说“票据已使用过”,然后又跳回业务系统,业务系统拿票据去验,验了一个空结果,又给用户弹回CAS,CAS又生成一个新票据……就这样循环往复,浏览器一直在两个地址间跳来跳去。

原因一般有两个。

一个是ST被客户端验了两次。CAS的ST默认是一次性的,第一次验证成功后,服务端就把它标记为已消费。如果你在Filter里验了一次,后面的业务代码又验了一次,第二次必然失败。解决办法是把验证结果缓存到Session里,不要反复调CAS接口。

另一个是进程时钟不同步。CAS在验证ST时会检查时间戳,如果业务应用服务器和CAS服务器之间的系统时间偏差超过允许范围,即使票据刚生成也会被判为过期。这个坑很隐蔽,尤其是虚拟化环境下,宿主机休眠恢复后容易时间漂移。解决办法是给所有服务器配NTP自动校时,并且检查CAS默认的allowedClockSkew参数,必要时在合理范围内放宽一点。

4.3 用户名大小写、邮箱格式带来的身份漂移

业务系统原有的用户体系如果跟CAS返回的用户标识对不上,就会出现“登录成功但找不到人”的情况。比如CAS里用户名是zhangsan,业务系统用户表里存的是ZhangSan,两边一比对,匹配不上,系统直接给用户弹一个“无权限”或者“用户不存在”。

这个问题的本质是“身份标识没有对齐”。我建议在规划阶段就统一用户唯一标识的格式。要么全用小写字母,要么用邮箱做唯一键,但邮箱也要注意大小写——很多邮箱域名本身不区分大小写,但你如果在这边存了Zhang@Example.com,那边存了zhang@example.com,字符串比较照样不同。

最稳妥的做法是,在业务系统用户表里增加一个cas_uid字段,专门存CAS返回的正式用户标识。以后所有用户关联都以这个cas_uid为准,不再拿姓名、邮箱这类可能变的字段做匹配。这个改造不需要动老表结构,加个字段建个索引就行,工作量不大,收益很大。

4.4 登出只走了个过场,会话却还活着

单点登录(SSO)做了,用户美滋滋地一键登录所有系统,结果有一天同事问:“我点了退出,为什么再点别的系统还是直接进去了?”

这就是登出没做彻底。CAS的登出分为两层:一是CAS中心自己的会话销毁,二是所有接入业务系统的本地会话销毁。如果你只跳了CAS的登出接口,CAS中心的TGT废了,但业务系统A里的Session还热乎着,只要业务系统A没有再校验一次,那个Session就能继续用。

要解决这个问题,正规的做法是业务系统都注册一个logout回调地址给CAS。CAS收到用户登出请求后,先销毁中心会话,然后同时通知各个业务系统的logout端点,让它们各自销毁本地会话。但这里有个前提:业务系统必须实现了那个logout回调接口。很多老系统没有这个接口,那就只能依赖Session超时机制兜底,把各个系统的Session超时时间统一调短一些,降低残留风险。

我自己在实操中还有一个经验:登出操作最好放在CAS中心做,别在业务系统里各自做。用户点某个业务系统的“退出”,其实是跳转到CAS的/cas/logout,由CAS广播退掉所有接入方。这才能做到“退一处,全退”。

5. 从“能登录”到“用得爽”:生产环境的进阶改造

5.1 会话超时策略与踢人逻辑

流程跑通、能登录、能登出,这只是起步。真正进入生产环节,你要开始面对各种“纠结”问题,比如会话到底要保持多久。

CAS中心会话和业务系统的会话是两回事。CAS中心的TGT时间长一点没关系,用户一天之内打开多个系统都不需要重新输入密码。但业务系统自己的Session如果设置得太长,风险就大了,比如用户在公司电脑上登录了OA,下班忘了退出,第二天保洁阿姨打开那个浏览器还能看到所有页面。如果Session设置得太短,用户可能正在写一份长文档,写着写着会话过期,保存不上,那也是很崩溃的。

我们的做法是把中心会话设成8小时,业务系统Session设成30分钟无操作自动失效。这样既保证了短时间的跨系统无感访问,又限制了业务系统的暴露风险。如果你有更强的安全要求,可以再加“踢人”功能:管理员在后台禁用某个用户时,同时向CAS发送一个强制登出或改密码的指令,让该用户在所有系统的会话立刻失效。这个功能CAS本身支持,但需要额外配置,别等出了安全事故再想起来。

5.2 多环境共用CAS的注意事项

开发环境、测试环境、生产环境都接入同一个CAS,这看起来很方便,实际很容易乱。最大的问题是:你在开发环境登录的TGC,跟测试环境登录的TGC混在一起,相互覆盖或者串号。

为什么?因为CAS是通过Cookie来保存TGT的,浏览器同一个域名下的Cookie是共享的。如果你开发、测试、生产共用同一个CAS域名,浏览器里只会存一份TGT,你在测试环境登录一次,再去访问开发环境的应用,CAS发现你有TGC,直接让你通过了,但你开发环境里对应的本地用户可能根本不存在,或者权限完全不一样。

所以多环境隔离是必须的。最省心的方案是:环境与CAS的映射彻底分开。

  • 开发环境用本地Mock登录,或者单独的CAS实例。
  • 测试环境用测试域的CAS。
  • 生产环境用生产域的CAS,域名最好带上环境标识,比如cas-test.example.com和cas.example.com。
  • 如果实在只能用一个CAS实例,那至少保证各个环境的Service URL写清楚,并且业务系统本地Session的隔离做得严格一些,不要通过CAS的全局会话推断本地用户。

这个坑我印象太深了。当时开发环境跟测试环境共用一个CAS,前端同事反复跟我反馈“我明明换了账号,怎么还是显示老账号的信息”,排查了两天,最后崩溃地发现就是Cookie里的TGC没变,CAS一直用同一个TGT在发ST。

5.3 安全加固:HTTPS、票据有效期、审计日志

安全性这块,我再强调几点,都是生产环境必须做、但文档里可能不写完整的。

第一,生产环境CAS一定要走HTTPS。票据是直接放在URL或Cookie里的,如果走明文HTTP,中间人抓包就能直接拿到ST或TGC。尤其ST是放在URL上回跳的,非常容易被日志系统记录、被Referer泄露。HTTPS不是可选项,是硬性要求。

第二,票据有效期要按场景调。ST的有效期没必要太长,默认60秒足够,太长的话重放攻击的时间窗口太大。TGT有效期也别设置成“永不过期”,建议结合公司安全要求,控制在8到12小时。用户如果登录超过一天,重新输一次密码是合理的,不要为了体验牺牲安全。

第三,必须记录认证日志。CAS本身会记录登录日志,但你要确保这些日志被收集到统一日志中心,保留至少半年。审计时我们需要回答“谁在什么时间从哪个IP登录了哪个系统”,CAS登录日志配合业务系统的访问日志,就能补全这条链路。我建议在业务系统接收CAS返回用户信息时,也打一条日志,包含CAS用户ID、来源IP、目标URL、时间戳,方便后续追踪。

第四,定期轮换CAS服务器自身的密钥和加密配置。如果你用的是正式版Apereo CAS,默认的签名密钥、加密密钥在部署后务必改成自己的,不要用官方文档里的演示值。这个改起来不麻烦,但漏掉的概率特别高,很多人跑通之后就直接没动过那些配置。

6. 落地之后的几点真心话

项目上线三个月后再回头看,CAS带给我们的不只是“一次输入密码”的便利。最明显的变化是IT部不用天天重置密码了,安全审计终于能给出一个完整的认证链路图。原本各自为政的用户表虽然没有立刻合并,但因为所有认证请求都经过CAS,我们对“谁是谁”这件事终于有了统一的判断标准。

我自己在接CAS过程中最大的体会是:别把它当成一个“登录页插件”,它是一个架构组件。你前期规划时对身份标识的统一、对登出回调的考虑、对多环境隔离的重视程度,决定了后期线上好不好用。那些只想着“先跑通登录,其他的再说”的团队,后面大概率要返工。

如果你正在筹备类似的项目,我的建议是:先花半天时间把CAS官网的术语表和流程图看明白,再花一天把服务端跑起来,然后用一个最简单的Java Filter把最核心的过滤、跳转、验票、建会话流程手写一遍。这套流程走完,你会比直接套用框架里的高级功能更理解CAS到底在做什么。

最后分享一个小的排查技巧:CAS集成出问题时,别急着看代码,先用浏览器开发者工具把从最初请求到最终跳转的所有302链路记录下来,逐个看Location地址、ticket参数、Cookie变化。每一次跳转对应着协议里的哪一步,协议一对照,问题定位通常不会超过十分钟。这个方法帮我解决过十几个线上线下问题,希望你也能用上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 16:55:06

hindsight dify:用Dify工作流打造AI复盘助手,让后见之明变成决策资产

“hindsight”这个词挺有意思。英文原意是“后见之明”,翻译成大白话就是“事后看明白了”。心理学里甚至有个专门术语叫“后见偏差”,意思是事情发生之后,人总会产生一种“我早就知道会这样”的错觉。但现实很打脸:绝大多数人既没…

作者头像 李华
网站建设 2026/9/29 16:55:05

extract-xiso 工具深度解析:Xbox XISO 格式双向操作与底层校验

简介:这是一份面向游戏备份与光盘镜像处理技术爱好者的开源命令行工具资源,专为Xbox平台XISO格式的创建、修改与提取提供跨平台支持。开发者可借助该工具完成游戏目录打包成XISO镜像、从XISO中还原文件、查看内容列表及重写元数据等核心操作,…

作者头像 李华
网站建设 2026/9/29 16:50:28

双电阻采样在SVPWM中的扇区分析与采样窗口优化

1. 为什么双电阻采样在SVPWM控制里是个“烫手山芋”,又非用不可? 双电阻电流采样,听着简单——电机三相绕组里只装两个电流传感器,省掉一个硬件,成本降一截,PCB面积小一圈,故障点少一个。但真把…

作者头像 李华
网站建设 2026/9/29 16:49:16

Claude插件开发全解析:plugin.json与mcp.json协议实战

1. 项目概述:Claude Plugins 官方生态的真实面貌与落地逻辑“claude-plugins-official”这个标题乍看像一个 GitHub 仓库名,但背后其实是一整套尚未完全公开、却已在开发者社区悄然运转的插件机制。它不是某个具体软件包,而是 Anthropic 官方…

作者头像 李华
网站建设 2026/9/29 16:48:15

基于场景法的含风电低碳调度源荷不确定性建模与求解

1. 为什么源荷两侧不确定性必须放在一个模型里如果你正在做电力系统优化调度,尤其是含风电的低碳调度,那么“源荷两侧不确定性”这几个字一定会出现在开题报告或者项目需求里。这个题目看起来不大,但真正动手用Matlab实现一遍后你会发现&…

作者头像 李华
网站建设 2026/9/29 16:47:40

基于TMS320F280049C的SOGI-PLL锁相环实现:从原理到代码

做电网同步或者电机相位跟踪的工程师,大概率都经历过这种场面:用最简单的过零检测去做锁相,电网稍微有点谐波、电压跌落或者频率偏移,过零点就开始乱跳,相位出来全是毛刺,后面PWM计算跟着一起抖。后来换成同…

作者头像 李华