單點登陸(Single sign-on,SSO)是一種訪問控制,在多個軟件應用中,用戶只需登陸其中一個應用,就能夠成功訪問其餘應用;一樣,用戶只需註銷其中一個應用,就能夠成功註銷其餘應用。javascript
當一個公司產品線愈來愈複雜,作的東西愈來愈多,考慮到用戶的便利性和業務的交集,單點登陸也就變得愈來愈必然。譬如,阿里巴巴中的淘寶網,天貓,聚划算和一淘,考慮下面的場景:咱們用戶登陸淘寶網購物,緊接着朋友打電話說出去玩,因而打開聚划算的時候你會發現,你已經登了聚划算!可能這些細節都被大多數人忽略了(被誰給慣壞了),但若是要讓用戶再次手動登陸聚划算,用戶體驗可想而知。這種便利性就是單點登陸所帶來的。html
在單點登陸中,認證系統會爲每個應用分配一把鑰匙,也就是說有了這把鑰匙,帳號密碼的輸入就能夠免去了。這把鑰匙就藏在瀏覽器的 cookie 中。應用獲取鑰匙有兩種方法:前端
一,成功登陸應用A 後,認證系統爲應用A 分配一把鑰匙;同時,應用A 憑藉本身已經成功登陸,幫其餘應用代領鑰匙。下一次訪問應用B 的時候,應用B 就能成功免登陸了。java
二,這裏認證系統的域名是應用A 的子域名,即若是應用A 是 example.com,認證系統多是個 passport.example.com。當成功登陸應用A 後,認證系統爲應用A 分配一把鑰匙;下一次訪問應用B 的時候,web 頁面被重定向到認證系統,由於認證系統的域名是應用A 的子域名,因此應用A 的鑰匙,即 cookie 被帶上,從而用戶的訪問獲得了信任,認證系統爲應用B 分配鑰匙,頁面被重定向到應用B。jquery
接下來會對淘寶網和京東商城的網站進行單點登陸實例分析。nginx
來看看淘寶網作法。web
登陸了 taobao.com 後,下面是所產生的 cookie,也就是說認證系統已經爲應用taobao.com 分配了鑰匙,但這裏並無 etao.com 或者 tmall.com 的 cookie,認證系統還未爲他們分配鑰匙。ajax
下一步咱們嘗試去訪問 etao.com:json
etao.com 被重定向到了 www.etao.com;訪問 www.etao.com 被重定向到 http://jump.taobao.com/,下面是 response HTTP:跨域
HTTP/1.1 302 Moved Temporarily Server: Tengine Date: Sat, 29 Mar 2014 14:29:41 GMT Content-Type: text/html Transfer-Encoding: chunked Connection: keep-alive Location: http://jump.taobao.com/jump?target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329
瀏覽器會繼續訪問 http://jump.taobao.com/jump?target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329,下面是 request HTTP,這裏訪問帶上了應用taobao.com 的鑰匙,即 cookie,網站後臺會驗證應用taobao.com 的鑰匙:
GET /jump?target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329 HTTP/1.1 Host: jump.taobao.com Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Cookie: ...
認證成功,看來鑰匙是有效的,又產生一個重定向,下面是和上面對應的 response HTTP:
HTTP/1.1 302 Found Date: Sat, 29 Mar 2014 14:29:41 GMT Content-Type: text/html Content-Length: 260 Connection: close Set-Cookie: _tb_token_=AtWQpv7iedma;domain=.taobao.com;Path=/;HttpOnly P3P: CP='CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR' Location: http://pass.etao.com/add?uc3=nk2=Dkjr489jrew%3D;id2=Vyu3rko%2FyYdG;vt3=F8dHqR4J6Q2W7ouKdi8%3D;lg2=WqG3DMC9VAQiUQ%3D%3D&lgc=XXXXXX&tracknick=XXXXXX&_l_g_=Ug%3D%3D&cookie1=U7lSAZ5WionEzYGt3e34IvM%2BhHRTBL5Y%2BYTf7E22Ixo%3D&cookie2=007485c12ac6179b824c7656627e806c&cookie17=Vyu3rko%2FyYdG&t=4376b50842a95fbd0464fc1d58fe84c5&unb=479940688&_nk_=XXXXXX&uc1=cookie15=V32FPkk%2Fw0dUvg%3D%3D&_tb_token_=AtWQpv7iedma&target=http%3A%2F%2Fwww.etao.com%2F%3Ftbpm%3D20140329&pacc=_7LwGS9DwiA3O_Iq8iAaMQ==&opi=183.62.180.11&tmsc=1396103381332051
接下來瀏覽器訪問 http://pass.etao.com/add?.......,下面 response HTTP:
HTTP/1.1 302 Found Date: Sat, 29 Mar 2014 14:29:41 GMT Content-Type: text/html Content-Length: 260 Connection: close P3P: CP='CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR' Set-Cookie: uc3=nk2=Dkjr489jrew%3D&id2=Vyu3rko%2FyYdG&vt3=F8dHqR4J6Q2W7ouKdi8%3D&lg2=WqG3DMC9VAQiUQ%3D%3D;domain=.etao.com;Path=/ Set-Cookie: ...;domain=.etao.com;Path=/ Set-Cookie: ...;domain=.etao.com;Path=/ Set-Cookie: ...;domain=.etao.com;Path=/ Set-Cookie: ... Set-Cookie: ... Set-Cookie: ... ... Location: http://www.etao.com/?tbpm=20140329
「Set-Cookie」意味着應用etao.com 拿到了認證系統的鑰匙,耶斯!
來看看京東商城的作法
登陸 jd.com 後發現,它已經給全部的應用代領了鑰匙!這裏利用了前端裏面的 jsonp,對於跨域的問題,jsonp 得心應手。
成功登陸 jd.com,會跳轉到 jd.com,裏面有一小段 js 代碼發起了 jsonp 請求:
$.ajax({ url: ("https:" == document.location.protocol ? "https://" : "http://") + "passport." + pageConfig.FN_getDomain() + "/new/helloService.ashx?m=ls", dataType: "jsonp", scriptCharset: "gb2312", success: function(a) { a && a.info && $("#loginbar").html(a.info), a && a.sso && $.each(a.sso, function() { $.getJSON(this) }) } })
ajax get 到數據自動調用預設值的回調函數。jsonp 返回的數據是:
jsonp1396084410330({"sso":[ "http:\/\/sso.jd.com\/setCookie?t=sso.360buy.com&callback=?", "http:\/\/sso.jd.com\/setCookie?t=sso.wangyin.com&callback=?", "http:\/\/sso.jd.com\/setCookie?t=sso.360top.com&callback=?", "http:\/\/sso.jd.com\/setCookie?t=sso.minitiao.com&callback=?", "http:\/\/sso.jd.com\/setCookie?t=sso.jcloud.com&callback=?"], "info":"您好,買東西別坑爹!<a href=\"http:\/\/passport.jd.com\/uc\/login?ltype=logout\" class=\"link-logout\">[退出]<\/a>"})
回調函數對 a.sso 中每個鏈接執行 getJSON()。注:使用 jquery 的 getJSON() 進行跨域讀取數據,實際上 getJSON() 方式的根本原理和 ajax 使用 jsonp 的方式同樣。
以第一個參數爲例,所產生的 request HTTP:
GET /setCookie?t=sso.360buy.com&callback=jsonp1396084410335&_=1396084411190 HTTP/1.1 Host: sso.jd.com Connection: keep-alive Accept: */* User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36 Referer: http://www.jd.com/ Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Cookie: ...
結果產生一個重定向到 http://sso.360buy.com 的響應,與上面對應的 response HTTP:
HTTP/1.1 302 Moved Temporarily Server: nginx Date: Sat, 29 Mar 2014 09:13:26 GMT pragma: no-cache cache-control: max-age=86400 Location: http://sso.360buy.com/sign?c=0f5882246bae6fc065e387995680f4d73a03c448a244d27e328ae7abcd3c7fd7fa629605ac516c1903e014b45e906a7bc7bf5d7c237752f85709d12fd9cbe5a81c2602f684d352311de39612555d2fc2b4aa6fffca26d5a7e37e61e9a1b9e7e184cf8b335982c36b39944faf29cb1b61dd6b6a8461000c7661fc881a5daa89ddaaecf062d2e84b96e958534f5a407524596dcce400299487e87060d4ac55d454c36804deca3fd670adca3902d7e05d778fbc135c9a6e491b44ed5954337ac38f5b61e99934a0411b9af695ebbb7f75a4e69da4c24f1b3137ef6cc916af3c62238ee2b3c90f762d9b162239d634928f027585ab13c1e031861fb41a6a83ec2d1fec8b00389b91e5ab5c5db3961f60949c230d5280898d62c9f66f76058c35af78b1781d266ee548bd&callback=jsonp1396084410335&_=1396084411190&t=1396084406861&pin=XXXXXX&unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9 Content-Length: 0 Expires: Sun, 30 Mar 2014 09:13:26 GMT Connection: Keep-alive Keep-Alive: timeout=15, max=100
再次產生新的請求,請求目標爲 http://sso.360buy.com:
GET /sign?c=0f5882246bae6fc065e387995680f4d73a03c448a244d27e328ae7abcd3c7fd7fa629605ac516c1903e014b45e906a7bc7bf5d7c237752f85709d12fd9cbe5a81c2602f684d352311de39612555d2fc2b4aa6fffca26d5a7e37e61e9a1b9e7e184cf8b335982c36b39944faf29cb1b61dd6b6a8461000c7661fc881a5daa89ddaaecf062d2e84b96e958534f5a407524596dcce400299487e87060d4ac55d454c36804deca3fd670adca3902d7e05d778fbc135c9a6e491b44ed5954337ac38f5b61e99934a0411b9af695ebbb7f75a4e69da4c24f1b3137ef6cc916af3c62238ee2b3c90f762d9b162239d634928f027585ab13c1e031861fb41a6a83ec2d1fec8b00389b91e5ab5c5db3961f60949c230d5280898d62c9f66f76058c35af78b1781d266ee548bd&callback=jsonp1396084410335&_=1396084411190&t=1396084406861&pin=XXXXXX&unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9 HTTP/1.1 Host: sso.360buy.com Connection: keep-alive Accept: */* User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36 Referer: http://www.jd.com/ Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
response HTTP 中有 「Set-Cookie」說明已經拿到認證系統的鑰匙了:
HTTP/1.1 200 OK Server: nginx Date: Sat, 29 Mar 2014 09:13:27 GMT Content-Type: text/javascript;charset=UTF-8 pragma: no-cache cache-control: max-age=86400 P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR" SET-COOKIE: ceshi3.com=EA0B41EDE28DC567542D0191B48C74F35CD76AAEBADA867F4879AA3F02B4EA559D6E537ED0BEB4CCD417D4A434A7CC86C30CBA8775BCA96C36AB067034D0E057F9ED0297AFA566954A10E03DFF292089736A8B79554C32CB8FCFAA2F042F547DACCD30BAC81A9815083C9F1071B5B8382968A5A7E82EC0172E6AEE15930A6AA47C6875A1CDEE90118515ED0022909DAE;path=/;domain=.360buy.com;httponly Set-Cookie: pin=XXXXXX; Domain=.360buy.com; Path=/ Set-Cookie: unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9; Domain=.360buy.com; Path=/ Content-Length: 43 Expires: Sun, 30 Mar 2014 09:13:27 GMT Connection: Keep-alive Keep-Alive: timeout=15, max=100
其餘應用相似,這裏只舉例 360buy.com.接着我試圖訪問 http://360buy.com:
GET / HTTP/1.1 Host: 360buy.com Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 // 下面提交的 cookie,便是 360buy.com 的鑰匙 Cookie: ceshi3.com=EA0B41EDE28DC567542D0191B48C74F35CD76AAEBADA867F4879AA3F02B4EA559D6E537ED0BEB4CCD417D4A434A7CC86C30CBA8775BCA96C36AB067034D0E057F9ED0297AFA566954A10E03DFF292089736A8B79554C32CB8FCFAA2F042F547DACCD30BAC81A9815083C9F1071B5B8382968A5A7E82EC0172E6AEE15930A6AA47C6875A1CDEE90118515ED0022909DAE; pin=XXXXXX; unick=%E4%B9%B0%E4%B8%9C%E8%A5%BF%E5%88%AB%E5%9D%91%E7%88%B9 HTTP/1.1 200 OK Server: JDWS/1.0.0 Date: Sat, 29 Mar 2014 11:24:37 GMT Content-Type: text/html Last-Modified: Wed, 26 Mar 2014 11:54:10 GMT Transfer-Encoding: chunked Vary: Accept-Encoding Expires: Sat, 29 Mar 2014 11:24:37 GMT Cache-Control: max-age=0 Content-Encoding: gzip Connection: Keep-alive Keep-Alive: timeout=15, max=100
可見訪問 http://360buy.com 的時候,並無特意跑去認證系統索要鑰匙,只憑藉以前訪問 jd.com 時 getJSON() 留下的 cookie。
關於單點登陸的問題,還有待更深刻討論。我只是用一些抓包工具和網站的前端代碼來猜想單點登陸策略如何,後臺會是更復雜的技術,譬如分佈式存儲等。對於上面的兩個案例,若是你瞭解淘寶或者京東商城的單點登陸的具體方法,不吝賜教。
上面所說,單點登陸所帶來的用戶體驗可能被用戶忽略了。說到底用戶都是給慣壞了,微信5 出來後被罵幾條街,還不是習慣了以前的操做,又要去探索新版本的用法,太懶;再說到底,用戶是上帝啊,就這麼慣着吧!
—-
搗亂 2014-3-29