如下要說的,雖然不是開發過程當中必須會遇到的,但倒是進階之路上必需要掌握的,一些涉及到狀態管理與安全的應用當中尤其重要。php
我以前雖略有學習,但也是東拼西湊臨時看的一點皮毛,因此在這個假期利用一點時間,整合一篇博文出來,方便之後本身溫故,固然能對新入行的朋友有些許幫助,那是最好的了。css
好,廢話結束,下面我們開始。html
----------------------------前端
咱們首先分別說一下cookie、session和token各是什麼,以及使用方法,而後再說一下他們的關係和區別。html5
1、Cookiejava
若是是歷來沒接觸過cookie的童鞋,能夠先去http://www.runoob.com/js/js-cookies.html文檔看一下,很通俗易懂的,裏面介紹了cookie的基礎屬性以及使用cookie時的三種封裝,十分鐘搞定了再回來。
web
因爲HTTP是一種無狀態的協議,服務器單從網絡鏈接上無從知道客戶身份。怎麼辦呢?就給客戶端們頒發一個通行證吧,每人一個,不管誰訪問都必須攜帶本身通行證。這樣服務器就能從通行證上確認客戶身份了。這就是Cookie的工做原理。ajax
Cookie其實是一小段的文本信息。客戶端請求服務器,若是服務器須要記錄該用戶狀態,就使用response向客戶端瀏覽器頒發一個Cookie。客戶端瀏覽器會把Cookie保存起來。當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給服務器。服務器檢查該Cookie,以此來辨認用戶狀態。服務器還能夠根據須要修改Cookie的內容。redis
除了name(名)和value(值),cookie還有如下一些可選屬性,用來控制cookie的有效期,做用域,安全性等:算法
expires屬性
指定了cookie的生存期,默認狀況下cookie是暫時存在的,他們存儲的值只在瀏覽器會話期間存在,當用戶退出瀏覽器後這些值也會丟失,若是想讓cookie存在一段時間,就要爲expires屬性設置爲將來的一個用毫秒數表示的過時日期或時間點,expires默認爲設置的expires的當前時間。如今已經被max-age屬性所取代,max-age用秒來設置cookie的生存期。
若是max-age屬性爲正數,則表示該cookie會在max-age秒以後自動失效。瀏覽器會將max-age爲正數的cookie持久化,即寫到對應的cookie文件中。不管客戶關閉了瀏覽器仍是電腦,只要還在max-age秒以前,登陸網站時該cookie仍然有效。
若是max-age爲負數,則表示該cookie僅在本瀏覽器窗口以及本窗口打開的子窗口內有效,關閉窗口後該cookie即失效。max-age爲負數的Cookie,爲臨時性cookie,不會被持久化,不會被寫到cookie文件中。cookie信息保存在瀏覽器內存中,所以關閉瀏覽器該cookie就消失了。cookie默認的max-age值爲-1。
若是max-age爲0,則表示刪除該cookie。cookie機制沒有提供刪除cookie的方法,所以經過設置該cookie即時失效實現刪除cookie的效果。失效的Cookie會被瀏覽器從cookie文件或者內存中刪除。
若是不設置expires或者max-age這個cookie默認是Session的,也就是關閉瀏覽器該cookie就消失了。
這裏要說明一下:Session的cookie在ie6下,若是用戶實在網頁上跳轉打開頁面或新開窗口(包括target=」_blank」,鼠標右鍵新開窗口),都是在同一個Session內。若是用戶新開瀏覽器程序或者說是進程再打開當前的頁面就不是同一個Session。其餘瀏覽器只要你Session存在,仍是同一個Session,cookie還能共享。
domain屬性
domain屬性可使多個web服務器共享cookie。domain屬性的默認值是建立cookie的網頁所在服務器的主機名。不能將一個cookie的域設置成服務器所在的域以外的域。
例如讓位於a.sodao.com的服務器可以讀取b.sodao.com設置的cookie值。若是b.sodao.com的頁面建立的cookie把它的path屬性設置爲「/」,把domain屬性設置成「.sodao.com」,那麼全部位於b.sodao.com的網頁和全部位於a.sodao.com的網頁,以及位於sodao.com域的其餘服務器上的網頁均可以訪問這個cookie。
path屬性
它指定與cookie關聯在一塊兒的網頁。在默認的狀況下cookie會與建立它的網頁,該網頁處於同一目錄下的網頁以及與這個網頁所在目錄下的子目錄下的網頁關聯。
secure屬性
它是一個布爾值,指定在網絡上如何傳輸cookie,默認是不安全的,經過一個普通的http鏈接傳輸;
HttpOnly屬性
HttpOnly 屬性限制了 cookie 對 HTTP 請求的做用範圍。特別的,該屬性指示用戶代理忽略那些經過「非 HTTP」 方式對 cookie 的訪問(好比瀏覽器暴露給js的接口)。注意 HttpOnly 屬性和 Secure 屬性相互獨立:一個 cookie 既能夠是 HttpOnly 的也能夠有 Secure 屬性。
在前段時間的項目中我就用js去讀取一個cookie,結果怎麼都取不到這個值,最後查證這個cookie是httpOnly的,花了近2個小時,悲劇了。
cookie的傳輸
瀏覽器將cookie信息以name-value對
的形式存儲於本地,每當請求新文檔時,瀏覽器將發送Cookie,目的是讓Server能夠經過HTTP請求追蹤客戶。因此從WEB性能的角度來講咱們要儘可能的減少cookie,以達到傳輸性能的最大化。
cookie的編碼和解碼
因爲cookie的名/值中的值不容許包含分號,逗號和空格符,爲了最大化用戶代理和服務器的兼容性,任何被存儲爲 cookie 值的數據都應該被編碼,例如用咱們前端熟知的js全局函數encodeURIComponent編碼和decodeURIComponent解碼。
cookie做爲客戶端存儲
前面說了每當請求新文檔時,瀏覽器將發送Cookie到服務器,致使WEB性能降低。因此不建議將cookie做爲客戶端存儲一種實現方案,替代方案參見:JavaScript本地存儲實踐(html5的localStorage和ie的userData)等。
cookie的同名問題
同名的 cookie,不一樣的 domain 或不一樣的 path,屬不一樣的 cookie;同名的 cookie,相同的 domain 且相同的 path,不一樣的 expires,屬同一個 cookie。
cookie的web安全方面
Cookie 的HttpOnly 屬性是Cookie 的擴展功能,它使JavaScript 腳本沒法得到Cookie。其主要目的爲防止跨站腳本攻擊(Cross-sitescripting,XSS)對Cookie 的信息竊取。發送指定HttpOnly 屬性的Cookie 的方法
Set-Cookie: name=value; HttpOnly
Cookie 的secure 屬性用於限制Web 頁面僅在HTTPS 安全鏈接時,才能夠發送Cookie。
基本上能夠用到的cookie相關的知識就是以上這些,接下來咱們說一下session。
二、Session
Session是服務器端使用的一種記錄客戶端狀態的機制,使用上比Cookie簡單一些,服務器使用一種相似於散列表的結構來保存信息。
因爲Session這個詞彙包含的語義不少,所以須要在這裏明確一下 Session的含義。
首先,咱們一般都會把Session翻譯成會話,所以咱們能夠把客戶端瀏覽器與服務器之間一系列交互的動做稱爲一個 Session。
從這個語義出發,咱們會提到Session持續的時間,會提到在Session過程當中進行了什麼操做等等;
其次,Session指的是服務器端爲客戶端所開闢的存儲空間,在其中保存的信息就是用於保持狀態。
從這個語義出發,咱們則會提到往Session中存放什麼內容,如何根據鍵值從 Session中獲取匹配的內容等。
要使用Session,第一步固然是建立Session了。那麼Session在什麼時候建立呢?固然仍是在服務器端程序運行的過程當中建立的,在建立了Session的同時,服務器會爲該Session生成惟一的Session id,而這個Session id在隨後的請求中會被用來從新得到已經建立的Session;
在Session被建立以後,就能夠調用Session相關的方法往Session中增長內容了,而這些內容只會保存在服務器中,發到客戶端的只有Session id;當客戶端再次發送請求的時候,會將這個Session id帶上,服務器接受到請求以後就會依據Session id找到相應的Session,從而再次使用之。
若是說Cookie機制是經過檢查客戶身上的「通行證」來肯定客戶身份的話,那麼Session機制就是經過檢查服務器上的「客戶明細表」來確認客戶身份。Session至關於程序在服務器上創建的一份客戶檔案,客戶來訪的時候只須要查詢客戶檔案表就能夠了。
Session的生命週期
Session保存在服務器端。爲了得到更高的存取速度,服務器通常把Session放在內存裏。每一個用戶都會有一個獨立的Session。若是Session內容過於複雜,當大量客戶訪問服務器時可能會致使內存溢出。所以,Session裏的信息應該儘可能精簡。
Session在用戶第一次訪問服務器的時候自動建立。須要注意只有訪問JSP、Servlet等程序時纔會建立Session,只訪問HTML、IMAGE等靜態資源並不會建立Session。若是還沒有生成Session,也可使用request.getSession(true)強制生成Session。
Session生成後,只要用戶繼續訪問,服務器就會更新Session的最後訪問時間,並維護該Session。用戶每訪問服務器一次,不管是否讀寫Session,服務器都認爲該用戶的Session「活躍(active)」了一次。
也就是說,session狀態管理,是依賴cookie的,若是一旦在瀏覽器中cookie被禁止了,session機制就會癱瘓(不絕對),以前咱們有兩種方法來在cookie被禁時使用:
常常被使用的一種技術叫作URL重寫,就是把session id直接附加在URL路徑的後面。
還有一種技術叫作表單隱藏字段。
不過如今二者存在必定安全問題,通常的大型網站都已經再也不使用,像web淘寶,一旦禁止了瀏覽器的cookie機制,便會被禁止登陸,而網易音樂則一種半登陸狀態,既只有登陸信息,可是像經過session id取到的列表則不會顯示,有興趣的童鞋能夠禁了cookie以後,去登陸試試。
由於session是服務端機制,前端並不會對其進行操做,因此一些方法咱們此處略過,感興趣的童鞋,能夠研究一下Session的其餘方法。
session基本上就介紹到這裏,下面咱們說一下token。
3、Token
token的意思是「令牌」,是用戶身份的驗證方式,最簡單的token組成:uid(用戶惟一的身份標識)、time(當前時間的時間戳)、sign(簽名,由token的前幾位+鹽以哈希算法壓縮成必定長的十六進制字符串,能夠防止惡意第三方拼接token請求服務器)。
關於token,比較流行的一種方式是使用web token,所謂的token能夠看做是一個標識身份的令牌。
客戶端在登陸成功後能夠得到服務端加密後的token,而後在後續須要身份認證的接口請求中在header中帶上這個token,服務端就能夠經過判斷token的有效性來驗證該請求是否合法。
是否是以爲這個token和session_id有點像,彷佛二者是乾的同一件事,我來講一個場景:
用戶主動退出登陸狀態。
容易想到的方案是,客戶端登陸成功後, 服務器爲其分配sessionId, 客戶端隨後每次請求資源時都帶上sessionId。
服務器判斷用戶是否登陸, 徹底依賴於sessionId, 一旦其被截獲, 黑客就可以模擬出用戶的請求。
因而咱們須要引入token的概念: 用戶登陸成功後, 服務器不但爲其分配了sessionId, 還分配了token, token是維持登陸狀態的關鍵祕密數據。
在服務器向客戶端發送的token數據,也須要加密。
客戶端向服務器第一次發起登陸請求(不傳輸用戶名和密碼)。
服務器利用RSA算法產生一對公鑰和私鑰。並保留私鑰, 將公鑰發送給客戶端。
客戶端收到公鑰後, 加密用戶密碼,向服務器發送用戶名和加密後的用戶密碼; 同時另外產生一對公鑰和私鑰,本身保留私鑰, 向服務器發送公鑰; 因而第二次登陸請求傳輸了用戶名和加密後的密碼以及客戶端生成的公鑰。
服務器利用保留的私鑰對密文進行解密,獲得真正的密碼。 通過判斷, 肯定用戶能夠登陸後,生成sessionId和token, 同時利用客戶端發送的公鑰,對token進行加密。最後將sessionId和加密後的token返還給客戶端。
客戶端利用本身生成的私鑰對token密文解密, 獲得真正的token。
這能夠看作是token和session管理狀態的區別,能夠看出一個是放在服務端,一個是放在客戶端,這也就是下面咱們要說的,三者之間的區別以及聯繫。
4、session、cookie以及token的聯繫
不打算按着網上的模式,陳列一堆諸如cookie和session的區別之類的話題,三者最終都會落實在會話管理上。
So,咱們今天就說說三者在會話管理上的區別,以及應用。
一、session管理方式
二、cookie管理方式
三、token管理方式
1.session管理方式
在早期web應用中,一般使用服務端session來管理用戶的會話。快速瞭解服務端session:
1) 服務端session是用戶第一次訪問應用時,服務器就會建立的對象,表明用戶的一次會話過程,能夠用來存放數據。服務器爲每個session都分配一個惟一的sessionid,以保證每一個用戶都有一個不一樣的session對象。
2)服務器在建立完session後,會把sessionid經過cookie返回給用戶所在的瀏覽器,這樣當用戶第二次及之後向服務器發送請求的時候,就會經過cookie把sessionid傳回給服務器,以便服務器可以根據sessionid找到與該用戶對應的session對象。
3)session一般有失效時間的設定,好比2個小時。當失效時間到,服務器會銷燬以前的session,並建立新的session返回給用戶。可是隻要用戶在失效時間內,有發送新的請求給服務器,一般服務器都會把他對應的session的失效時間根據當前的請求時間再延長2個小時。
4)session在一開始並不具有會話管理的做用。它只有在用戶登陸認證成功以後,而且往sesssion對象裏面放入了用戶登陸成功的憑證,才能用來管理會話。管理會話的邏輯也很簡單,只要拿到用戶的session對象,看它裏面有沒有登陸成功的憑證,就能判斷這個用戶是否已經登陸。當用戶主動退出的時候,會把它的session對象裏的登陸憑證清掉。因此在用戶登陸前或退出後或者session對象失效時,確定都是拿不到須要的登陸憑證的。
主流的web開發平臺(java,.net,php)都原生支持這種會話管理的方式,並且開發起來很簡單,相信大部分後端開發人員在入門的時候都瞭解並使用過它。它還有一個比較大的優勢就是安全性好,由於在瀏覽器端與服務器端保持會話狀態的媒介始終只是一個sessionid串,只要這個串夠隨機,攻擊者就不能輕易冒充他人的sessionid進行操做;除非經過CSRF或http劫持的方式,纔有可能冒充別人進行操做;即便冒充成功,也必須被冒充的用戶session裏面包含有效的登陸憑證才行。可是在真正決定用它管理會話以前,也得根據本身的應用狀況考慮如下幾個問題:
1)這種方式將會話信息存儲在web服務器裏面,因此在用戶同時在線量比較多時,這些會話信息會佔據比較多的內存;
2)當應用採用集羣部署的時候,會遇到多臺web服務器之間如何作session共享的問題。由於session是由單個服務器建立的,可是處理用戶請求的服務器不必定是那個建立session的服務器,這樣他就拿不到以前已經放入到session中的登陸憑證之類的信息了;
3)多個應用要共享session時,除了以上問題,還會遇到跨域問題,由於不一樣的應用可能部署的主機不同,須要在各個應用作好cookie跨域的處理。
針對問題1和問題2,我見過的解決方案是採用redis這種中間服務器來管理session的增刪改查,一來減輕web服務器的負擔,二來解決不一樣web服務器共享session的問題。針對問題3,因爲服務端的session依賴cookie來傳遞sessionid,因此在實際項目中,只要解決各個項目裏面如何實現sessionid的cookie跨域訪問便可,這個是能夠實現的,就是比較麻煩,先後端有可能都要作處理。
若是不考慮以上三個問題,這種管理方式比較值得使用,尤爲是一些小型的web應用。可是一旦應用未來有擴展的必要,那就得謹慎對待前面的三個問題。若是真要在項目中使用這種方式,推薦結合單點登陸框架如CAS一塊兒用,這樣會使應用的擴展性更強。
2.cookie管理方式
因爲前一種方式會增長服務器的負擔和架構的複雜性,因此後來就有人想出直接把用戶的登陸憑證直接存到客戶端的方案,當用戶登陸成功以後,把登陸憑證寫到cookie裏面,並給cookie設置有效期,後續請求直接驗證存有登陸憑證的cookie是否存在以及憑證是否有效,便可判斷用戶的登陸狀態。使用它來實現會話管理的總體流程以下:
1)用戶發起登陸請求,服務端根據傳入的用戶密碼之類的身份信息,驗證用戶是否知足登陸條件,若是知足,就根據用戶信息建立一個登陸憑證,這個登陸憑證簡單來講就是一個對象,最簡單的形式能夠只包含用戶id,憑證建立時間和過時時間三個值。
2)服務端把上一步建立好的登陸憑證,先對它作數字簽名,而後再用對稱加密算法作加密處理,將簽名、加密後的字串,寫入cookie。cookie的名字必須固定(如ticket),由於後面再獲取的時候,還得根據這個名字來獲取cookie值。這一步添加數字簽名的目的是防止登陸憑證裏的信息被篡改,由於一旦信息被篡改,那麼下一步作簽名驗證的時候確定會失敗。作加密的目的,是防止cookie被別人截取的時候,沒法輕易讀到其中的用戶信息。
3)用戶登陸後發起後續請求,服務端根據上一步存登陸憑證的cookie名字,獲取到相關的cookie值。而後先作解密處理,再作數字簽名的認證,若是這兩步都失敗,說明這個登陸憑證非法;若是這兩步成功,接着就能夠拿到原始存入的登陸憑證了。而後用這個憑證的過時時間和當前時間作對比,判斷憑證是否過時,若是過時,就須要用戶再從新登陸;若是未過時,則容許請求繼續。
這種方式最大的優勢就是實現了服務端的無狀態化,完全移除了服務端對會話的管理的邏輯,服務端只須要負責建立和驗證登陸cookie便可,無需保持用戶的狀態信息。對於第一種方式的第二個問題,用戶會話信息共享的問題,它也能很好解決:由於若是隻是同一個應用作集羣部署,因爲驗證登陸憑證的代碼都是同樣的,因此無論是哪一個服務器處理用戶請求,總能拿到cookie中的登陸憑證來進行驗證;若是是不一樣的應用,只要每一個應用都包含相同的登陸邏輯,那麼他們也是能輕易實現會話共享的,不過這種狀況下,登陸邏輯裏面數字簽名以及加密解密要用到的密鑰文件或者密鑰串,須要在不一樣的應用裏面共享,總而言之,就是須要算法徹底保持一致。
這種方式因爲把登陸憑證直接存放客戶端,而且須要cookie傳來傳去,因此它的缺點也比較明顯:
1)cookie有大小限制,存儲不了太多數據,因此要是登陸憑證存的消息過多,致使加密簽名後的串太長,就會引起別的問題,好比其它業務場景須要cookie的時候,就有可能沒那麼多空間可用了;因此用的時候得謹慎,得觀察實際的登陸cookie的大小;好比太長,就要考慮是非是數字簽名的算法太嚴格,致使簽名後的串太長,那就適當調整簽名邏輯;好比若是一開始用4096位的RSA算法作數字簽名,能夠考慮換成102四、2048位;
2)每次傳送cookie,增長了請求的數量,對訪問性能也有影響;
3)也有跨域問題,畢竟仍是要用cookie。
相比起第一種方式,cookie-based方案明顯仍是要好一些,目前好多web開發平臺或框架都默認使用這種方式來作會話管理,好比php裏面yii框架,這是咱們團隊後端目前用的,它用的就是這個方案,以上提到的那些登陸邏輯,框架也都已經封裝好了,實際用起來也很簡單;asp.net裏面forms身份認證,也是這個思路,這裏有一篇好文章把它的實現細節都說的很清楚:
前面兩種會話管理方式由於都用到cookie,不適合用在native app裏面:native app很差管理cookie,畢竟它不是瀏覽器。這兩種方案都不適合用來作純api服務的登陸認證。要實現api服務的登陸認證,就要考慮下面要介紹的第三種會話管理方式。
3.token管理方式
這種方式從流程和實現上來講,跟cookie-based的方式沒有太多區別,只不過cookie-based裏面寫到cookie裏面的ticket在這種方式下稱爲token,這個token在返回給客戶端以後,後續請求都必須經過url參數或者是http header的形式,主動帶上token,這樣服務端接收到請求以後就能直接從http header或者url裏面取到token進行驗證:
這種方式不經過cookie進行token的傳遞,而是每次請求的時候,主動把token加到http header裏面或者url後面,因此即便在native app裏面也能使用它來調用咱們經過web發佈的api接口。app裏面還要作兩件事情:
1)有效存儲token,得保證每次調接口的時候都能從同一個位置拿到同一個token;
2)每次調接口的的代碼裏都得把token加到header或者接口地址裏面。
看起來麻煩,其實也不麻煩,這兩件事情,對於app來講,很容易作到,只要對接口調用的模塊稍加封裝便可。
這種方式一樣適用於網頁應用,token能夠存於localStorage或者sessionStorage裏面,而後每發ajax請求的時候,都把token拿出來放到ajax請求的header裏便可。不過若是是非接口的請求,好比直接經過點擊連接請求一個頁面這種,是沒法自動帶上token的。因此這種方式也僅限於走純接口的web應用。
這種方式用在web應用裏也有跨域的問題,好比應用若是部署在a.com,api服務部署在b.com,從a.com裏面發出ajax請求到b.com,默認狀況下是會報跨域錯誤的,這種問題能夠用CORS(跨域資源共享)的方式來快速解決,相關細節可去閱讀前面給出的CORS文章詳細瞭解。
這種方式跟cookie-based的方式一樣都還有的一個問題就是ticket或者token刷新的問題。有的產品裏面,你確定不但願用戶登陸後,操做了半個小時,結果ticket或者token到了過時時間,而後用戶又得去從新登陸的狀況出現。這個時候就得考慮ticket或token的自動刷新的問題,簡單來講,能夠在驗證ticket或token有效以後,自動把ticket或token的失效時間延長,而後把它再返回給客戶端;客戶端若是檢測到服務器有返回新的ticket或token,就替換原來的ticket或token。
總結:
前面這三種方式,各自有各自的優勢及使用場景,我以爲沒有哪一個是最好的,作項目的時候,根據項目未來的擴展狀況和架構狀況,才能決定用哪一個是最合適的。本文的目的也就是想介紹這幾種方式的原理,以便掌握web應用中登陸驗證的關鍵因素。
做爲一個前端開發人員,本文雖然介紹了3種會話管理的方式,可是與前端關係最緊密的仍是第三種方式,畢竟如今前端開發SPA應用以及hybrid應用已經很是流行了,因此掌握好這個方式的認證過程和使用方式,對前端來講,顯然是頗有幫助的。
文章部分參考:諸葛流雲