SSO (單點登錄)實現方式
SSO (Single-Sign-On) 即單點登錄,在互聯網應用中是多個站點通過一次登錄即可訪問所有產品,如Google所有產品通過 http://accounts.google.com/,百度所有產品統一登錄地點是 http://passport.baidu.com/ 等,也有些產品是提供自己的登錄界面,然后到統一入口驗證。總之,就是要實現一次登錄,處處登錄。
如果所有產品都是在同一主域下,那么只要把登錄的標識信息存放到主域的 cookie 中,即可實現訪問任一產品的頁面時把登錄信息傳遞到服務器,然后服務器根據該信息判斷是否需要用戶再次登錄。雖然大多數公司的產品都是在同一個域名下,但 有些還是獨立域名的,這時就涉及到跨域問題,也是 SSO 的難點所在。
基本思路是在一個固定的入口登錄,成功后返回一個 token,將這個 token 附帶在跨域訪問的某個文件上,該文件拿到這個 token 和服務器上存放的值比較,并獲取對應的登錄用戶信息,然后設置登錄標識 cookie,以此完成 SSO 登錄。服務器上的 token 可以存在 memcache 等緩存中,或者通過 RPC 訪問。
通過查看現有網站,主要是百度和新浪的 SSO 實現方案(截至2013-7-31),來對 SSO 實現方式有一個詳細的了解。
百度的 SSO
百度的很多產品都是在 baidu.com 這個主域名下,納入到 SSO 的其他獨立域名目前只有 hao123.com(很討厭這個網站,一不小心就會在裝軟件時將它設為默認首頁,還好現在不怎么用 IE)。
點擊百度產品的登錄,一般會跳轉到 passport.baidu.com,在這個頁面完成登錄,也有些是浮層,但登錄實現方式一樣:
填寫完用戶名密碼后,點擊“登錄”按鈕。
js 創建一個 div 容器,在這個容器中主要創建兩個元素:form 表單和 iframe。
</li>
from 中包含很多
type="hidden"
的<input/>
標記,主要包含填寫內容和其他相關內容,如要登錄的產品標識和成功后的跳轉地址等。form 的提交目標窗口指向之后的 iframe。iframe 初始地址所一個 _blank.html 文件,會造成一次 http 請求。
這些內容都是拼成一個字符串,一次性寫入的。
</ul>
動態創建節點完成后,馬上提交數據,指向 iframe 中 passport.baidu.com/v2/api/?login。
提交返回的結果內容在 iframe 中,通過 js 中的 'location.replace()' 跳轉 iframe 到一個新的頁面,這個頁面是第2步中的表單項 'staticpage' 指定的,一般都叫 v3Jump.hmtl,不同產品只是路徑不同。
v3Jump.html 中的 js 的作用就是以形如
parent.someMethod({})
的方式調用 iframe 父級窗口的 js 方法。涉及到不同域名調用主窗口 js 方法,因此一般 v3Jump.html 都是放在和主窗口同一域名下。主窗口的 js 處理頁面表現,同時為了實現跨域,會調用 https://user.hao123.com/static/crossdomain.php?bdu=...&t=3434312,這個 php 會根據 bdu 值驗證登錄狀態,并設置 hao123.com 這個域名的 cookie 登錄標識。比較巧妙的是,百度把這個地址作為一個 Image 對象的 src 屬性,這樣能完成請求,還不會渲染到頁面上。
最后,主窗口的 js 跳轉頁面到指定的地址。
</ol>
同百度
同百度
提交數據到 iframe, 指向 login.sina.com.cn/sso/login.php,提交表單后,會立馬刪除 from 節點(百度只在登錄失敗再次提交時才替換這個 form 節點)
如果登錄失敗,iframe 中的內容為
location.replace()
跳轉到 weibo.com/ajaxlogin.php;如果登錄成功,則跳轉地址為 weibo.com/sso/login.php,該文件返回 302,跳轉到 ajaxlogin.php。ajaxlogin.php 負責用形如
parent.someMethod({})
方式調用父窗口 js,處理頁面。父窗口 js 刪除 iframe 節點
同百度
</ol>
同 weibo
同 weibo
同 weibo,但不會刪除 form 節點
這里有些不同,login.sina.com.cn 下,login.php 的內容用
location.replace()
指向 login.sina.com.cn/crossdomain2.php,由這個頁面調用parent.someMethod()
;而在新浪首頁,login.php 中先設置document.domain='sina.com.cn'
(login.php 與頁面域名不完全相同),然后再調用parent.someMethod()
。父窗口的 js 處理頁面表現,并用jsonp方式(放到script標記的src中)調用 weibo.com/sso/login.php 來完成跨域登錄。
結束
</ol>
iframe 中的 js 如果要訪問父窗口的方法,需要保證域名相同,或者主域相同并都設置domain屬性為主域地址,否則瀏覽器會報安全警告。
跨域只要發起跨域的http請求即可,可以放到 script 標記的src中,也可已放到圖片對象的 src 中,推薦后者,因為后者不用放到dom節點中。需要注意的是,這兩種方式都要帶一個每次都不同的參數,可以是時間戳,以防止瀏覽器的自動緩存。
</ul>
在 Google 產品站點擊“登錄”,會跳轉到 https://accounts.google.com/ServiceLogin 登錄;
表單 post 提交到 https://accounts.google.com/ServiceLoginAuth 驗證;
登錄成功則直接通過 302 轉到 https://accounts.google.com/CheckCookie,訪問跨域產品的頁面,一般是 accounts.hostname/accounts/SetSID,設置登錄cookie;
頁面跳轉回登錄來源頁。
</ol>
如果登錄來源是 *.google.com 或 油Tube.com,則直接通過302指向 accounts.油Tube.com/accounts/SetSID,之后該地址再轉向到來源地址。
如果登錄來源是 *.google.com.hk,則返回200,通過 jsonp 請求 accounts.google.com.hk/accounts/SetSID 和 accounts.油Tube.com/accounts/SetSID
</ul>
</blockquote>
新浪的 SSO
新浪的獨立域名好像只有一個 weibo.com,其他產品都是在 sina.com.cn 下。它的登錄方式與百度基本相同,這里挑選不同的幾點說明。
在 weibo.com 登錄
新浪其他 sina.com.cn 子域登錄
新浪的登錄 js 并不統一,明顯 weibo 的 js 處理的更精細。
weibo 登錄中的第 4 步,iframe 中跳轉方式實現對跨域的訪問,顯然有些局限。如果還有其他獨立域名需要訪問,這種跳轉將無法兼顧。
總結
上面兩個站點的實現方式基本相同,都是用 iframe 登錄,然后在 iframe 中跳轉來達到通過同一入口進行登錄。難點在于跨域,特別需要注意的是:
上面著重關注了訪問的跳轉順序,還有一個關鍵點是如何標識登錄狀態。上面的方案中,都會在請求跨域文件時,帶上登錄返回的一個唯一字符串,將它作為 一個 token,在后端驗證登錄,識別登錄的用戶。可以把這個 token 設置為很短的時間有效,如1分鐘,并在訪問一次后刪除,可以保證一定的安全性,但是如果在使用前截取,那么可以拿到任何一個地方登錄。因為上面兩個網站對 跨域文件的訪問都是寫在主窗口的js里,因此可以很容易設置斷點獲取之。
據說 Google 在這點上是安全的,可惜身邊沒法KX上網,留待以后吧。
補記
*2013-8-4
周末有空折騰了一下KX上網,找到個超級好東西:SmartHosts,油Tube/非死book/推ter... 都可以上了。
Google SSO 實現方式
Google 所有產品的登錄都是通過 https://accounts.google.com/ 進行處理,基本流程如下:
針對不同的來源,具體處理上在第3步有些差別:
可以看到,基本原理是一樣的,都是登錄后,訪問一下跨域的頁面,完成登錄信息的cookie設置。
過程中通過修改 accounts.油Tube.com 的 ip 指向,截取到了登錄后的 /accounts/SetSID 鏈接,直接在一個新的瀏覽器中訪問,是可以完成登錄的。因此,Google 的 token 也不能保證這種截取的安全性。
原文地址:http://calefy.org/2013/07/31/how-to-achieve-sso-login.html