關於Cas的認證原理、Rest的使用請參考前面的文章。本文重點闡述使用Rest接口登陸系統和其他單點登錄系統打通遇到的問題,及解決問題的思路和過程。
一: 遇到的問題
使用Rest接口實現登陸后,再訪問其他使用Cas單點登陸的系統時,Cas認定為當前用戶未登陸並要求登陸。經過分析發現,當訪問其他使用Cas單點登錄系統時Cas無法獲取到當前客戶端Cookie中的TGC,也就是說使用Rest接口實現登陸Cas無法往客戶端Cookie寫入TGC。有關TGC的解析這里不詳述。
問題適用場景
- 部分系統使用授權免登的方式進入到CAS單點登錄系統,同時又希望能使用SSO切換到其他系統。
- 部分業務系統有自己的登陸頁面,在自己的登陸邏輯中調用Cas的Rest接口實現登陸,同時希望能使用SSO切換到其他系統
二: 解決問題過程
- 分析Rest接口發現,調用 cas/v1/tickets/ 接口登陸成功后cas返回了用戶的身份信息TGT,那么TGT和TGC有什么關聯呢?
- 繼續分析cas登陸流程,Cas使用spring-webflow控制登陸流程,找到WEB-INF/webflow/login/login-webflow.xml。在flow執行之前Cas先做了些處理,如下配置
<on-start> <evaluate expression="initialFlowSetupAction"/> </on-start>
WebUtils.putTicketGrantingTicketInScopes(context,this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
繼續追蹤到
CookieRetrievingCookieGenerator類的retrieveCookieValue方法中:
try { final Cookie cookie = org.springframework.web.util.WebUtils.getCookie( request, getCookieName()); return cookie == null ? null : this.casCookieValueManager.obtainCookieValue(cookie, request); } catch (final Exception e) { logger.debug(e.getMessage(), e); } return null;
代碼說明:邏輯比較簡單,直接從cookie中獲取TGC(getCookieName()方法有興趣可以追蹤下看看),如果TGC不為空,調用this.casCookieValueManager.obtainCookieValue(cookie, request)方法解析TGC得到TGT,問題逐漸明朗了,TGT是根據TGC獲取到的。
再追蹤到DefaultCasCookieValueManager(為什么選擇DefaultCasCookieValueManager實現類,有興趣可以追蹤看看)類的obtainCookieValue方法中:
final String cookieValue = this.cipherExecutor.decode(cookie.getValue()); LOGGER.debug("Decoded cookie value is [{}]", cookieValue); if (StringUtils.isBlank(cookieValue)) { LOGGER.debug("Retrieved decoded cookie value is blank. Failed to decode cookie [{}]", cookie.getName()); return null; } final String[] cookieParts = cookieValue.split(String.valueOf(COOKIE_FIELD_SEPARATOR)); if (cookieParts.length != COOKIE_FIELDS_LENGTH) { throw new IllegalStateException("Invalid cookie. Required fields are missing"); } final String value = cookieParts[0]; final String remoteAddr = cookieParts[1]; final String userAgent = cookieParts[2]; if (StringUtils.isBlank(value) || StringUtils.isBlank(remoteAddr) || StringUtils.isBlank(userAgent)) { throw new IllegalStateException("Invalid cookie. Required fields are empty"); } if (!remoteAddr.equals(request.getRemoteAddr())) { throw new IllegalStateException("Invalid cookie. Required remote address does not match " + request.getRemoteAddr()); } if (!userAgent.equals(request.getHeader("user-agent"))) { throw new IllegalStateException("Invalid cookie. Required user-agent does not match " + request.getHeader("user-agent")); } return value;
代碼說明:首先解密TGC后得到一個由@符號分隔的字符串,分隔后獲取到TGT、客戶端IP、客戶端代理信息。並將從TGC中解密的客戶端IP信息和客戶端代理信息與當前請求的客戶端IP信息和客戶端代理信息進行比較,若不相等就拋出異常(Cas的安全策略)。
- 回到問題的根源,當業務系統調用Cas的Rest接口實現登陸,再切換到其他單點登錄系統時,先請求Cas的登陸授權頁面,當程序調用this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)獲取TGT時,此時客戶端的TGC為空自然無法解析出TGT。
- 思考:既然調用Rest接口實現登陸有返回TGT信息,是否可以再請求一遍Cas的登陸授權頁面並傳入TGT信息,然后修改源碼將TGT加密為TGC並加入到客戶端的cookie中,此時Cas認定為當前用戶為已登陸並重定向回service地址。
- Cas的登陸授權頁面需要改造獲取傳入的TGT信息,還是需要到InitialFlowSetupAction類的doExecute方法進行處理
改造前:
WebUtils.putTicketGrantingTicketInScopes(context, this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
改造后:
String cookie = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request); //增加從request中獲取tgt if (cookie == null && request.getRequestURI().contains("/login")) { String tgt = request.getParameter("tgt"); if (StringUtils.isNotBlank(tgt)) { HttpServletResponse response = WebUtils.getHttpServletResponse(context); //TGT生成為TGC並添加客戶端cookie中 this.ticketGrantingTicketCookieGenerator.addCookie(request, response, tgt); //tgt直接付值給cookie cookie = tgt; } } WebUtils.putTicketGrantingTicketInScopes(context,cookie);
將login-flow.xml中on-start配置修改為自定義的initialFlowSetupExAction
<on-start> <evaluate expression="initialFlowSetupExAction"/> </on-start>
關於 this.ticketGrantingTicketCookieGenerator.addCookie(request, response, tgt);方法有興趣的朋友可以追蹤看看。
客戶端改造:調用Cas的Rest接口登陸成功后,解析出返回的TGT,並重定向到Cas的登陸頁面加上service和tgt參數即可
例:
望大家不吝指教,有任何錯誤、疑問多交流。
版權聲明:本文為博主原創文章,轉載需注明出處。
http://www.cnblogs.com/bryanx/p/8588270.html