Cas 4.2.7 OAuth+Rest 實現SSO

關於Cas的認證原理、Rest的使用請參考前面的文章。本文重點闡述使用Rest接口登錄系統和其餘單點登陸系統打通遇到的問題,及解決問題的思路和過程。
 
 一: 遇到的問題
        使用Rest接口實現登錄後,再訪問其餘使用Cas單點登錄的系統時,Cas認定爲當前用戶未登錄並要求登錄。通過分析發現,當訪問其餘使用Cas單點登陸系統時Cas沒法獲取到當前客戶端Cookie中的TGC,也就是說使用Rest接口實現登錄Cas沒法往客戶端Cookie寫入TGC。有關TGC的解析這裏不詳述。
     問題適用場景
  1. 部分系統使用受權免登的方式進入到CAS單點登陸系統,同時又但願能使用SSO切換到其餘系統。
  2. 部分業務系統有本身的登錄頁面,在本身的登錄邏輯中調用Cas的Rest接口實現登錄,同時但願能使用SSO切換到其餘系統
 
二: 解決問題過程
  1. 分析Rest接口發現,調用 cas/v1/tickets/ 接口登錄成功後cas返回了用戶的身份信息TGT,那麼TGT和TGC有什麼關聯呢?
  2. 繼續分析cas登錄流程,Cas使用spring-webflow控制登錄流程,找到WEB-INF/webflow/login/login-webflow.xml。在flow執行以前Cas先作了些處理,以下配置
    <on-start>
          <evaluate expression="initialFlowSetupAction"/>
    </on-start> 
    追蹤到InitialFlowSetupAction類的doExecute方法發現核心代碼:
    WebUtils.putTicketGrantingTicketInScopes(context,this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
    代碼說明:當用戶訪問Cas登錄頁時,程序調用this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)方法獲取TGT並存放入當前做用域中。
 
         繼續追蹤到 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獲取到的。html

 
   再追蹤到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的安全策略)。web

  1. 回到問題的根源,當業務系統調用Cas的Rest接口實現登錄,再切換到其餘單點登陸系統時,先請求Cas的登錄受權頁面,當程序調用this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)獲取TGT時,此時客戶端的TGC爲空天然沒法解析出TGT。       
  1. 思考:既然調用Rest接口實現登錄有返回TGT信息,是否能夠再請求一遍Cas的登錄受權頁面並傳入TGT信息,而後修改源碼將TGT加密爲TGC並加入到客戶端的cookie中,此時Cas認定爲當前用戶爲已登錄並重定向回service地址。
  1. 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 
相關文章
相關標籤/搜索