Cookie
-
瀏覽器第一次訪問服務(wù)端時(shí),,服務(wù)器此時(shí)肯定不知道他的身份,所以創(chuàng)建一個(gè)獨(dú)特的身份標(biāo)識(shí)數(shù)據(jù),,格式為key=value
,放入到Set-Cookie
字段里,,隨著響應(yīng)報(bào)文發(fā)給瀏覽器,。
-
瀏覽器看到有Set-Cookie
字段以后就知道這是服務(wù)器給的身份標(biāo)識(shí),于是就保存起來,,下次請(qǐng)求時(shí)會(huì)自動(dòng)將此key=value
值放入到Cookie
字段中發(fā)給服務(wù)端,。
-
服務(wù)端收到請(qǐng)求報(bào)文后,發(fā)現(xiàn)Cookie
字段中有值,,就能根據(jù)此值識(shí)別用戶的身份然后提供個(gè)性化的服務(wù),。
接下來我們用代碼演示一下服務(wù)器是如何生成,我們自己搭建一個(gè)后臺(tái)服務(wù)器,,這里我用的是SpringBoot搭建的,,并且寫入SpringMVC的代碼如下:
@RequestMapping("/testCookies")
public String cookies(HttpServletResponse response){
response.addCookie(new Cookie("testUser","xxxx"));
return "cookies";
}
項(xiàng)目啟動(dòng)以后我們輸入路徑http://localhost:8005/testCookies
,然后查看發(fā)的請(qǐng)求,??梢钥吹较旅婺菑垐D使我們首次訪問服務(wù)器時(shí)發(fā)送的請(qǐng)求,可以看到服務(wù)器返回的響應(yīng)中有Set-Cookie
字段,。而里面的key=value
值正是我們服務(wù)器中設(shè)置的值,。
接下來我們?cè)俅嗡⑿逻@個(gè)頁面可以看到在請(qǐng)求體中已經(jīng)設(shè)置了Cookie
字段,并且將我們的值也帶過去了,。這樣服務(wù)器就能夠根據(jù)Cookie
中的值記住我們的信息了,。
接下來我們換一個(gè)請(qǐng)求呢?是不是Cookie
也會(huì)帶過去呢,?接下來我們輸入路徑http://localhost:8005
請(qǐng)求,。我們可以看到Cookie
字段還是被帶過去了,。
那么瀏覽器的Cookie
是存放在哪呢,?如果是使用的是Chrome
瀏覽器的話,,那么可以按照下面步驟。
-
在計(jì)算機(jī)打開Chrome
-
在右上角,,一次點(diǎn)擊更多
圖標(biāo)->設(shè)置
-
在底部,,點(diǎn)擊高級(jí)
-
在隱私設(shè)置和安全性
下方,點(diǎn)擊網(wǎng)站設(shè)置
-
依次點(diǎn)擊Cookie
->查看所有Cookie和網(wǎng)站數(shù)據(jù)
然后可以根據(jù)域名進(jìn)行搜索所管理的Cookie
數(shù)據(jù),。所以是瀏覽器替你管理了Cookie
的數(shù)據(jù),,如果此時(shí)你換成了Firefox
等其他的瀏覽器,因?yàn)?code style="box-sizing: border-box; outline: 0px; --tw-shadow:0 0 #0000 ; --tw-ring-inset:var(--tw-empty, ); --tw-ring-offset-width:0px; --tw-ring-offset-color:#fff; --tw-ring-color:rgba(66, 153, 225, 0.5); --tw-ring-offset-shadow:0 0 #0000; --tw-ring-shadow:0 0 #0000 ; border-radius: 4px; overflow-wrap: break-word;">Cookie剛才是存儲(chǔ)在Chrome
里面的,,所以服務(wù)器又蒙圈了,,不知道你是誰,,就會(huì)給Firefox
再次貼上小紙條,。
Cookie中的參數(shù)設(shè)置
說到這里,,應(yīng)該知道了Cookie
就是服務(wù)器委托瀏覽器存儲(chǔ)在客戶端里的一些數(shù)據(jù),而這些數(shù)據(jù)通常都會(huì)記錄用戶的關(guān)鍵識(shí)別信息,。
所以Cookie
需要用一些其他的手段用來保護(hù),,防止外泄或者竊取,這些手段就是Cookie
的屬性,。
參數(shù)名 |
作用 |
后端設(shè)置方法 |
Max-Age
|
設(shè)置cookie的過期時(shí)間,,單位為秒
|
cookie.setMaxAge(10)
|
Domain
|
指定了Cookie所屬的域名
|
cookie.setDomain("")
|
Path
|
指定了Cookie所屬的路徑
|
cookie.setPath("");
|
HttpOnly
|
告訴瀏覽器此Cookie只能靠瀏覽器Http協(xié)議傳輸,禁止其他方式訪問
|
cookie.setHttpOnly(true)
|
Secure
|
告訴瀏覽器此Cookie只能在Https安全協(xié)議中傳輸,如果是Http則禁止傳輸
|
cookie.setSecure(true)
|
下面我就簡(jiǎn)單演示一下這幾個(gè)參數(shù)的用法及現(xiàn)象。
Path
設(shè)置為cookie.setPath("/testCookies")
,,接下來我們?cè)L問http://localhost:8005/testCookies
,,我們可以看到在左邊和我們指定的路徑是一樣的,所以Cookie
才在請(qǐng)求頭中出現(xiàn),,接下來我們?cè)L問http://localhost:8005
,,我們發(fā)現(xiàn)沒有Cookie
字段了,這就是Path
控制的路徑,。
Domain
設(shè)置為cookie.setDomain("localhost")
,,接下來我們?cè)L問http://localhost:8005/testCookies
我們發(fā)現(xiàn)下圖中左邊的是有Cookie
的字段的,但是我們?cè)L問http://172.16.42.81:8005/testCookies
,,看下圖的右邊可以看到?jīng)]有Cookie
的字段了,。這就是Domain
控制的域名發(fā)送Cookie
。
接下來的幾個(gè)參數(shù)就不一一演示了,,相信到這里大家應(yīng)該對(duì)Cookie
有一些了解了,。
Session
- Cookie是存儲(chǔ)在客戶端方,Session是存儲(chǔ)在服務(wù)端方,,客戶端只存儲(chǔ)SessionId
在上面我們了解了什么是Cookie
,,既然瀏覽器已經(jīng)通過Cookie
實(shí)現(xiàn)了有狀態(tài)這一需求,那么為什么又來了一個(gè)Session
呢,?這里我們想象一下,,如果將賬戶的一些信息都存入Cookie
中的話,一旦信息被攔截,,那么我們所有的賬戶信息都會(huì)丟失掉,。所以就出現(xiàn)了Session
,在一次會(huì)話中將重要信息保存在Session
中,,瀏覽器只記錄SessionId
一個(gè)SessionId
對(duì)應(yīng)一次會(huì)話請(qǐng)求,。
@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);
}
這里我們寫一個(gè)新的方法來測(cè)試Session
是如何產(chǎn)生的,我們?cè)谡?qǐng)求參數(shù)中加上HttpSession session
,,然后再瀏覽器中輸入http:
此時(shí)我們?cè)L問路徑http://localhost:8005/testGetSession
,,發(fā)現(xiàn)得到了我們上面存儲(chǔ)在Session
中的信息,。那么Session
什么時(shí)候過期呢?
-
客戶端:和Cookie
過期一致,,如果沒設(shè)置,,默認(rèn)是關(guān)了瀏覽器就沒了,即再打開瀏覽器的時(shí)候初次請(qǐng)求頭中是沒有SessionId
了,。
-
服務(wù)端:服務(wù)端的過期是真的過期,,即服務(wù)器端的Session
存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)多久不可用了,默認(rèn)是30分鐘,。
既然我們知道了Session
是在服務(wù)端進(jìn)行管理的,,那么或許你們看到這有幾個(gè)疑問,Session
是在在哪創(chuàng)建的,?Session
是存儲(chǔ)在什么數(shù)據(jù)結(jié)構(gòu)中,?接下來帶領(lǐng)大家一起看一下Session
是如何被管理的。
Session
的管理是在容器中被管理的,,什么是容器呢,?Tomcat
、Jetty
等都是容器,。接下來我們拿最常用的Tomcat
為例來看下Tomcat
是如何管理Session
的,。在ManageBase
的createSession
是用來創(chuàng)建Session
的。
@Override
public Session createSession(String sessionId) {
到此我們明白了Session
是如何創(chuàng)建出來的,,創(chuàng)建出來后Session
會(huì)被保存到一個(gè)ConcurrentHashMap
中??梢钥?code style="box-sizing: border-box; outline: 0px; --tw-shadow:0 0 #0000 ; --tw-ring-inset:var(--tw-empty, ); --tw-ring-offset-width:0px; --tw-ring-offset-color:#fff; --tw-ring-color:rgba(66, 153, 225, 0.5); --tw-ring-offset-shadow:0 0 #0000; --tw-ring-shadow:0 0 #0000 ; border-radius: 4px; overflow-wrap: break-word;">StandardSession類,。
protected Map<string, session> www.jintianxuesha.com sessions = new ConcurrentHashMap<>();
到這里大家應(yīng)該對(duì)Session
有簡(jiǎn)單的了解了。
Session是存儲(chǔ)在Tomcat的容器中,,所以如果后端機(jī)器是多臺(tái)的話,,因此多個(gè)機(jī)器間是無法共享Session的,此時(shí)可以使用Spring提供的分布式Session的解決方案,,是將Session放在了Redis中,。
Token
Session
是將要驗(yàn)證的信息存儲(chǔ)在服務(wù)端,并以SessionId
和數(shù)據(jù)進(jìn)行對(duì)應(yīng),,SessionId
由客戶端存儲(chǔ),,在請(qǐng)求時(shí)將SessionId
也帶過去,因此實(shí)現(xiàn)了狀態(tài)的對(duì)應(yīng)。而Token
是在服務(wù)端將用戶信息經(jīng)過Base64Url編碼過后傳給在客戶端,,每次用戶請(qǐng)求的時(shí)候都會(huì)帶上這一段信息,,因此服務(wù)端拿到此信息進(jìn)行解密后就知道此用戶是誰了,這個(gè)方法叫做JWT(Json Web Token),。
> Token
相比較于Session
的優(yōu)點(diǎn)在于,,當(dāng)后端系統(tǒng)有多臺(tái)時(shí),,由于是客戶端訪問時(shí)直接帶著數(shù)據(jù),,因此無需做共享數(shù)據(jù)的操作。
Token的優(yōu)點(diǎn)
-
簡(jiǎn)潔:可以通過URL
,POST
參數(shù)或者是在HTTP
頭參數(shù)發(fā)送,,因?yàn)閿?shù)據(jù)量小,,傳輸速度也很快
-
自包含:由于串包含了用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫
-
因?yàn)門oken是以Json的形式保存在客戶端的,,所以JWT是跨語言的
-
不需要在服務(wù)端保存會(huì)話信息,,特別適用于分布式微服務(wù)
JWT的結(jié)構(gòu)
實(shí)際的JWT大概長下面的這樣,它是一個(gè)很長的字符串,,中間用.
分割成三部分
JWT是有三部分組成的
Header
是一個(gè)Json對(duì)象,,描述JWT的元數(shù)據(jù),通常是下面這樣子的
{
"alg": "HS256",
"typ": "JWT"
}
上面代碼中,,alg屬性表示簽名的算法(algorithm),,默認(rèn)是 HMAC SHA256(寫成 HS256);typ屬性表示這個(gè)令牌(token)的類型(type),,JWT 令牌統(tǒng)一寫為JWT,。最后,將上面的 JSON 對(duì)象使用 Base64URL 算法轉(zhuǎn)成字符串,。
> JWT 作為一個(gè)令牌(token),,有些場(chǎng)合可能會(huì)放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個(gè)字符+,、/和=,,在 URL 里面有特殊含義,所以要被替換掉:=被省略,、+替換成-,,/替換成_ 。這就是 Base64URL 算法,。
Payload
Payload部分也是一個(gè)Json對(duì)象,,用來存放實(shí)際需要傳輸?shù)臄?shù)據(jù),JWT官方規(guī)定了下面幾個(gè)官方的字段供選用,。
-
iss (issuer):簽發(fā)人
-
exp (expiration time):過期時(shí)間
-
sub (subject):主題
-
aud (audience):受眾
-
nbf (Not Before):生效時(shí)間
-
iat (Issued At):簽發(fā)時(shí)間
-
jti (JWT ID):編號(hào)
當(dāng)然除了官方提供的這幾個(gè)字段我們也能夠自己定義私有字段,,下面就是一個(gè)例子
{
"name": "xiaoMing",
"age": 14
}
默認(rèn)情況下JWT是不加密的,任何人只要在網(wǎng)上進(jìn)行Base64解碼就可以讀到信息,,所以一般不要將秘密信息放在這個(gè)部分,。這個(gè)Json對(duì)象也要用Base64URL
算法轉(zhuǎn)成字符串
Signature
Signature部分是對(duì)前面的兩部分的數(shù)據(jù)進(jìn)行簽名,,防止數(shù)據(jù)篡改。
首先需要定義一個(gè)秘鑰,,這個(gè)秘鑰只有服務(wù)器才知道,,不能泄露給用戶,然后使用Header中指定的簽名算法(默認(rèn)情況是HMAC SHA256),,算出簽名以后將Header,、Payload、Signature三部分拼成一個(gè)字符串,,每個(gè)部分用.
分割開來,,就可以返給用戶了。
> HS256可以使用單個(gè)密鑰為給定的數(shù)據(jù)樣本創(chuàng)建簽名,。當(dāng)消息與簽名一起傳輸時(shí),,接收方可以使用相同的密鑰來驗(yàn)證簽名是否與消息匹配。
Java中如何使用Token
上面我們介紹了關(guān)于JWT的一些概念,,接下來如何使用呢,?首先在項(xiàng)目中引入Jar包
compile('io.jsonwebtoken:jjwt:0.9.0')
然后編碼如下
發(fā)現(xiàn)輸出的Token如下
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwibmFtZSI6InhpYW9NaW5nIiwiYWdlIjoxNH0.3KOWQ-oYvBSzslW5vgB1D-JpCwS-HkWGyWdXCP5l3Ko
此時(shí)在網(wǎng)上隨便找個(gè)Base64解碼的網(wǎng)站就能將信息解碼出來
總結(jié)
相信大家看到這應(yīng)該對(duì)Cookie
,、Session
,、Token
有一定的了解了,接下來再回顧一下重要的知識(shí)點(diǎn),。
-
Cookie是存儲(chǔ)在客戶端的
-
Session是存儲(chǔ)在服務(wù)端的,,可以理解為一個(gè)狀態(tài)列表。擁有一個(gè)唯一會(huì)話標(biāo)識(shí)SessionId
,??梢愿鶕?jù)SessionId
在服務(wù)端查詢到存儲(chǔ)的信息。
-
Session會(huì)引發(fā)一個(gè)問題,,即后端多臺(tái)機(jī)器時(shí)Session共享的問題,,解決方案可以使用Spring提供的框架。
-
Token類似一個(gè)令牌,,無狀態(tài)的,,服務(wù)端所需的信息被Base64編碼后放到Token中,服務(wù)器可以直接解碼出其中的數(shù)據(jù),。