CAS+SpringMVC+Shiro单点登录集成实战与避坑指南

发布时间:2026/10/14 15:15:12

CAS+SpringMVC+Shiro单点登录集成实战与避坑指南 简介本资源是一套将CAS、SpringMVC与Shiro三者整合实现单点登录SSO的完整实践示例面向具备一定Java Web基础、希望深入理解SSO认证流程与安全框架集成的开发者。内容围绕CAS服务端与客户端配置、Shiro的CasRealm与CasFilter定制、SpringMVC拦截器处理票据校验以及登录登出、权限控制与异常处理等关键环节展开帮助读者打通认证票据在服务端与各应用间的流转逻辑。压缩包共316个文件约40.79MB以jar依赖包、jsp页面、java源码、xml配置与properties文件为主另含少量前端图片、js与css资源结构贴近真实项目。目前已有492人学习下载可作为搭建SSO环境、理解CAS与Shiro协作机制、排查票据验证问题的参考实例。1. CAS 结合 SpringMVC 与 Shiro 的单点登录为什么你的第一次集成就翻车很多团队第一次做单点登录都是从一个看似简单的需求开始的公司内部有好几个后台系统用户不想每个系统都注册一遍、登录一遍。于是有人翻出 CAS——Central Authentication Service中央认证服务一个老牌但极其稳定的 SSO 协议实现。然后问题来了CAS 服务端搭好了客户端怎么跟 SpringMVC 和 Shiro 揉在一起我见过太多项目卡在这一步。CAS 官方文档给的是 Servlet Filter 的集成方式Shiro 也有自己的 Filter 链SpringMVC 还有自己的拦截器和上下文。三套 Filter 机制叠在一起谁先谁后、谁管认证、谁管授权、Session 怎么共享、Ticket 怎么校验任何一个环节没对齐结果就是无限重定向或者 401 死循环。这篇笔记就是拆这个问题的。我会从 CAS 协议的核心流程讲起然后一步步把 SpringMVC 的 Controller 层、Shiro 的 SecurityManager 和 CAS 的 Ticket 校验串起来中间会给出可直接抄的配置和代码最后把那些让我加班到凌晨的坑一个个列出来。适合正在做内部系统整合、需要落地 SSO 的后端开发也适合已经集成过但被重定向问题折磨过的同行。2. CAS 协议的核心流程与 Shiro 集成选型2.1 CAS 的 Ticket 机制到底在干什么CAS 的核心其实就两个东西TGT 和 ST。TGTTicket Granting Ticket是用户第一次在 CAS 服务端登录成功后服务端种在浏览器里的一个 Cookie 对应的凭证它代表“这个用户已经认证过了”。STService Ticket是客户端服务拿着 TGT 去换的一次性票据用来证明“这个用户确实有权限访问我”。整个流程走一遍用户访问系统 A系统 A 发现没有本地 Session于是重定向到 CAS 服务端带上自己的 service 参数。CAS 服务端检查有没有 TGT没有就让用户登录登录成功后生成 ST重定向回系统 A 的回调地址。系统 A 拿到 ST在后台向 CAS 服务端发起校验请求CAS 返回用户身份信息系统 A 建立本地 Session完成登录。这个流程里最关键的一点ST 是一次性的校验一次就失效。这意味着你不能在前端拿 ST 做任何缓存也不能在多个请求里复用同一个 ST。很多新手在这里翻车就是因为把 ST 当成了长期凭证。2.2 为什么选 Shiro 而不是 Spring SecuritySpring Security 确实有官方的 CAS 支持配置起来也算顺手。但如果你已经在一个用 Shiro 做权限控制的项目里再引入 Spring Security 就意味着一套应用里跑两套安全框架Filter 链会打架Session 管理也会冲突。Shiro 的优势在于它的 Filter 链是可插拔的你可以自定义一个 CasFilter 挂在 authc 的位置接管认证逻辑。Shiro 的 Subject 和 SecurityManager 模型也比 Spring Security 的上下文更直观至少对我来说调试的时候更容易看清“当前用户是谁、有什么权限”。选型建议新项目如果没有任何安全框架Spring Security CAS 是更省心的选择老项目已经用了 Shiro就老老实实写一个 CasFilter别折腾双框架共存。2.3 最小可跑的集成架构一个能跑通的架构至少包含三个部分CAS 服务端独立部署、客户端应用的 Shiro 配置、以及 SpringMVC 的 Controller 层回调处理。CAS 服务端我一般用官方推荐的 WAR 包部署数据库认证源接 MySQL这些不是本文重点。客户端这边Shiro 的配置需要做三件事定义 CasFilter、配置 SecurityManager 使用 CAS 的 Realm、在 SpringMVC 里暴露一个 /cas/callback 接口用来接收 ST。下面是一个最小的 Shiro 配置片段用 Java Config 方式写比 XML 更容易看清依赖关系Configuration public class ShiroCasConfig { Bean public CasFilter casFilter() { CasFilter filter new CasFilter(); // 认证失败时跳转到 CAS 服务端登录页 filter.setLoginUrl(https://cas.example.com/cas/login); // 认证成功后回到这个地址由 Controller 处理 ST filter.setSuccessUrl(/cas/callback); return filter; } Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean factory new ShiroFilterFactoryBean(); factory.setSecurityManager(securityManager); MapString, Filter filters new HashMap(); filters.put(cas, casFilter()); factory.setFilters(filters); MapString, String chain new LinkedHashMap(); // 回调地址放行否则 ST 校验请求会被拦截 chain.put(/cas/callback, anon); // 其余请求走 CAS 认证 chain.put(/**, cas); factory.setFilterChainDefinitionMap(chain); return factory; } }这段代码的逻辑说明CasFilter 继承自 Shiro 的 AuthenticatingFilter核心是重写 isAccessAllowed 和 onAccessDenied 方法。isAccessAllowed 判断当前 Subject 是否已认证未认证则触发 onAccessDenied在 onAccessDenied 里构造 CAS 登录 URL 并重定向。参数方面loginUrl 必须指向 CAS 服务端的 /login 路径successUrl 是本地回调地址这两个地址的域名和端口必须和 CAS 服务端配置的 service 白名单一致否则 CAS 会拒绝重定向。3. 从零搭建 CAS 客户端Shiro Realm 与 SpringMVC 回调3.1 自定义 CasRealm 的认证逻辑Shiro 的 Realm 是认证和授权的数据源。CAS 集成场景下Realm 的认证逻辑不再是查数据库比对密码而是校验 ST 并解析 CAS 返回的用户信息。public class CasRealm extends AuthorizingRealm { private String casServerUrl https://cas.example.com/cas; private String clientServiceUrl https://app.example.com/cas/callback; Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { CasToken casToken (CasToken) token; String ticket (String) casToken.getCredentials(); if (StringUtils.isBlank(ticket)) { throw new AuthenticationException(ST 为空无法认证); } // 构造 CAS 校验地址 String validateUrl casServerUrl /serviceValidate?ticket ticket service URLEncoder.encode(clientServiceUrl, UTF-8); // 发起 HTTP 请求校验 ST String response HttpUtils.get(validateUrl); // 解析 XML提取用户身份 String username CasXmlParser.parseUsername(response); if (username null) { throw new AuthenticationException(CAS 校验失败ST 无效); } // 返回认证信息principal 是用户名credentials 是 ST return new SimpleAuthenticationInfo(username, ticket, getName()); } Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info new SimpleAuthorizationInfo(); // 这里可以从本地数据库加载角色和权限 info.setRoles(roleService.findRolesByUsername(username)); info.setStringPermissions(permissionService.findPermissionsByUsername(username)); return info; } }逻辑说明doGetAuthenticationInfo 接收的 token 是 CasTokencredentials 里存的是 ST。校验地址的 service 参数必须和 CAS 服务端配置的 service 完全一致包括协议、域名、端口、路径差一个字符都会导致校验失败。CAS 返回的是 XML 格式需要解析出 cas:user 节点的值作为用户名。参数方面casServerUrl 和 clientServiceUrl 建议放在配置文件中不要硬编码因为测试环境和生产环境不一样。3.2 SpringMVC 回调接口怎么写才不丢 STCAS 校验成功后重定向回客户端URL 上会带一个 ticket 参数。SpringMVC 的 Controller 需要接收这个参数然后触发 Shiro 的登录流程。Controller public class CasCallbackController { RequestMapping(/cas/callback) public String callback(RequestParam(ticket) String ticket, HttpServletRequest request, HttpServletResponse response) { // 构造 CasToken交给 Shiro 认证 CasToken token new CasToken(ticket); Subject subject SecurityUtils.getSubject(); try { subject.login(token); } catch (AuthenticationException e) { // 认证失败重定向到 CAS 登录页重新走流程 return redirect:https://cas.example.com/cas/login?service URLEncoder.encode(https://app.example.com/cas/callback); } // 认证成功跳转到首页 return redirect:/index; } }逻辑说明这个接口必须配置为 anon否则 Shiro 的 CasFilter 会拦截它导致 ST 还没校验就被重定向走。subject.login(token) 会触发 CasRealm 的 doGetAuthenticationInfo完成 ST 校验。如果校验失败不要直接抛异常给用户而是重定向回 CAS 登录页让用户重新登录。参数方面ticket 参数名是 CAS 协议固定的不能改service 参数在重定向时必须和 CAS 服务端配置的一致。3.3 Shiro 的 Session 管理与 CAS 单点登出的配合单点登录做完了单点登出是下一个坑。CAS 的单点登出需要客户端暴露一个 /cas/logout 接口CAS 服务端在用户登出时会向所有注册的客户端发送登出通知。Controller public class CasLogoutController { RequestMapping(/cas/logout) public String logout(HttpServletRequest request) { Subject subject SecurityUtils.getSubject(); if (subject ! null) { subject.logout(); } // 清除本地 Session request.getSession().invalidate(); return redirect:https://cas.example.com/cas/logout; } }逻辑说明subject.logout() 会清除 Shiro 的认证信息request.getSession().invalidate() 清除 Servlet Session。然后重定向到 CAS 服务端的登出地址CAS 会清除 TGT并向其他客户端发送登出通知。参数方面这个接口也必须配置为 anon否则登出请求会被拦截。注意CAS 的单点登出依赖服务端能访问到客户端的登出地址如果客户端在内网需要确保网络可达。4. 避坑指南CAS 集成中最容易翻车的五个地方4.1 无限重定向ST 校验通过但 Session 没建立现象用户登录成功后浏览器一直在 CAS 服务端和客户端之间来回跳转最终报“重定向次数过多”。原因CasFilter 的 isAccessAllowed 判断的是 Subject.isAuthenticated()但 ST 校验成功后Shiro 的 Subject 并没有自动变成已认证状态。需要在 CasRealm 返回 AuthenticationInfo 后Shiro 才会更新 Subject 状态。如果 CasFilter 在回调接口之前执行就会一直认为用户未认证。解决确保 /cas/callback 配置为 anon让回调接口先执行 subject.login()完成认证后再放行其他请求。另外检查 CasFilter 的 successUrl 是否配置正确如果 successUrl 指向的地址也被 CasFilter 拦截就会形成死循环。4.2 ST 校验返回 400service 参数不匹配现象CAS 服务端返回 400 Bad Request提示“service not authorized”。原因客户端构造的 service 参数和 CAS 服务端配置的白名单不一致。常见的不一致包括http 和 https 混用、端口号省略或多余、路径末尾多了斜杠、URL 编码方式不同。解决在 CAS 服务端的 service 注册文件里把客户端的 service 地址完整写进去包括协议、域名、端口、路径。客户端构造 service 参数时用 URLEncoder.encode 编码确保特殊字符不会丢失。测试时可以在浏览器地址栏直接访问校验地址看 CAS 返回的具体错误信息。4.3 Shiro 的 Session 和 CAS 的 TGT 不同步现象用户在系统 A 登出后系统 B 仍然保持登录状态。原因CAS 的单点登出需要客户端主动清除本地 Session如果客户端没有暴露 /cas/logout 接口或者 CAS 服务端无法访问到该接口登出通知就发不出去。解决在每个客户端都实现 /cas/logout 接口并配置为 anon。CAS 服务端的登出通知地址需要和客户端的实际地址一致。如果客户端在内网确保 CAS 服务端能通过网络访问到。测试时可以先手动访问 /cas/logout确认本地 Session 被清除。4.4 SpringMVC 的拦截器和 Shiro Filter 顺序冲突现象自定义的 SpringMVC 拦截器在 Shiro 认证之前执行导致获取不到当前用户信息。原因Shiro 的 Filter 是在 Servlet 容器层面执行的而 SpringMVC 的拦截器是在 DispatcherServlet 内部执行的。Filter 的顺序由 web.xml 或 Java Config 中的 FilterRegistrationBean 决定如果 Shiro Filter 的 order 值比 SpringMVC 的 DispatcherServlet 大就会后执行。解决在 Spring Boot 项目中通过 FilterRegistrationBean 设置 Shiro Filter 的 order 为 1确保它在 DispatcherServlet 之前执行。如果是传统 web.xml 项目把 Shiro Filter 的 mapping 放在 DispatcherServlet 之前。4.5 CAS 服务端证书不被信任导致校验失败现象ST 校验时抛出 SSLHandshakeException提示“unable to find valid certification path”。原因CAS 服务端使用 HTTPS客户端 JVM 的 truststore 里没有导入 CAS 服务端的证书。解决把 CAS 服务端的证书导出用 keytool 导入到客户端 JVM 的 cacerts 文件中。命令如下# 导出 CAS 服务端证书 openssl s_client -connect cas.example.com:443 /dev/null | openssl x509 -out cas.crt # 导入到 JVM truststore keytool -import -alias cas -keystore $JAVA_HOME/jre/lib/security/cacerts -file cas.crt参数说明-alias 是证书别名-keystore 是 truststore 路径默认密码是 changeit。导入后重启客户端应用生效。如果不想改 JVM 全局 truststore可以在 CAS 校验的 HTTP 客户端里单独配置 SSLSocketFactory。5. 进阶用 CAS 代理模式做跨系统权限传递CAS 除了基本的 SSO还支持代理模式Proxy Mode允许一个客户端服务代表用户去访问另一个客户端服务。这个模式在微服务架构里特别有用网关服务认证用户后拿着 PTProxy Ticket去调用下游服务下游服务校验 PT 后信任网关传递的用户身份。代理模式的核心是 PGT 和 PT。PGT 是客户端服务在用户认证成功后向 CAS 服务端申请的一个凭证用来生成 PT。PT 和 ST 类似也是一次性的但 PT 可以指定目标服务。下面是一个申请 PGT 和生成 PT 的代码片段// 在 CasRealm 认证成功后申请 PGT String pgtUrl https://app.example.com/cas/proxyCallback; String pgtRequestUrl casServerUrl /proxy?pgt pgt targetService URLEncoder.encode(pgtUrl, UTF-8); // CAS 会回调 pgtUrl带上 PGTIOU客户端需要保存 PGTIOU 和 PGT 的映射逻辑说明PGT 的申请需要在 ST 校验成功后立即进行CAS 会通过回调 pgtUrl 的方式把 PGT 和 PGTIOU 传给客户端。客户端需要把这两个值存起来后续生成 PT 时使用。参数方面pgtUrl 必须是 HTTPSCAS 服务端会校验这个地址的证书。代理模式的坑在于PGT 的有效期和 TGT 绑定TGT 过期后 PGT 也失效。另外PT 的生成需要指定 targetService这个地址也必须在 CAS 服务端的白名单里。如果下游服务很多白名单维护会变成一个体力活。我一般会在网关层做统一的 PT 管理下游服务只负责校验 PT不直接和 CAS 服务端交互。这样可以把 CAS 的复杂性收敛到一个点上出问题的时候排查范围也小。最后一个习惯每次改完 CAS 相关配置先用 curl 手动走一遍完整流程确认 ST 校验、PT 生成、登出通知都正常再交给前端联调。这个习惯帮我省了至少三次通宵排查的时间。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/14 15:15:12

Tomcat与国产中间件技术选型对比与实践

1. 中间件技术选型的时代背景在数字化转型浪潮中,Web中间件作为企业级应用的基石,其技术选型直接影响着系统稳定性、开发效率和运维成本。过去十年间,以Tomcat为代表的开源中间件与国产中间件形成了明显的技术分野。我在实际项目中发现&#…

2026/10/14 15:15:12

基于Transformer的聊天机器人Python源码实战:环境搭建、训练与推理

简介:这是一份基于Transformer模型构建的聊天机器人Python源码包,面向具备一定Python基础、希望动手实践对话系统或完成课程设计的开发者。使用者输入文本后,系统即可根据语义生成相应回答,实现日常聊天功能。资源包共367个文件&a…

2026/10/14 15:15:12

Tomcat与国产中间件技术选型及性能对比分析

1. 中间件技术选型的核心考量在构建企业级应用系统时,Web中间件的选择往往直接影响系统的稳定性、性能表现和长期维护成本。作为从业十五年的架构师,我见证过从传统商业中间件到开源解决方案的技术演进,也参与过多次国产化替代的技术评估。To…

2026/10/14 16:35:55

STM32F103C8T6参考例程全指南:从跑通到避坑再到固件模板

简介:嵌入式开发中,参考例程是工程师快速上手芯片外设的重要资源。以STM32F103C8T6为例,其HAL库风格代码覆盖时钟树、GPIO、串口、定时器与ADC等关键模块,理解启动文件和时钟配置顺序是确保程序正确运行的前提。掌握参考例程的工程…

2026/10/14 16:35:55

Python模拟登陆学信网查询四六级成绩,告别准考证号丢失

简介:这份Android示例项目实现了模拟登录学信网并查询四六级成绩,适合正在学习网络编程与数据解析的Android开发者。项目完整展示了从构造登录请求、携带Cookie维持会话,到解析返回HTML并提取成绩信息的全过程,涉及HttpURLConnect…

2026/10/14 16:30:48

基于jQuery UI的虚拟键盘实现:布局、焦点与避坑指南

简介:VirtualKeyboard是一款基于jQuery UI的虚拟键盘插件,面向需要优化网页表单输入体验的Web开发者。它模拟真实键盘提供字母、数字、符号等布局,支持jQuery UI多主题切换,并针对触摸屏操作做了专门优化;在密码输入等…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