在學習Cookie
、Session
和Token
之前,我們先了解下HTTP的無狀態協議
。
1、HTTP的無狀態協議
HTTP無狀態協議
是指該協議對事件的處理過程沒有記憶能力,當後續的步驟需要上一步的信息時,則需要重傳,即需要攜帶上一次的信息。
因此,對於存在依賴性的訪問請求,則下一次的傳遞需要攜帶上一次傳遞的信息,依次不斷的疊加,會導致傳輸的信息量會越來越大,服務器響應較慢。
HTTP無狀態訪問在90年代,瀏覽器剛出現的時候,它的作用也僅僅是瀏覽一些文本而已,彼此之間不存在依賴關係(在後面的學習中,我們把這種依賴關係
理解爲會話
)。因此,服務器不需要做任何的記錄,瀏覽器請求什麼,服務器就返回什麼,彼此之間清清楚楚,不存在任何的愛恨情仇,雙方的關係非常的融洽。
隨着技術的發展,特別是當客戶端與服務器進行動態交互的Web應用程序出現之後,HTTP的無狀態特徵嚴重影響了這類Web應用程序,Web應用的交互是雙向,需要承前啓後的,總不能每次都失憶。
就像夏洛特煩惱中,夏洛和大爺的對話:
夏洛:大爺,樓上322住的是馬冬梅家嗎?大爺:馬冬什麼?夏洛:馬冬梅。大爺:什麼冬梅啊?夏洛:馬冬梅啊。大爺:馬什麼梅啊?夏洛:……行,大爺,您先涼快吧。
如簡單的購物車程序也要知道用戶到底在之前選擇了什麼商品,總不能所有用戶都使用一個購物車吧,所以需要把每個用戶都區分開,服務器需要記錄每個用戶的會話,又因爲HTTP是無狀態的,那麼就要想辦法爲每個用戶保持一個會話。
因此市面上出現了兩種保持HTTP連接狀態的技術:Cookie
和Session
。
Cookie是客戶端
保持HTTP會話狀態的技術,而Session是服務端
保持HTTP會話狀態的技術。通常情況下,這兩種技術是結合使用的。
下面,我們將分別學習Cookie、Session,並針對它們的不足,學習Token的原理與使用。
2、Cookie
在學習Cookie之前,我們先考慮以下問題:
(1)什麼是Cookie,Cookie的作用是什麼?
(2)Cookie的工作機制是什麼?
(3)Cookie的基本屬性有哪些?
針對上面的問題,我們一一來做解答。
2.1 Cookie的原理及工作機制
按照官方的定義:
Cookie是保存在客戶端瀏覽器中的文本文件(key-value形式),這個文件與訪問的特定的Web頁面(文檔)關聯在一起,並且保存在本地的客戶端中,Cookie 最根本的用途是幫助 Web 站點保存有關訪問者的信息。
舉個例子,當客戶端瀏覽器訪問服務端時,服務端會記錄每個用戶的訪問信息並以Cookie文件
的形式保存在客戶端,當用戶再次訪問服務端的特定頁面時,服務端會首先檢查客戶端攜帶的Cookie中的用戶身份信息,從而保持了會話的進行。
什麼意思呢,我們舉個生活中的例子:
當我們去銀行辦理儲蓄業務時,櫃員第一次給我們辦了張銀行卡,裏面存放了身份證、密碼、手機等個人信息。當我們下次再來這個銀行時,銀行機器能識別這種卡,從而能夠直接辦理業務。
那麼問題來了,Cookie到底是如何起作用的呢?
事實上,當用戶每次訪問服務器時,Web應用程序都可以讀取Cookie包含的信息,當用戶再次訪問該頁面時,瀏覽器就會在本地硬盤上查找與該URL相關的Cookie,如果該 Cookie 存在(Cookie在不過期的情況下),瀏覽器就將它添加到request header
的Cookie
字段中,與http請求
一起發送到該站點。
我們以登錄爲例:
實現登陸我們就必須需要cookie, 使用 cookie 來保存用戶的信息。
在請求某個域的時候,http 會自動將這個域的 Cookie 放到請求頭當中。所以我們只需要在登陸用戶成功後,將必要的信息通過設置 Set-Cookie 這個響應頭返回給客戶端。
瀏覽器會自動將該頭的信息存儲到當前域名下的 Cookie 中,當下次用戶請求的時候,http 協議會自動將該 cookie 帶上。我們就可以在每次的請求的請求頭當中拿到該 Cookie, 然後去判斷用戶是否登陸,進而根據用戶是否登陸進行相應的處理。
NOTE:
Cookie被添加到
request header
中是「瀏覽器的行爲」,存儲在cookie
的數據「每次」都會被瀏覽器「自動」放在http
請求中。Cookie過多的信息會增加網絡流量,因此,我們必須考慮什麼樣的數據才能放入到Cookie中,我們用的最多的是身份驗證信息。
因此,當用戶第一次訪問並登陸一個網站的時候,Cookie的設置以及發送會經歷以下4個步驟:
(1)客戶端發送一個請求數據
(2)服務器發送一個HttpResponse響應到客戶端,該響應包含Set-Cookie頭部
(3)客戶端保存Cookie,之後向服務器發送請求時,HttpRequest請求中會包含一個Cookie的頭部
(4)服務器返回響應數據
攜帶Cookie訪問2.2 Cookie的組成
以登錄
爲例,一般來說我們不會在用戶登陸後將用戶的用戶名和密碼設置到 Cookie 中,從而直接在請求頭拿到用戶名和密碼
,然後去判斷用戶是否登陸,這些信息過於敏感,暴露出來十分危險。
通常情況下,正確的做法應該是不管用戶是否登錄,都應該通過 Cookie 給用戶返回一個標誌(通常稱爲session_id或者token 等,在下面的部分具體展開),並需要設置其過期時間
、httpOnly
、path
等。每次用戶請求時,都根據這個標誌在服務端去取根據這個標誌存在服務端的用戶的信息。
因此,Cookie一般由以下幾部分構成
(1)Name/Value:
該屬性是設置Cookie的名稱及相對應的值,該值通常是保留在Cookie中的用戶信息。對於認證Cookie,Value值包括Web服務器所提供的訪問令牌(繼續往下看,下面內容會學習令牌)。
(2)Expires屬性:
該屬性是設置Cookie的生存週期。
在默認情況下,Cookie是臨時存在的。
當一個瀏覽器窗口打開時,可以設置Cookie,只要該瀏覽器窗口沒有關閉,Cookie就一直有效,而一旦瀏覽器窗口關閉後,Cookie也就隨之消失。
如果想要cookie在瀏覽器窗口之後還能繼續使用,就需要爲Cookie設置一個生存期。所謂生存期也就是Cookie的終止日期,在這個終止日期到達之前,瀏覽器都可以讀取該Cookie。一旦終止日期到達之後,該cookie將會從cookie文件中刪除。
(3)Path屬性:
定義了Web站點上可以訪問該Cookie的目錄
。比如,設置爲"/"
表示允許當前域名下的所有路徑都可以使用該Cookie。
(4)Domain屬性:
指定了可以訪問該 Cookie 的 Web 站點
或域
,默認爲當前域。
如當前域是www.simon.item,那麼它的子域www.simon.item.count共享父域的Cookie,對於不同的域或者平行域則無法共享該Cookie。
(5)Secure屬性:
secure
是 cookie 的安全標誌,指定是否使用HTTPS
安全協議發送Cookie。
(6)HTTPOnly 屬性 :
用於防止客戶端腳本通過document.cookie
屬性訪問Cookie,有助於保護Cookie不被跨站腳本攻擊竊取或篡改。
綜上所述,服務器通過發送一個名爲 Set-Cookie
(Cookie是一個對象,需要自己new出來) 的HTTP頭來創建一個cookie,並作爲 Response Headers 的一部分。每個Set-Cookie
表示一個 Cookie(如果有多個Cookie,需寫多個Set-Cookie),每個屬性也是以名/值
對的形式(除了secure
),屬性間以分號加空格
隔開。格式如下:
Set-Cookie: name1=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
Set-Cookie: name2=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
Set-Cookie: name3=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
......
除了在服務器端
設置,還可以在客戶端
設置Cookie
document.cookie = "test1=myCookie1;"
document.cookie = "test2=myCookie2; domain=.google.com.hk; path=/webhp"
document.cookie = "test3=myCookie3; domain=.google.com.hk; expires=Sat, 08 AUG 2021 16:00:00 GMT; secure"
document.cookie = "test4=myCookie4; domain=.google.com.hk; max-age=10800;"
NOTE:只有name/value
可以被髮送至服務端,其餘的參數僅僅是服務端給客戶端的指示或者客戶端自身的約束。
由於Cookie的出現可以解決HTTP的無狀態,維持會話的正常進行,我們使用Cookie的應用場景通常有以下幾種:
(1)購物車(網購)
(2)自動登錄(登錄賬號時的自動登錄)
(3)精準廣告
平常瀏覽網頁時有時會推出商品剛好是你最近瀏覽過,買過的類似東西,這些是通過cookie記錄的。
雖然Cookie有很多優點,其缺點也很明顯。
(1)每個域的Cookie總數是有限的,不同瀏覽器之間各有不同
如Firefox限制每個域最多50個cookie,IE限制50個,Chrome對於每個域的Cookie數量沒有規定
(2)Cookie大小限制
大多數瀏覽器限制Cookie的大小爲4KB
(3)安全性
Cookie文件中可能含有涉密信息,可能會導致信息泄露。
由於Cookie存儲信息的大小不僅有限制,而且還存在信息安全問題,因此,必須想辦法把一些具體的信息存儲到服務端上。因此,Session的出現可以解決這個問題。
3、Session
Session
在計算機中被稱爲會話控制
。
Session對象
可以存儲特定用戶會話所需的屬性
及配置信息
,它通過給不同的用戶發送session_id並放在Cookie中,然後具體的數據則是保存在session中。
如果用戶已經登錄,則服務器會在Cookie中保存一個session_id
,下次再次請求的時候,會把該session_id攜帶上來,服務器根據session_id在session庫中獲取用戶的session數據。就能知道該用戶到底是誰,以及之前保存的一些狀態信息,從而保持會話連接的狀態。
圖解如下:
Cookie+SessionSession流程對應的後端代碼如下:
package xdp.gacl.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setCharacterEncoding("UTF=8");
response.setContentType("text/html;charset=UTF-8");
//使用request對象的getSession()獲取session,如果session不存在則創建一個
HttpSession session = request.getSession();
//將數據存儲到session中
session.setAttribute("data", "小郎同學");
//獲取session的Id
String sessionId = session.getId();
//判斷session是不是新創建的
if (session.isNew()) {
response.getWriter().print("session創建成功,session的id是:"+sessionId);
}else {
response.getWriter().print("服務器已經存在該session了,session的id是:"+sessionId);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
隨着訪問服務器的用戶數量的增多,服務器上保存的Session
也日益增多,這對服務器來說是個巨大的開銷,對於單個服務器的Web應用匯總,大量的Session
會佔用比較多的內存。
不僅如此,在分佈式系統中,由於負載均衡對請求轉發,這樣就有可能導致同一個用戶的請求分發到不同的服務器上,會出現不能獲取不到Session的情況。
Session不一致原理圖
Session不一致原理圖解決Session不一致的情況通常有三種方法。
1、反向代理hash一致性
這類方法是使用Nginx
的負載均衡算法其中的hash_ip
算法將ip
固定到某一臺服務器上,這樣就不會出現session
共享問題,因爲同一個ip
訪問下,永遠是同一個服務器。
但是這樣的做法也是不保險的,當綁定的機器掛了,請求還是會被分發到別的機器上去。
因此,可以對多個服務器間進行Session複製
,這樣就可以保障每個服務器上都包好全部請求的session
2、Session複製
Session複製多個服務器間進行Session複製非常佔用內網的帶寬,每個服務器都有相同的Session不僅導致服務器的空間利用降低
,而且受內存的限制,無法水平擴展
。
既然每個服務器都包含相同的Session,我們可以把Session統一存儲管理。
3、共享Session服務器
該方法是把Session集中存儲一個服務器上
,Session服務器是將有狀態的Session信息與無狀態的應用服務器相分離,所有的請求都來訪問這個session服務器。
這種方法雖然不再需要複製,但是卻增加了單點失敗的可能性,如果負責Session的機器掛了,那麼保存所有用戶信息的Session將會丟失。有沒有一種辦法使得服務端存儲這些session呢?
在這個基礎上,Token就出現了。
4、Token
通常意義上的token
是把session
中的內容都放到token中(可以明文形式或者用對稱加密算法加密
(加密密鑰放在服務器端), 然後再把這些內容做hash簽名
(密鑰在服務器),把內容和其簽名拼接成一個字符串(這個就是token) 發送給客戶端
。
客戶端後續請求都帶上這個token,服務器首先校驗token
是否被篡改(根據hash簽名
校驗),如果沒被篡改而且token 也沒過期,就把token中的用戶信息(可能還有權限信息等等)拿出來,直接使用,不需要像session一樣去查詢具體用戶信息。
通常情況下,基於Token的身份驗證的過程如下:
(1)用戶通過用戶名和密碼發送請求。
(2)程序驗證。
(3)程序返回一個簽名的token 給客戶端。
(4)客戶端儲存token,並且每次用於每次發送請求。
(5)服務端驗證token並返回數據。
使用token訪問Token具有以下的優勢:
(1)無狀態、可擴展
在客戶端存儲的token是無狀態的,並且能夠被擴展。基於這種無狀態和不存儲Session信息,負載負載均衡器能夠將用戶信息從一個服務傳到其他服務器上。
如果我們將已驗證的用戶的信息保存在Session中,則每次請求都需要用戶向已驗證的服務器發送驗證信息(稱爲Session親和性)。用戶量大時,可能會造成一些擁堵。
但是不要着急。使用tokens之後這些問題都迎刃而解,因爲服務端使用token可以使用祕鑰找到用戶的信息。
(2)安全性
請求中發送token而不再是發送cookie能夠防止CSRF(跨站請求僞造)。即使在客戶端使用cookie存儲token,cookie也僅僅是一個存儲機制而不是用於認證。不將信息存儲在Session中,讓我們少了對session操作。
token是有時效的,一段時間之後用戶需要重新驗證。我們也不一定需要等到token自動失效,token有撤回的操作,通過token revocataion可以使一個特定的token或是一組有相同認證的token無效。
- 可擴展性
使用token時,可以提供可選的權限給第三方應用程序。當用戶想讓另一個應用程序訪問它們的數據,我們可以通過建立自己的API,得出特殊權限的tokens。
如使用微信登錄微博
- 多平臺跨域
只要用戶有一個通過了驗證的token,數據和資源就能夠在任何域上被請求到。
總結
下面,我們對Cookie、Session和Token做以下總結:
HTTP請求是無狀態
的,就是說第一次和服務器連接並登陸成功後,第二次請求服務器仍然不知道當前請求的用戶。Cookie出現就是解決了這個問題,第一次登陸後服務器返回一些數據(cookie)給瀏覽器,然後瀏覽器保存在本地,當用戶第二次返回請求的時候,就會把上次請求存儲的cookie數據自動攜帶給服務器。
如果關閉瀏覽器Cookie失效(Cookie就是保存在內存中)
如果關閉瀏覽器Cookie不失效(Cookie保存在磁盤中)
Session和Cookie的作用有點類似,都是爲了存儲用戶相關的信息
。
不同的是,Cookie是存儲在本地瀏覽器
,而Session存儲在服務器
。存儲在服務器的數據會更加的安全,不容易被竊取。但存儲在服務器也有一定的弊端,就是會佔用服務器的資源。
存儲在服務端
- 通過cookie存儲一個session_id,然後具體的數據則是保存在session中。
- 如果用戶已經登錄,則服務器會在cookie中保存一個session_id,下次再次請求的時候,會把該session_id攜帶上來,服務器根據session_id在session庫中獲取用戶的session數據。就能知道該用戶到底是誰,以及之前保存的一些狀態信息。
1、Cookie與Session的區別
cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
cookie不是很安全,別人可以分析存放在本地的cookie並進行cookie欺騙考慮到安全應當使用session。
session會在一定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能考慮到減輕服務器性能方面,應當使用cookie。
單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。因此使用cookie只能存儲一些小量的數據。
所以開發人員的通常做法是:
將登陸信息等重要信息存放爲Session 其他信息如果需要保留,可以放在Cookie中
2、Token和Session的區別
共同點:
都是保存了用戶身份信息,都有過期時間。
不同點:
session翻譯爲會話,token翻譯爲令牌。
session是
空間換時間
,token是時間換空間
。session和session_id:服務器會保存一份,可能保存到緩存/數據庫/文件。
token:服務器不需要記錄任何東西,每次都是一個
無狀態的請求
,每次都是通過解密來驗證是否合法
。token 只是一個 key,不存放實際的數據,與這個 token 相關的數據還是存放在服務器上,例如 Session,Redis 等分佈式緩存裏,用 token 去請求對應的數據。session_id:一般是隨機字符串,要到
服務器檢索id的有效性
。出現請求:服務器重啓內存中的session沒了,數據庫服務器掛了。token 和 cookie 本質上沒啥區別,只不過 token 只是一個字符串,訪問的時候可以放在 url 的參數,header 裏等,不像 cookie 那麼重量級,而且移動端訪問的時候 token 更方便,僅此而已。
巨人的肩膀
[1]https://segmentfault.com/a/1190000006156098[2]https://www.cnblogs.com/moyand/p/9047978.html[3]https://blog.csdn.net/qq_43542074/article/details/100395011[4]https://baike.baidu.com/item/HTTP%E6%97%A0%E7%8A%B6%E6%80%81%E5%8D%8F%E8%AE%AE/5808645?fr=aladdin