Cookie
-
瀏覽器第一次訪問服務端時,服務器此時肯定不知道他的身份,所以創建一個獨特的身份標識數據,格式為key=value
,放入到Set-Cookie
字段里,隨著響應報文發給瀏覽器。
-
瀏覽器看到有Set-Cookie
字段以后就知道這是服務器給的身份標識,于是就保存起來,下次請求時會自動將此key=value
值放入到Cookie
字段中發給服務端。
-
服務端收到請求報文后,發現Cookie
字段中有值,就能根據此值識別用戶的身份然后提供個性化的服務。
接下來我們用代碼演示一下服務器是如何生成,我們自己搭建一個后臺服務器,這里我用的是SpringBoot搭建的,并且寫入SpringMVC的代碼如下:
@RequestMapping("/testCookies")
public String cookies(HttpServletResponse response){
response.addCookie(new Cookie("testUser","xxxx"));
return "cookies";
}
項目啟動以后我們輸入路徑http://localhost:8005/testCookies
,然后查看發的請求。可以看到下面那張圖使我們首次訪問服務器時發送的請求,可以看到服務器返回的響應中有Set-Cookie
字段。而里面的key=value
值正是我們服務器中設置的值。
接下來我們再次刷新這個頁面可以看到在請求體中已經設置了Cookie
字段,并且將我們的值也帶過去了。這樣服務器就能夠根據Cookie
中的值記住我們的信息了。
接下來我們換一個請求呢?是不是Cookie
也會帶過去呢?接下來我們輸入路徑http://localhost:8005
請求。我們可以看到Cookie
字段還是被帶過去了。
那么瀏覽器的Cookie
是存放在哪呢?如果是使用的是Chrome
瀏覽器的話,那么可以按照下面步驟。
然后可以根據域名進行搜索所管理的Cookie
數據。所以是瀏覽器替你管理了Cookie
的數據,如果此時你換成了Firefox
等其他的瀏覽器,因為Cookie
剛才是存儲在Chrome
里面的,所以服務器又蒙圈了,不知道你是誰,就會給Firefox
再次貼上小紙條。
Cookie中的參數設置
說到這里,應該知道了Cookie
就是服務器委托瀏覽器存儲在客戶端里的一些數據,而這些數據通常都會記錄用戶的關鍵識別信息。
所以Cookie
需要用一些其他的手段用來保護,防止外泄或者竊取,這些手段就是Cookie
的屬性。
參數名 |
作用 |
后端設置方法 |
Max-Age
|
設置cookie的過期時間,單位為秒
|
cookie.setMaxAge(10)
|
Domain
|
指定了Cookie所屬的域名
|
cookie.setDomain("")
|
Path
|
指定了Cookie所屬的路徑
|
cookie.setPath("");
|
HttpOnly
|
告訴瀏覽器此Cookie只能靠瀏覽器Http協議傳輸,禁止其他方式訪問
|
cookie.setHttpOnly(true)
|
Secure
|
告訴瀏覽器此Cookie只能在Https安全協議中傳輸,如果是Http則禁止傳輸
|
cookie.setSecure(true)
|
下面我就簡單演示一下這幾個參數的用法及現象。
Path
設置為cookie.setPath("/testCookies")
,接下來我們訪問http://localhost:8005/testCookies
,我們可以看到在左邊和我們指定的路徑是一樣的,所以Cookie
才在請求頭中出現,接下來我們訪問http://localhost:8005
,我們發現沒有Cookie
字段了,這就是Path
控制的路徑。
Domain
設置為cookie.setDomain("localhost")
,接下來我們訪問http://localhost:8005/testCookies
我們發現下圖中左邊的是有Cookie
的字段的,但是我們訪問http://172.16.42.81:8005/testCookies
,看下圖的右邊可以看到沒有Cookie
的字段了。這就是Domain
控制的域名發送Cookie
。
接下來的幾個參數就不一一演示了,相信到這里大家應該對Cookie
有一些了解了。
Session
- Cookie是存儲在客戶端方,Session是存儲在服務端方,客戶端只存儲SessionId
在上面我們了解了什么是Cookie
,既然瀏覽器已經通過Cookie
實現了有狀態這一需求,那么為什么又來了一個Session
呢?這里我們想象一下,如果將賬戶的一些信息都存入Cookie
中的話,一旦信息被攔截,那么我們所有的賬戶信息都會丟失掉。所以就出現了Session
,在一次會話中將重要信息保存在Session
中,瀏覽器只記錄SessionId
一個SessionId
對應一次會話請求。
@RequestMapping("/testSession")
@ResponseBody
public String testSession(HttpSession session){
session.setAttribute("testSession","this is my session");
return "testSession";
}
@RequestMapping("/testGetSession")
@ResponseBody
public String testGetSession(HttpSession session){
Object testSession = session.getAttribute("testSession");
return String.valueOf(testSession);
}
這里我們寫一個新的方法來測試Session
是如何產生的,我們在請求參數中加上HttpSession session
,然后再瀏覽器中輸入http:
此時我們訪問路徑http://localhost:8005/testGetSession
,發現得到了我們上面存儲在Session
中的信息。那么Session
什么時候過期呢?
既然我們知道了Session
是在服務端進行管理的,那么或許你們看到這有幾個疑問,Session
是在在哪創建的?Session
是存儲在什么數據結構中?接下來帶領大家一起看一下Session
是如何被管理的。
Session
的管理是在容器中被管理的,什么是容器呢?Tomcat
、Jetty
等都是容器。接下來我們拿最常用的Tomcat
為例來看下Tomcat
是如何管理Session
的。在ManageBase
的createSession
是用來創建Session
的。
@Override
public Session createSession(String sessionId) {
到此我們明白了Session
是如何創建出來的,創建出來后Session
會被保存到一個ConcurrentHashMap
中。可以看StandardSession
類。
protected Map<string, session> www.jintianxuesha.com sessions = new ConcurrentHashMap<>();
到這里大家應該對Session
有簡單的了解了。
Session是存儲在Tomcat的容器中,所以如果后端機器是多臺的話,因此多個機器間是無法共享Session的,此時可以使用Spring提供的分布式Session的解決方案,是將Session放在了Redis中。
Token
Session
是將要驗證的信息存儲在服務端,并以SessionId
和數據進行對應,SessionId
由客戶端存儲,在請求時將SessionId
也帶過去,因此實現了狀態的對應。而Token
是在服務端將用戶信息經過Base64Url編碼過后傳給在客戶端,每次用戶請求的時候都會帶上這一段信息,因此服務端拿到此信息進行解密后就知道此用戶是誰了,這個方法叫做JWT(Json Web Token)。
> Token
相比較于Session
的優點在于,當后端系統有多臺時,由于是客戶端訪問時直接帶著數據,因此無需做共享數據的操作。
Token的優點
-
簡潔:可以通過URL
,POST
參數或者是在HTTP
頭參數發送,因為數據量小,傳輸速度也很快
-
自包含:由于串包含了用戶所需要的信息,避免了多次查詢數據庫
-
因為Token是以Json的形式保存在客戶端的,所以JWT是跨語言的
-
不需要在服務端保存會話信息,特別適用于分布式微服務
JWT的結構
實際的JWT大概長下面的這樣,它是一個很長的字符串,中間用.
分割成三部分
JWT是有三部分組成的
Header
是一個Json對象,描述JWT的元數據,通常是下面這樣子的
{
"alg": "HS256",
"typ": "JWT"
}
上面代碼中,alg屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256);typ屬性表示這個令牌(token)的類型(type),JWT 令牌統一寫為JWT。最后,將上面的 JSON 對象使用 Base64URL 算法轉成字符串。
> JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個字符+、/和=,在 URL 里面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。
Payload
Payload部分也是一個Json對象,用來存放實際需要傳輸的數據,JWT官方規定了下面幾個官方的字段供選用。
當然除了官方提供的這幾個字段我們也能夠自己定義私有字段,下面就是一個例子
{
"name": "xiaoMing",
"age": 14
}
默認情況下JWT是不加密的,任何人只要在網上進行Base64解碼就可以讀到信息,所以一般不要將秘密信息放在這個部分。這個Json對象也要用Base64URL
算法轉成字符串
Signature
Signature部分是對前面的兩部分的數據進行簽名,防止數據篡改。
首先需要定義一個秘鑰,這個秘鑰只有服務器才知道,不能泄露給用戶,然后使用Header中指定的簽名算法(默認情況是HMAC SHA256),算出簽名以后將Header、Payload、Signature三部分拼成一個字符串,每個部分用.
分割開來,就可以返給用戶了。
> HS256可以使用單個密鑰為給定的數據樣本創建簽名。當消息與簽名一起傳輸時,接收方可以使用相同的密鑰來驗證簽名是否與消息匹配。
Java中如何使用Token
上面我們介紹了關于JWT的一些概念,接下來如何使用呢?首先在項目中引入Jar包
compile('io.jsonwebtoken:jjwt:0.9.0')
然后編碼如下
發現輸出的Token如下
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwibmFtZSI6InhpYW9NaW5nIiwiYWdlIjoxNH0.3KOWQ-oYvBSzslW5vgB1D-JpCwS-HkWGyWdXCP5l3Ko
此時在網上隨便找個Base64解碼的網站就能將信息解碼出來
總結
相信大家看到這應該對Cookie
、Session
、Token
有一定的了解了,接下來再回顧一下重要的知識點。
-
Cookie是存儲在客戶端的
-
Session是存儲在服務端的,可以理解為一個狀態列表。擁有一個唯一會話標識SessionId
。可以根據SessionId
在服務端查詢到存儲的信息。
-
Session會引發一個問題,即后端多臺機器時Session共享的問題,解決方案可以使用Spring提供的框架。
-
Token類似一個令牌,無狀態的,服務端所需的信息被Base64編碼后放到Token中,服務器可以直接解碼出其中的數據。