新浪微博如何實現 SSO 的分析

轉載地址:http://www.iteye.com/topic/1039052 


最近在使用sina微博時,經常性交替使用 weibo.com 和 t.sina.cm.cn進入我的微博。發現當我在 t.sina.com.cn中登錄之後,直接切換至weibo.com,這時候在 weibo.com是已經登錄的,當我在 weibo.com進行註銷之後,再切換至 t.sina.com.cn,這時候在 t.sina.com.cn也已經是註銷的狀態了。


     對於SSO的實現方案及其機制,早已經不是什麼新鮮的技術了,從微軟爲.net提供的passport機制到java中開源的JBoss SSO、Oracle OpenSSO及經典的 Yale CAS等等之類的開源或一些商業SSO中間件都不失爲作爲單點登錄實現的選擇。當然一些企業也會選擇自己實現一套適合自己輕量級方案,如採用SESSIONID轉遞或SESSION同步複製之類的。 可以看得出SSO的價值也是具大的,就拿sina來說吧,增加 weibo.com域名之後,對於用戶來說來說沒有任何影響,即使你在 t.sina.com.cn中進行登錄,可以無縫在兩域名之間隨意切換,對於它推廣weibo.com無非是大大的益處。

    由於近年來一直在使用 Yale的CAS作爲SSO的方案,覺得 SINA的SSO與Yale-CAS有很多異曲同工之妙,於是便對SINA的SSO進行分析,其中的細節處理還是很值的學習的。當然,由於分析看到的SINA SSO處理都只是一些表現或表面上的東西,再加上其大部分關鍵的sso js都已經被壓縮,及SERVER端的實現機制也只是靠自己的經驗及結合CAS的的一些原理進行猜測。其實本文應該叫 <CAS SSO與SINA SSO的實現對比分析>更比較貼切。
  
    好吧,進入正題。


  • Sina SSO之分析篇
    首先是進入 t.sina.com.cn,提交用戶名及密碼進行登錄,通過 Firebug可以看到它通過類似Aajx POST到了 http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.3.12),如下圖所示:
   

    不難看出,其 http://login.sina.com.cn/sso/login.php 就是類似是 CAS 中的 Server,對sina的所有應用系統提供的統一登錄入口。上面的參數中有一個service參數,瞭解 CAS的GG應該知道 cas 在登錄的時候除了username 和 password同樣也有一個 service 參數,其CAS該參數含義是子應用系統的服務名標識及登錄成功之後所跳轉的地址。當然,sina這裏使用了 "miniblog"作爲微博的服務名,估計他在sso-server端對 miniblog 與登錄成功之後的地址進行映射,如 miniblog=http://t.sina.com.cn/,這樣就避免了CAS-client中轉入service= decodeURIComponent('http://t.sina.com.cn')之類的做法了。

    這裏的登錄與CAS做法一致,將登錄驗證提交至統一的認證中心進行驗證處理,從而避免了跨子域和全域的問題。 驗證成功之後路轉的路徑就是service所向的地址,驗證失敗之後則返回至當前登錄頁。下面就SSO中的一些登錄方面的核心問題做一些分析,看看SINA和CAS分別是如何處理的:

      1.如何授權某個子系統允許其在sso-server進行登錄驗證呢,類似cas-server中的login-ticket;
        對於cas來說,在首次進入  /cas/login頁時, 會產生一個一次性的login-ticket,也就是說在提交登錄驗證前必須向服務器請求一個login-ticket,在登錄提交時,需要將用戶名及密碼以及login-ticket進行提交至 cas-server端,cas-server端確定login-ticket有效後纔會對用戶名及密碼進行認證。
        看看sina如何處理的吧,繼續看firebug:
               以上截圖是當我首次進行 t.sina.com.cn時,通過 ajax/jsonp的方式發起的一個請求,可以看到返回的callback函數中的 json 串中包含了 nonce:"SXK19N"的屬性,參數名的漢譯是“一次”或“一次性”的意思,估計這裏的 nonce就是login-ticket,爲再一次確實,我再試着提交登錄看看,看它是否將該參數POST過去:
      
       果然不出所料, nonce:"SXK19N"作爲參數提交過去了,證明所猜測的應該是正確的。

  2.比如驗證碼跨域跨服務器導致從session無法獲取的問題,我們曾經遇到過;
        貌似sina登錄沒有涉及到驗證碼之類的東西,當你多次登錄失敗之後,它採用的是“您的登錄過於頻繁,請稍後再試吧”,這種方案確實比驗證碼要好的多,而且還避免了上面的說的問題。

     3. 當我登錄失敗了,/sso/login.php 如何將登錄的錯誤信息返回給 t.sina.com.cn並讓它進行顯示呢,如果我登錄成功了/sso/login.php 通過什麼方式通知t.sina.com.cn呢,因爲它這裏使用的是ajax方式登錄?
       對於這方面,cas的處理是將錯誤信息以參數的方式返回給 client-login,如登錄失敗,重定向地址: http://cas-client.com?errocode=0,如果登錄成功,則直接 重定向至 service 中的url,並生成ST給客戶端,表示其已經在cas-server登錄成功了。
       看看sina如何處理的吧,隨便輸入一個用戶名密碼,提交登錄,繼續通過firebug看看它的處理過程:
  
        再看看t.sina.com.cn 中的html內容的變化:
       
       
    以上圖1中發生了兩次請求,第一次登錄驗證是訪問 sso認證中心,它所返回response是一個html內容,第二次請求的地址: http://t.sina.com.cn/ajaxlogin.php framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack&retcode=4038&amp;reason=%B5%C7%C2)
    再結合以上圖2信息,看到 html 中發生了變化,創建了一個 id=ssoLoginFrame 的iframe,於是便可以得出,sina 的登錄並非原生的ajax方式,而是通過創建iframe來模擬提交不刷新的登錄。也就是說,當用戶點擊登錄提交時,這時候它會通過js創建iframe,將登錄提效至該iframe中。
         既然已經知道它登錄是提交到iframe中,而非ajax方式,那麼對於以上截圖1中兩個請求爲什麼返回的都是HTML內容就很容易解釋了。再回到上面的問題,/sso/login是如何通知t.sina.com.cn登錄失敗了呢? 首先在以上第一個截圖中返回的 HTML包含了一段 javascript:
       
