关于CAS的认证原理,网上已有很多相关文章了,官方文档也有说明,这里就不再详述了。本文重在阐述使用Apereo Cas 5.1.3遇到的与TGC相关的问题,及问题的分析解决过程。
遇到的TGC相关的问题:
- 由于需要使用云环境,把cas部署在了华为云上,配置了负载均衡(未启用负载均衡的会话保持),结果登陆状态无法保持,经过查看cas的日志,发现第二次访问的时候,请求的源IP与第一次访问的源IP不一致,被CAS拒绝了。华为云上的负载均衡器是一个集群,对内有多个内网IP,导致出现该问题,启用ELB的会话保持,暂时避开了此问题。
- 为了实现CAS集群,实现CAS无状态化,需要把CAS的session、TGT存在外部缓存redis中。实现之后,发现浏览器端的Cookie有一个TGC的值,会话状态是靠TGC保持的(即使删除SessionID,只要TGC存在就能保持登录状态)。在redis中却没发现TGC的踪迹。TGC还存储在CAS程序中?如果是,那么会话状态还是在应用实例中保持的,没有实现将所有状态信息放到程序外部。
- 一些业务系统需要有自己的登陆页面,在自己的登陆页面中调用CAS的Restful接口进行登录,同时又希望登录后,再访问其他使用CAS单点登录的系统时,不用再次登录。使用CAS的Restful接口进行登录认证倒容易实现,但再访问其他使用CAS单点登录的系统时,不用再次登录却没法按照CAS官方资料实现。经过分析,主要是由于业务系统使用自己的登陆页面登陆后,没有在CAS的域下写入TGC的Cookie值。要解决这个问题就需要弄清楚TGC的生成及使用逻辑,不得不去了解TGC的来龙去脉了。
解决问题的过程:
通过堆栈信息,顺利找到了CAS页面登录,表单提交的Action : SendTicketGrantingTicketAction
,顺藤摸瓜,在doExecute
方法中找到了调用CookieRetrievingCookieGenerator
的addCookie
相关方法:
this.ticketGrantingTicketCookieGenerator.addCookie(request, response, ticketGrantingTicketId);
在addCookie方法中找到了构造cookie的相关方法:
final String theCookieValue = this.casCookieValueManager.buildCookieValue(cookieValue, request);
CookieValueManager
有两个实现类:DefaultCasCookieValueManager
和 NoOpCookieValueManager
。在Debug过程中,发现使用的是DefaultCasCookieValueManager
实现类,查看它的buildCookieValue
方法:
@Override
public String buildCookieValue(final String givenCookieValue, final HttpServletRequest request) {
final ClientInfo clientInfo = ClientInfoHolder.getClientInfo();
final StringBuilder builder = new StringBuilder(givenCookieValue)
.append(COOKIE_FIELD_SEPARATOR)
.append(clientInfo.getClientIpAddress());
final String userAgent = WebUtils.getHttpServletRequestUserAgent(request);
if (StringUtils.isBlank(userAgent)) {
throw new IllegalStateException("Request does not specify a user-agent");
}
builder.append(COOKIE_FIELD_SEPARATOR).append(userAgent);
final String res = builder.toString();
LOGGER.debug("Encoding cookie value [{}]", res);
return this.cipherExecutor.encode(res);
}
逻辑很简单,方法传入的givenCookieValue的值其实就是TGT的值,把TGT的值使用分隔符附加上客户端IP地址、客户端代理信息(浏览器信息),编码后就得到了TGC的值。
例如:
加密前的值:
TGT-1-VOscp5EQosRdeRJK1vIIHwFCkFOeoHIqTnggPCqJ67TdqbWLi3-yh-PC@0:0:0:0:0:0:0:1@Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0
加密后的值,即客户端浏览器中存储的TGC Cookie的值:
eyJhbGciOiJIUzUxMiJ9.WlhsS05tRllRV2xQYVVwRlVsVlphVXhEU21oaVIyTnBUMmxLYTJGWVNXbE1RMHBzWW0xTmFVOXBTa0pOVkVrMFVUQktSRXhWYUZSTmFsVXlTVzR3TGk1NVJubEpjblpHWVZOR1Z5MVhURjg1UkVweWMzaFJMblI2TUZSRVpVSktjVWwxU1RkWVdGbFdkVk5uZEhkM2NHTlhXbWQwZFc1NlVVSnBUbEJGVkRWaVpYUktPREJGUTBGTExVbDZTR2Q2WlVwMFF6WTRPVTgzWmt4RVQyTjVaVkZZZGpRdFowSnllR1kzYW14MGJUYzRNMVJ5WmpGcmFITmxXbGt3WkVaM0xURkVTMjR6WHpVdFlqWnFUVTFCTVVsUk9FZ3RValkxTlZWclQzZEtNWFI2V1U5U1pFcGtaVk4yZEZSaVkyRmhaMGRFVlVodFUyOXVaSEJIT0RWdlptTkZhek5sYlVsSmVrZHBaMkp4VkZZeE4yY3pWRlZxY2k1Rk0wODNiMHAxY0ZOV1dVRm1SMjFOZDBSRFpEWjM.pZlVEOYioDF9_DJjlmOSTvWJ--WJOJe3dFQpAJRHXwH35XYXPNgcsfQ6NQ-1xoBb_-whvsOdCx68yQNdSWTyYQ
那么TGC的值在服务端是存储在哪里的呢?答案是没有存储。因为浏览器再次发送请求时,会传输来Cookie的值(即从客户端请求中获取TGC),解密TGC的值就获取了对应的TGT信息。看一下该类中对应的获取cookie的方法obtainCookieValue
@Override
public String obtainCookieValue(final Cookie cookie, final HttpServletRequest request) {