环境
Apollo Portal 2.5.0(自定义 CAS SSO 分支)
Spring Boot 2.7.x,Spring Session JDBC
现象
用户通过 CAS SSO 完成认证、CAS 服务端回调 /login/cas?ticket=ST-xxx 后,浏览器报 ERR_TOO_MANY_REDIRECTS,无法进入 Portal 主页。
根因
Session 序列化路径断裂,CAS 断言在每次请求时丢失。
application.yml 配置 spring.session.store-type: jdbc,Spring Session 通过 SpringSessionConfig.springSessionConversionService 将所有 session 属性序列化为 JSON 存入数据库。
SpringSessionConfig.objectMapper() 仅注册了 SecurityJackson2Modules(覆盖 Spring Security 类型),但 未注册 Jasig CAS Client 的 Jackson mixin。
Jasig 的 Cas30ProxyReceivingTicketValidationFilter 在验票成功后将 AssertionImpl / AttributePrincipalImpl 存入 session(key: const_cas_assertion)。这些对象序列化时不携带 @Class 顶层类型信息。
下次请求 Spring Session 从 JDBC 读回时,objectMapper.readValue(bytes, Object.class) 因缺少 @Class 无法还原原始类型,返回 LinkedHashMap 而非 AssertionImpl,等效于断言丢失。
CasAssertionSecurityContextFilter 读到 null(类型不匹配,强转后得 null)→ 不填充 SecurityContextHolder → Spring Security 认为请求未认证 → 跳转 CAS 登录 → CAS 刚发过 ticket 再次重定向 → 无限循环。
Jasig 存 AssertionImpl → JDBC flush → JSON 无 @Class
→ readValue(bytes, Object.class) 返回 LinkedHashMap
→ const_cas_assertion 强转失败 → null
→ SecurityContext 未填充 → 302 to CAS → 302 back → 302 to CAS → …
Assertion assertion = (Assertion) session.getAttribute(CAS_ASSERTION_KEY);
if (assertion == null) {
chain.doFilter(request, response);
return;
}
// 读完即移除,防止 Jasig 对象进入 JDBC 序列化路径
// AssertionImpl 无 Jackson mixin,留在 session 中会导致下次请求反序列化失败
session.removeAttribute(CAS_ASSERTION_KEY);
后续请求的认证完全依赖 SecurityContextPersistenceFilter 从 JDBC session 恢复的 SecurityContext,与 auth profile 的账密登录路径完全一致,无需引入额外的 Jasig Jackson mixin。
环境
Apollo Portal 2.5.0(自定义 CAS SSO 分支)
Spring Boot 2.7.x,Spring Session JDBC
现象
用户通过 CAS SSO 完成认证、CAS 服务端回调 /login/cas?ticket=ST-xxx 后,浏览器报 ERR_TOO_MANY_REDIRECTS,无法进入 Portal 主页。
根因
Session 序列化路径断裂,CAS 断言在每次请求时丢失。
application.yml 配置 spring.session.store-type: jdbc,Spring Session 通过 SpringSessionConfig.springSessionConversionService 将所有 session 属性序列化为 JSON 存入数据库。
SpringSessionConfig.objectMapper() 仅注册了 SecurityJackson2Modules(覆盖 Spring Security 类型),但 未注册 Jasig CAS Client 的 Jackson mixin。
Jasig 的 Cas30ProxyReceivingTicketValidationFilter 在验票成功后将 AssertionImpl / AttributePrincipalImpl 存入 session(key: const_cas_assertion)。这些对象序列化时不携带 @Class 顶层类型信息。
下次请求 Spring Session 从 JDBC 读回时,objectMapper.readValue(bytes, Object.class) 因缺少 @Class 无法还原原始类型,返回 LinkedHashMap 而非 AssertionImpl,等效于断言丢失。
CasAssertionSecurityContextFilter 读到 null(类型不匹配,强转后得 null)→ 不填充 SecurityContextHolder → Spring Security 认为请求未认证 → 跳转 CAS 登录 → CAS 刚发过 ticket 再次重定向 → 无限循环。
Jasig 存 AssertionImpl → JDBC flush → JSON 无 @Class
→ readValue(bytes, Object.class) 返回 LinkedHashMap
→ const_cas_assertion 强转失败 → null
→ SecurityContext 未填充 → 302 to CAS → 302 back → 302 to CAS → …
Assertion assertion = (Assertion) session.getAttribute(CAS_ASSERTION_KEY);
if (assertion == null) {
chain.doFilter(request, response);
return;
}
// 读完即移除,防止 Jasig 对象进入 JDBC 序列化路径
// AssertionImpl 无 Jackson mixin,留在 session 中会导致下次请求反序列化失败
session.removeAttribute(CAS_ASSERTION_KEY);
后续请求的认证完全依赖 SecurityContextPersistenceFilter 从 JDBC session 恢复的 SecurityContext,与 auth profile 的账密登录路径完全一致,无需引入额外的 Jasig Jackson mixin。