Javascript代碼  收藏代碼
  1. location.replace("http://t.sina.com.cn/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack&retcode=4038&reason=%B5%C7%C2%BC%B3%A%BC%B3%A2%CA%D4%B4%CE%CA%FD%B9%FD%D3%DA%C6%B5%B7%B1%A3%AC%C7%EB%C9%D4%BA%F3%D4%D9%B5%C7%C2%BC");  

         location.replace的意思與location.href類似,同樣都是改變當前的URL地址,具體區別及做法可以參考這裏這裏。需要注意的這裏所說的通過location.replace改變當前的URL其它並非改變t.sina.com.cn的地址,而是第二個截圖裏iframe中src的地址,因爲這段HTML是在iframe中輸出的。
       在  locaiton.replace 的地址中包含了一個 retcode 及 reason參數,估計這就是當前登錄的錯誤信息。在上面第一個截圖的第二個請求實際就是在iframe 中進行的 location.replace操作後的跳轉地址。關鍵看它輸出的html內容:
  
Html代碼  收藏代碼
  1. <html><head>  
  2. <script language='javascript'>  
  3.  parent.sinaSSOController.feedBackUrlCallBack({"result":false,"errno":"4038","reason":"\u767b\u5f55\u5c1d\u8bd5\u6b21\u6570\u8fc7\u4e8e\u9891\u7e41\uff0c\u8bf7\u7a0d\u540e\u518d\u767b\u5f55"});</script></head><body></body></html>null  

      這段js是在 iframe中執行的,所以可以通過 parent 進行訪問 t.sina.com.cn中的js,可以肯定 parent.sinaSSOController.feedBackUrlCallBack 就是告訴 t.sina.com.cn 當前已經登錄失敗了,並且將錯誤信息傳至該入該callback了。至此,已經完成了 /sso/login.php 對 t.sina.com.cn的信息傳送。 新浪果然是有一手呀,在CAS中AJAX登錄一直都是一個問題,而sina它巧妙的通過iframe+callback 進行實現了。
      接着,再看看它對於登錄成功之後如何通知 t.sina.com.cn的吧,先看看登錄成功之後 sina-sso-server 會做什麼,看firebug截圖:
      
       重點在於 set-Cookie: tgc=TGT-MTc4NTc0NzM0Mw==-1305003116-ja-D51B2EB107B79FC50D8CA424BFE08907;  哈哈,熟悉CAS的應該會很熟悉這個,沒想到SINA的TGT與CAS的TGT不但參數命名,居然連生成的規則也一模一樣,估計sina肯定是參考了 cas 的實現機制。關於TGT是什麼或其作用可以參考:CAS總結之Ticket篇。另外還有一個就是當登錄成功之後,sina-sso-server會將用戶登陸名等等放在sina.com.cn根域的cookie中。
       然後再看看登錄成功之後 sina-sso-server所返回的response內容:
      
       以下是從以上摘取JS部分:
      
