(兩篇文章轉自:https://www.jianshu.com/p/bd1be47a16c1;https://www.jianshu.com/p/180a870a308a)php
什麼是Cookie?html
Cookie 技術產生源於 HTTP 協議在互聯網上的急速發展。隨着互聯網時代的策馬奔騰,帶寬等限制不存在了,人們須要更復雜的互聯網交互活動,就必須同服務器保持活動狀態(簡稱:保活)。因而,在瀏覽器發展初期,爲了適應用戶的需求技術上推出了各類保持 Web 瀏覽狀態的手段,其中就包括了 Cookie 技術。Cookie 在計算機中是個存儲在瀏覽器目錄中的文本文件,當瀏覽器運行時,存儲在 RAM 中發揮做用 (此種 Cookies 稱做 Session Cookies),一旦用戶從該網站或服務器退出,Cookie 可存儲在用戶本地的硬盤上 (此種 Cookies 稱做 Persistent Cookies)。前端
Cookie 起源:1993 年,網景公司僱員 Lou Montulli 爲了讓用戶在訪問某網站時,進一步提升訪問速度,同時也爲了進一步實現我的化網絡,發明了今天普遍使用的 Cookie。(因此,適當的偷懶也會促進人類計算機發展史的一小步~)web
Cookie時效性:目前有些 Cookie 是臨時的,有些則是持續的。臨時的 Cookie 只在瀏覽器上保存一段規定的時間,一旦超過規定的時間,該 Cookie 就會被系統清除。算法
Cookie使用限制:Cookie 必須在 HTML 文件的內容輸出以前設置;不一樣的瀏覽器 (Netscape Navigator、Internet Explorer) 對 Cookie 的處理不一致,使用時必定要考慮;客戶端用戶若是設置禁止 Cookie,則 Cookie 不能創建。 而且在客戶端,一個瀏覽器能建立的 Cookie 數量最多爲 300 個,而且每一個不能超過 4KB,每一個 Web 站點能設置的 Cookie 總數不能超過 20 個。數據庫
執行流程:json
A:首先,客戶端會發送一個http請求到服務器端。後端
B: 服務器端接受客戶端請求後,發送一個http響應到客戶端,這個響應頭,其中就包含Set-Cookie頭部。api
C:在客戶端發起的第二次請求(注意:若是服務器須要咱們帶上Cookie,咱們就須要在B步驟上面拿到這個Cookie而後做爲請求頭一塊兒發起第二次請求),提供給了服務器端能夠用來惟一標識客戶端身份的信息。這時,服務器端也就能夠判斷客戶端是否啓用了cookies。儘管,用戶可能在和應用程序交互的過程當中忽然禁用cookies的使用,可是,這個狀況基本是不太可能發生的,因此能夠不加以考慮,這在實踐中也被證實是對的。瀏覽器
爲了方便理解,能夠先看下這張流程執行圖加深概念
那麼,在瀏覽器上面的請求頭和Cookie在那?下圖給你們截取了其中一種。
那麼,上面都是談瀏覽器上的Cookie,那麼在Android開發中,咱們該如何去管理和使用Cookie?Okhttp做爲經典到爆的網絡框架,它的API(本文是基於Okhttp3.0版本以上,3.0如下的版本API有所不一樣)是經過OkhttpClient中的CookieJar或者攔截器去管理Cookie的。理論上,咱們只需在構建單例OkhttpClient的時候,設置cookiejar或者攔截器,而後具體的操做(具體的操做也就是保存Cookie,取Cookie),Okhttp框架就會幫咱們自動管理Cookie。以下圖:
這是其中一種經過集合的增查特性,就能夠簡單有效的幫咱們管理Cookie。但咱們仍是要經過源代碼去一探究竟。首先,CookieJar是一個接口。
英文註釋翻譯過來就是(對應段落翻譯):
CookieJar這個接口爲HTTP cookies提供了強大的支持和相關策略。
這種策略的實現做用會負責選擇接受和拒絕那些cookie。一個合理的策略是拒絕全部的cookie,儘管這樣會干擾須要cookie的基於會話的自身身份驗證方案。
做爲Cookie的持久性,該接口的實現也必需要提供Cookie的存儲。一種簡單的實現能夠將cookie存儲在內存中;複雜的系統可使用文件系統用於保存已接受的cookie的數據庫。這裏的連接指定cookie存儲模型更新和過時的cookie的策略。
因此,Okhttp的源碼告知咱們能夠將cookie存儲在內存中;複雜的系統可使用文件系統用於保存已接受的cookie的數據庫。所以,咱們就能夠經過Map去簡單的管理和使用。
繼續分析CookieJar接口裏面的方法,依舊上源碼
裏面有方法一個是saveFromResponse(HttpUrl url, List cookies)、loadForRequest(HttpUrl url)
saveFromResponse方法翻譯:根據這個jar的方法,能夠將cookie從一個HTTP響應保存到這裏。請注意,若是響應,此方法可能被稱爲第二次HTTP響應,包括一個追蹤。對於這個隱蔽的HTTP特性,這裏的cookie只包含其追蹤的cookie。簡單點理解就是若是咱們使用了這個方法,就會進行追蹤(說白了就是客戶端請求成功之後,在響應頭裏面去存cookie)
loadForRequest方法翻譯:將cookie從這個方法加載到一個HTTP請求到指定的url。這個方法從網絡上返回的結果多是一個空集合。簡單的實現將返回還沒有過時的已接受的cookie去進行匹配。(說白了就是加載url的時候在請求頭帶上cookie)。
這樣,咱們經過以上代碼就能夠完成了Cookie的非持久化。什麼,非持久化,這又是神馬?
繼續給你們科普,在上面說道,Cookie是具備時效性的,因此,Cookie的管理又分爲持久化Cookie和非持久化Cookie。非持久化Cookie存儲在內存中,也就意味着,其生命週期基本和app保持一致,app關閉後,Cookie丟失。而持久化Cookie則是存儲在本地磁盤中,app關閉後不丟失。那麼,若是咱們要使用Cookie的持久化策略,思想能夠參考上面的非持久化策略,只須要將存儲方式改一下便可:
A:經過響應攔截器從response取出cookie並保存到本地,經過請求攔截器從本地取出cookie並添加到請求中
B:自定義CookieJar,在saveFromResponse()中保存cookie到本地,在loadForRequest()從本地取出cookie。
那麼在這裏主要介紹如何經過Okhttp逼格值較高的攔截器去進行持久化cookie操做。(攔截器源碼見文章末尾處)
這個SaveCookiesInterceptor攔截器的實現,是首先從response獲取set-cookie字段的值,而後經過SharedPreferences保存在本地。
AddCookiesInterceptor請求攔截器,這個攔截的做用就是判斷若是該請求存在cookie,則爲其添加到Header的Cookie中。
寫好這兩個攔截器以後,咱們只須要將實例對象放進OkhttpClient裏面便可快速的完成Cookie持久化操做。(PS:這兩個攔截器在同步Cookie的時候也是超級好用)。
拓展:如何經過客戶端的cookie與H5上面的cookie進行同步,針對這個問題,給你們推薦筆者的另一篇文章
Session :
Session是對於服務端來講的,客戶端是沒有Session一說的。Session是服務器在和客戶端創建鏈接時添加客戶端鏈接標誌,最終會在服務器軟件(Apache、Tomcat、JBoss)轉化爲一個臨時Cookie發送給給客戶端,當客戶端第一請求時服務器會檢查是否攜帶了這個Session(臨時Cookie),若是沒有則會添加Session,若是有就拿出這個Session來作相關操做。
在這裏引用別人家的一個小故事來加深印象:
在說session是啥以前,咱們先來講說爲何會出現session會話,它出現的機理是什麼? 咱們知道,咱們用瀏覽器打開一個網頁,用到的是HTTP協議,瞭解計算機的應該都知道這個協議,它是無狀態的,什麼是無狀態呢?就是說這一次請求和上一次請求是沒有任何關係的,互不認識的,沒有關聯的。可是這種無狀態的的好處是快速。因此就會帶來一個問題就是,我但願幾個請求的頁面要有關聯,好比:我在www.a.com/login.php裏面登錄了,我在www.a.com/index.php 也但願是登錄狀態,可是,這是2個不一樣的頁面,也就是2個不一樣的HTTP請求,這2個HTTP請求是無狀態的,也就是無關聯的,因此沒法單純的在index.php中讀取到它在login.php中已經登錄了! 那咋搞呢?我不可能這2個頁面我都去登錄一遍吧。或者用笨方法這2個頁面都去查詢數據庫,若是有登錄狀態,就判斷是登錄的了。這種查詢數據庫的方案雖然可行,可是每次都要去查詢數據庫不是個事,會形成數據庫的壓力。 因此正是這種訴求,這個時候,一個新的客戶端存儲數據方式出現了:cookie。cookie是把少許的信息存儲在用戶本身的電腦上,它在一個域名下是一個全局的,只要設置它的存儲路徑在域名www.a.com下 ,那麼當用戶用瀏覽器訪問時,php就能夠從這個域名的任意頁面讀取cookie中的信息。因此就很好的解決了我在www.a.com/login.php頁面登錄了,我也能夠在www.a.com/index.php獲取到這個登錄信息了。同時又不用反覆去查詢數據庫。 雖然這種方案很不錯,也很快速方便,可是因爲cookie 是存在用戶端,並且它自己存儲的尺寸大小也有限,最關鍵是用戶能夠是可見的,並能夠隨意的修改,很不安全。那如何又要安全,又能夠方便的全局讀取信息呢?因而,這個時候,一種新的存儲會話機制:session 誕生了。 Session 就是在一次會話中解決2次HTTP的請求的關聯,讓它們產生聯繫,讓2兩個頁面都能讀取到找個這個全局的session信息。session信息存在於服務器端,因此也就很好的解決了安全問題。
Token :
token是用戶身份的驗證方式,咱們一般叫它:令牌。最簡單的token組成:uid(用戶惟一的身份標識)、time(當前時間的時間戳)、sign(簽名,由token的前幾位+鹽以哈希算法壓縮成必定長的十六進制字符串,能夠防止惡意第三方拼接token請求服務器)。還能夠把不變的參數也放進token,避免屢次查庫。
應用場景:
A:當用戶首次登陸成功(註冊也是一種能夠適用的場景)以後, 服務器端就會生成一個 token 值,這個值,會在服務器保存token值(保存在數據庫中),再將這個token值返回給客戶端.
B:客戶端拿到 token 值以後,進行本地保存。(SP存儲是你們可以比較支持和易於理解操做的存儲)
C:當客戶端再次發送網絡請求(通常不是登陸請求)的時候,就會將這個 token 值附帶到參數中發送給服務器.
D:服務器接收到客戶端的請求以後,會取出token值與保存在本地(數據庫)中的token值作對比
對比一:若是兩個 token 值相同, 說明用戶登陸成功過!當前用戶處於登陸狀態!
對比二:若是沒有這個 token 值, 則說明沒有登陸成功.
對比三:若是 token 值不一樣: 說明原來的登陸信息已經失效,讓用戶從新登陸.
Cookie和Session的區別:
一、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
二、cookie不是很安全,別人能夠分析存放在本地的cookie並進行cookie欺騙,考慮到安全應當使用session。
三、session會在必定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能,考慮到減輕服務器性能方面,應當使用cookie。
四、單個cookie保存的數據不能超過4K,不少瀏覽器都限制一個站點最多保存20個cookie。
五、因此我的建議:
將登錄信息等重要信息存放爲session
其餘信息若是須要保留,能夠放在cookie中
Token 和 Session 的區別:
session和 token並不矛盾,做爲身份認證token安全性比session好,由於每一個請求都有簽名還能防止監聽以及重放攻擊,而session就必須靠鏈路層來保障通信安全了。如上所說,若是你須要實現有狀態的會話,仍然能夠增長session來在服務器端保存一些狀態
App一般用restful api跟server打交道。Rest是stateless的,也就是app不須要像browser那樣用cookie來保存session,所以用session token來標示本身就夠了,session/state由api server的邏輯處理。若是你的後端不是stateless的rest api,那麼你可能須要在app裏保存session.能夠在app裏嵌入webkit,用一個隱藏的browser來管理cookie session.
Session是一種HTTP存儲機制,目的是爲無狀態的HTTP提供的持久機制。所謂Session認證只是簡單的把User信息存儲到Session裏,由於SID的不可預測性,暫且認爲是安全的。這是一種認證手段。而Token,若是指的是OAuth Token或相似的機制的話,提供的是 認證 和 受權 ,認證是針對用戶,受權是針對App。其目的是讓 某App有權利訪問 某用戶 的信息。這裏的Token是惟一的。不能夠轉移到其它App上,也不能夠轉到其它 用戶 上。轉過來講Session。Session只提供一種簡單的認證,即有此SID,即認爲有此User的所有權利。是須要嚴格保密的,這個數據應該只保存在站方,不該該共享給其它網站或者第三方App。因此簡單來講,若是你的用戶數據可能須要和第三方共享,或者容許第三方調用API接口,用Token。若是永遠只是本身的網站,本身的App,用什麼就無所謂了。
token就是令牌,好比你受權(登陸)一個程序時,他就是個依據,判斷你是否已經受權該軟件;cookie就是寫在客戶端的一個txt文件,裏面包括你登陸信息之類的,這樣你下次在登陸某個網站,就會自動調用cookie自動登陸用戶名;session和cookie差很少,只是session是寫在服務器端的文件,也須要在客戶端寫入cookie文件,可是文件裏是你的瀏覽器編號.Session的狀態是存儲在服務器端,客戶端只有session id;而Token的狀態是存儲在客戶端。
在先後端分離開發時爲何須要用戶認證呢?緣由是因爲HTTP協定是不儲存狀態的(stateless),這意味着當咱們透過賬號密碼驗證一個使用者時,當下一個request請求時它就把剛剛的資料忘了。因而咱們的程序就不知道誰是誰,就要再驗證一次。因此爲了保證系統安全,咱們就須要驗證用戶否處於登陸狀態。
先後端分離經過Restful API進行數據交互時,如何驗證用戶的登陸信息及權限。在原來的項目中,使用的是最傳統也是最簡單的方式,前端登陸,後端根據用戶信息生成一個token
,並保存這個 token
和對應的用戶id到數據庫或Session中,接着把 token
傳給用戶,存入瀏覽器 cookie,以後瀏覽器請求帶上這個cookie,後端根據這個cookie值來查詢用戶,驗證是否過時。
但這樣作問題就不少,若是咱們的頁面出現了 XSS 漏洞,因爲 cookie 能夠被 JavaScript 讀取,XSS 漏洞會致使用戶 token 泄露,而做爲後端識別用戶的標識,cookie 的泄露意味着用戶信息再也不安全。儘管咱們經過轉義輸出內容,使用 CDN 等能夠儘可能避免 XSS 注入,但誰也不能保證在大型的項目中不會出現這個問題。
在設置 cookie 的時候,其實你還能夠設置 httpOnly 以及 secure 項。設置 httpOnly 後 cookie 將不能被 JS 讀取,瀏覽器會自動的把它加在請求的 header 當中,設置 secure 的話,cookie 就只容許經過 HTTPS 傳輸。secure 選項能夠過濾掉一些使用 HTTP 協議的 XSS 注入,但並不能徹底阻止。
httpOnly 選項使得 JS 不能讀取到 cookie,那麼 XSS 注入的問題也基本不用擔憂了。但設置 httpOnly 就帶來了另外一個問題,就是很容易的被 XSRF,即跨站請求僞造。當你瀏覽器開着這個頁面的時候,另外一個頁面能夠很容易的跨站請求這個頁面的內容。由於 cookie 默認被髮了出去。
另外,若是將驗證信息保存在數據庫中,後端每次都須要根據token
查出用戶id
,這就增長了數據庫的查詢和存儲開銷。若把驗證信息保存在session中,有加大了服務器端的存儲壓力。那咱們可不能夠不要服務器去查詢呢?若是咱們生成token
遵循必定的規律,好比咱們使用對稱加密算法來加密用戶id
造成token
,那麼服務端之後其實只要解密該token
就能夠知道用戶的id
是什麼了。不過呢,我只是舉個例子而已,要是真這麼作,只要你的對稱加密算法泄露了,其餘人能夠經過這種加密方式進行僞造token
,那麼全部用戶信息都再也不安全了。恩,那用非對稱加密算法來作呢,其實如今有個規範就是這樣作的,就是咱們接下來要介紹的 JWT。
JWT 是一個開放標準(RFC 7519),它定義了一種用於簡潔,自包含的用於通訊雙方之間以 JSON 對象的形式安全傳遞信息的方法。JWT 可使用 HMAC 算法或者是 RSA 的公鑰密鑰對進行簽名。它具有兩個特色:
簡潔(Compact)
能夠經過URL, POST 參數或者在 HTTP header 發送,由於數據量小,傳輸速度快
自包含(Self-contained)
負載中包含了全部用戶所須要的信息,避免了屢次查詢數據庫
Header 頭部
頭部包含了兩部分,token 類型和採用的加密算法
{ "alg": "HS256", "typ": "JWT" }
它會使用 Base64 編碼組成 JWT 結構的第一部分,若是你使用Node.js,能夠用Node.js的包base64url來獲得這個字符串。
Base64是一種編碼,也就是說,它是能夠被翻譯回原來的樣子來的。它並非一種加密過程。
Payload 負載
這部分就是咱們存放信息的地方了,你能夠把用戶 ID 等信息放在這裏,JWT 規範裏面對這部分有進行了比較詳細的介紹,經常使用的由 iss(簽發者),exp(過時時間),sub(面向的用戶),aud(接收方),iat(簽發時間)。
{ "iss": "lion1ou JWT", "iat": 1441593502, "exp": 1441594722, "aud": "www.example.com", "sub": "lion1ou@163.com" }
一樣的,它會使用 Base64 編碼組成 JWT 結構的第二部分
Signature 簽名
前面兩部分都是使用 Base64 進行編碼的,即前端能夠解開知道里面的信息。Signature 須要使用編碼後的 header 和 payload 以及咱們提供的一個密鑰,而後使用 header 中指定的簽名算法(HS256)進行簽名。簽名的做用是保證 JWT 沒有被篡改過。
三個部分經過.
鏈接在一塊兒就是咱們的 JWT 了,它可能長這個樣子,長度貌似和你的加密算法和私鑰有關係。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ
.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s
其實到這一步可能就有人會想了,HTTP 請求總會帶上 token,這樣這個 token 傳來傳去佔用沒必要要的帶寬啊。若是你這麼想了,那你能夠去了解下 HTTP2,HTTP2 對頭部進行了壓縮,相信也解決了這個問題。
簽名的目的
最後一步簽名的過程,其實是對頭部以及負載內容進行簽名,防止內容被竄改。若是有人對頭部以及負載的內容解碼以後進行修改,再進行編碼,最後加上以前的簽名組合造成新的JWT的話,那麼服務器端會判斷出新的頭部和負載造成的簽名和JWT附帶上的簽名是不同的。若是要對新的頭部和負載進行簽名,在不知道服務器加密時用的密鑰的話,得出來的簽名也是不同的。
信息暴露
在這裏你們必定會問一個問題:Base64是一種編碼,是可逆的,那麼個人信息不就被暴露了嗎?
是的。因此,在JWT中,不該該在負載裏面加入任何敏感的數據。在上面的例子中,咱們傳輸的是用戶的User ID。這個值實際上不是什麼敏感內容,通常狀況下被知道也是安全的。可是像密碼這樣的內容就不能被放在JWT中了。若是將用戶的密碼放在了JWT中,那麼懷有惡意的第三方經過Base64解碼就能很快地知道你的密碼了。
所以JWT適合用於向Web應用傳遞一些非敏感信息。JWT還常常用於設計用戶認證和受權系統,甚至實現Web應用的單點登陸。
JWT 使用
首先,前端經過Web表單將本身的用戶名和密碼發送到後端的接口。這一過程通常是一個HTTP POST請求。建議的方式是經過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
後端覈對用戶名和密碼成功後,將用戶的id等其餘信息做爲JWT Payload(負載),將其與頭部分別進行Base64編碼拼接後簽名,造成一個JWT。造成的JWT就是一個形同lll.zzz.xxx的字符串。
後端將JWT字符串做爲登陸成功的返回結果返回給前端。前端能夠將返回的結果保存在localStorage或sessionStorage上,退出登陸時前端刪除保存的JWT便可。
前端在每次請求時將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)
後端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過時;檢查Token的接收方是不是本身(可選)。
驗證經過後後端使用JWT中包含的用戶信息進行其餘邏輯操做,返回相應結果。
和Session方式存儲id的差別
Session方式存儲用戶id的最大弊病在於Session是存儲在服務器端的,因此須要佔用大量服務器內存,對於較大型應用而言可能還要保存許多的狀態。通常而言,大型應用還須要藉助一些KV數據庫和一系列緩存機制來實現Session的存儲。
而JWT方式將用戶狀態分散到了客戶端中,能夠明顯減輕服務端的內存壓力。除了用戶id以外,還能夠存儲其餘的和用戶相關的信息,例如該用戶是不是管理員、用戶所在的分組等。雖然說JWT方式讓服務器有一些計算壓力(例如加密、編碼和解碼),可是這些壓力相比磁盤存儲而言可能就不算什麼了。具體是否採用,須要在不一樣場景下用數聽說話。
單點登陸
Session方式來存儲用戶id,一開始用戶的Session只會存儲在一臺服務器上。對於有多個子域名的站點,每一個子域名至少會對應一臺不一樣的服務器,例如:www.taobao.com
,nv.taobao.com
,nz.taobao.com
,login.taobao.com
。因此若是要實如今login.taobao.com
登陸後,在其餘的子域名下依然能夠取到Session,這要求咱們在多臺服務器上同步Session。使用JWT的方式則沒有這個問題的存在,由於用戶的狀態已經被傳送到了客戶端。
總結
JWT的主要做用在於(一)可附帶用戶信息,後端直接經過JWT獲取相關信息。(二)使用本地保存,經過HTTP Header中的Authorization位提交驗證。但其實關於JWT存放到哪裏一直有不少討論,有人說存放到本地存儲,有人說存 cookie。我的偏向於放在本地存儲,若是你有什麼意見和見解歡迎提出。