Javascript代碼  收藏代碼
  1. <script>  
  2. try{sinaSSOController.setCrossDomainUrlList({"retcode":0,"arrURL":["http:\/\/weibo.com\/sso\/crosdom?action=login&savestate=1305607916"]});}catch(e){}try{sinaSSOController.crossDomainAction('login',function(){location.replace('http://t.sina.com.cn/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack&retcode=0');});}catch(e){}  
  3. </script>  

      首先再次聲明,以上firebug截圖中的請求處理,並非 AJAX,而是在 t.sina.com.cn中放了一個iframe,輸出的 reponse都會至iframe當中.    
      以上的js主要重點在於:
     
Javascript代碼  收藏代碼
  1. location.replace('http://t.sina.com.cn/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack&retcode=0')  

      還是通過設置當前iframe中src地址,再看看跳轉至http://t.sina.com.cn/ajaxlogin.php後的response內容吧:
     
      返回用戶信息(從cookie中獲取的),並且還是類似上面的做法,通過 parent.sinaSSOController.feedBackUrlCallBack回調t.sina.com.cn中的js,告訴它這個用戶已經登錄成功了。
      於是t.sina.com.cn便進行跳轉至 t.sina.com.cn/dengers 中,從而實現登錄。
     
      整體的處理流程如下:
     


      4. 當我在t.sina.com.cn中登錄後,切換至weibo.com,weibo.com我應該也是已經登錄的,如何做到呢?
       對於這個問題,CAS中的處理就是,當我進入 weibo.com的時候,馬上跳轉至  /cas/login,然後在login中判斷cookie是否存在TGT,如果存在,並確定其有效性後,則認爲你已經登錄,併爲你生成一個ST,將ST作爲ticket參數使其重定向至 weibo.com?ticket=TG-xxxx 並登錄。
      看看sina怎麼處理的吧,首先我直接在t.sina.com.cn登錄成功。然後再新建一個選項卡,輸入 weibo.com:
     
      可以看得出,當我進入 weibo.com之後,sina並沒有直接進入 weibo.com的主頁,而是馬上重定向至:  http://login.sina.com.cn/sso/login.php?url=http%3A%2F%2Fweibo.com%2F&_rand=1305008634.5127&gateway=1&service=miniblog&useticket=1&returntype=META  與cas的做法確實一致。 再看看該 login.php的Response 信息,主要是JS:
      
Js代碼  收藏代碼
  1. <script type="text/javascript" language="javascript">  
  2. location.replace("http://weibo.com/sso/login.php?url=http%3A%2F%2Fweibo.com%2F&ticket=ST-MTc4NTc0NzM0Mw==-1305008634-ja-694BA43623A3F72999AE7129A0572048&retcode=0");  
  3. </script>  

      看到這裏之後,不得不懷疑 SINA 的 SSO 是不是用的就是 CAS 啊!!不但連 TGT 參數名一樣,連ST規則及參數名也一模一樣,其處理機制也十分相似。
      到這裏之後就與 CAS 的處理一樣了,就不詳細寫了,可以參考 CAS相關文章。

──────────
PS:由於在分析過程中裏面的很多SSO關鍵JS都壓縮了,所以難免會存在誤差。 不過SINA的SSO很多細節方面確實處理的很好,作爲互聯網應用的話,如果單純的只是把 CAS DOWNLOAD 下來,然後直接配配就用的話很多方面的處理還是很不到位的。 有時間我把我們CAS參考 SINA 調整一下。



    到這裏,不得不說的一個事情就是,之前在分析淘寶cookie如何跨域獲取時,大家都說出了一個taobao的jsonp實際存在一定的安全隱患。後面那個淘寶的GG看到之後加入Refer的判斷。而現在,在分析的過程中發現新浪也有這樣的問題,可以嘗試一下,隨便在本地建立一個html,引入jquery,然後使用下面的JS,就可以獲取到sina中的登錄郵箱名等信息,前提是你需要先在sina中登錄:
 
Js代碼  收藏代碼
  1. $.ajax({url: 'http://t.sina.com.cn/ajaxlogin.php?framelogin=0&callback=?&retcode=0', dataType:'jsonp',  
  2.                 success:function(data){  
  3.                     alert(data.userinfo.userid);  
  4.                 }}); 
發佈了28 篇原創文章 · 獲贊 9 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章