一文徹底搞懂Cookie妓柜、Session、Token到底是什么

前言

在了解這三個概念之前我們先要了解HTTP是無狀態(tài)的Web服務器涯穷,什么是無狀態(tài)呢棍掐?就像上面夏洛特煩惱中經(jīng)典的一幕對話一樣,一次對話完成后下一次對話完全不知道上一次對話發(fā)生了什么拷况。如果在Web服務器中只是用來管理靜態(tài)文件還好說作煌,對方是誰并不重要掘殴,把文件從磁盤中讀取出來發(fā)出去即可。但是隨著網(wǎng)絡的不斷發(fā)展粟誓,比如電商中的購物車只有記住了用戶的身份才能夠執(zhí)行接下來的一系列動作奏寨。所以此時就需要我們無狀態(tài)的服務器記住一些事情。

那么Web服務器是如何記住一些事情呢鹰服?既然Web服務器記不住東西病瞳,那么我們就在外部想辦法記住,相當于服務器給每個客戶端都貼上了一個小紙條悲酷。上面記錄了服務器給我們返回的一些信息套菜。然后服務器看到這張小紙條就知道我們是誰了。那么Cookie是誰產(chǎn)生的呢设易?Cookies是由服務器產(chǎn)生的逗柴。接下來我們描述一下Cookie產(chǎn)生的過程。

瀏覽器第一次訪問服務端時顿肺,服務器此時肯定不知道他的身份戏溺,所以創(chuàng)建一個獨特的身份標識數(shù)據(jù),格式為key=value屠尊,放入到Set-Cookie字段里于购,隨著響應報文發(fā)給瀏覽器。

瀏覽器看到有Set-Cookie字段以后就知道這是服務器給的身份標識知染,于是就保存起來,下次請求時會自動將此key=value值放入到Cookie字段中發(fā)給服務端斑胜。

服務端收到請求報文后控淡,發(fā)現(xiàn)Cookie字段中有值,就能根據(jù)此值識別用戶的身份然后提供個性化的服務止潘。

接下來我們用代碼演示一下服務器是如何生成掺炭,我們自己搭建一個后臺服務器,這里我用的是SpringBoot搭建的凭戴,并且寫入SpringMVC的代碼如下涧狮。

@RequestMapping("/testCookies")

publicStringcookies(HttpServletResponse response){

response.addCookie(newCookie("testUser","xxxx"));

return"cookies";

}

項目啟動以后我們輸入路徑http://localhost:8005/testCookies,然后查看發(fā)的請求么夫≌咴可以看到下面那張圖使我們首次訪問服務器時發(fā)送的請求,可以看到服務器返回的響應中有Set-Cookie字段档痪。而里面的key=value值正是我們服務器中設置的值涉枫。

接下來我們再次刷新這個頁面可以看到在請求體中已經(jīng)設置了Cookie字段,并且將我們的值也帶過去了腐螟。這樣服務器就能夠根據(jù)Cookie中的值記住我們的信息了愿汰。

接下來我們換一個請求呢困后?是不是Cookie也會帶過去呢?接下來我們輸入路徑http://localhost:8005請求衬廷。我們可以看到Cookie字段還是被帶過去了摇予。

那么瀏覽器的Cookie是存放在哪呢?如果是使用的是Chrome瀏覽器的話吗跋,那么可以按照下面步驟侧戴。

在計算機打開Chrome

在右上角,一次點擊更多圖標->設置

在底部小腊,點擊高級

在隱私設置和安全性下方救鲤,點擊網(wǎng)站設置

依次點擊Cookie->查看所有Cookie和網(wǎng)站數(shù)據(jù)

然后可以根據(jù)域名進行搜索所管理的Cookie數(shù)據(jù)。所以是瀏覽器替你管理了Cookie的數(shù)據(jù)秩冈,如果此時你換成了Firefox等其他的瀏覽器本缠,因為Cookie剛才是存儲在Chrome里面的,所以服務器又蒙圈了入问,不知道你是誰丹锹,就會給Firefox再次貼上小紙條。

Cookie中的參數(shù)設置

說到這里芬失,應該知道了Cookie就是服務器委托瀏覽器存儲在客戶端里的一些數(shù)據(jù)楣黍,而這些數(shù)據(jù)通常都會記錄用戶的關鍵識別信息。所以Cookie需要用一些其他的手段用來保護棱烂,防止外泄或者竊取租漂,這些手段就是Cookie的屬性。

參數(shù)名作用后端設置方法

Max-Age設置cookie的過期時間颊糜,單位為秒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)

下面我就簡單演示一下這幾個參數(shù)的用法及現(xiàn)象哩治。

Path

設置為cookie.setPath("/testCookies"),接下來我們訪問http://localhost:8005/testCookies衬鱼,我們可以看到在左邊和我們指定的路徑是一樣的业筏,所以Cookie才在請求頭中出現(xiàn),接下來我們訪問http://localhost:8005鸟赫,我們發(fā)現(xiàn)沒有Cookie字段了蒜胖,這就是Path控制的路徑。

Domain

設置為cookie.setDomain("localhost")抛蚤,接下來我們訪問http://localhost:8005/testCookies我們發(fā)現(xiàn)下圖中左邊的是有Cookie的字段的台谢,但是我們訪問http://172.16.42.81:8005/testCookies,看下圖的右邊可以看到?jīng)]有Cookie的字段了岁经。這就是Domain控制的域名發(fā)送Cookie对碌。

接下來的幾個參數(shù)就不一一演示了,相信到這里大家應該對Cookie有一些了解了蒿偎。

Session

Cookie是存儲在客戶端方朽们,Session是存儲在服務端方怀读,客戶端只存儲SessionId

在上面我們了解了什么是Cookie,既然瀏覽器已經(jīng)通過Cookie實現(xiàn)了有狀態(tài)這一需求骑脱,那么為什么又來了一個Session呢菜枷?這里我們想象一下,如果將賬戶的一些信息都存入Cookie中的話叁丧,一旦信息被攔截啤誊,那么我們所有的賬戶信息都會丟失掉。所以就出現(xiàn)了Session拥娄,在一次會話中將重要信息保存在Session中蚊锹,瀏覽器只記錄SessionId一個SessionId對應一次會話請求。

@RequestMapping("/testSession")

@ResponseBody

publicString testSession(HttpSession session){

session.setAttribute("testSession","this is my session");

return"testSession";

}

@RequestMapping("/testGetSession")

@ResponseBody

publicString testGetSession(HttpSession session){

Object testSession = session.getAttribute("testSession");

returnString.valueOf(testSession);

}

這里我們寫一個新的方法來測試Session是如何產(chǎn)生的稚瘾,我們在請求參數(shù)中加上HttpSession session牡昆,然后再瀏覽器中輸入http://localhost:8005/testSession進行訪問可以看到在服務器的返回頭中在Cookie中生成了一個SessionId。然后瀏覽器記住此SessionId下次訪問時可以帶著此Id摊欠,然后就能根據(jù)此Id找到存儲在服務端的信息了丢烘。

此時我們訪問路徑http://localhost:8005/testGetSession,發(fā)現(xiàn)得到了我們上面存儲在Session中的信息些椒。那么Session什么時候過期呢播瞳?

客戶端:和Cookie過期一致,如果沒設置免糕,默認是關了瀏覽器就沒了赢乓,即再打開瀏覽器的時候初次請求頭中是沒有SessionId了。

服務端:服務端的過期是真的過期石窑,即服務器端的Session存儲的數(shù)據(jù)結(jié)構(gòu)多久不可用了牌芋,默認是30分鐘。

既然我們知道了Session是在服務端進行管理的尼斧,那么或許你們看到這有幾個疑問,Session是在在哪創(chuàng)建的试吁?Session是存儲在什么數(shù)據(jù)結(jié)構(gòu)中棺棵?接下來帶領大家一起看一下Session是如何被管理的。

Session的管理是在容器中被管理的熄捍,什么是容器呢烛恤?Tomcat、Jetty等都是容器余耽。接下來我們拿最常用的Tomcat為例來看下Tomcat是如何管理Session的缚柏。在ManageBase的createSession是用來創(chuàng)建Session的。

@Override

publicSessioncreateSession(String sessionId){

//首先判斷Session數(shù)量是不是到了最大值碟贾,最大Session數(shù)可以通過參數(shù)設置

if((maxActiveSessions >=0) &&

(getActiveSessions() >= maxActiveSessions)) {

rejectedSessions++;

thrownewTooManyActiveSessionsException(

sm.getString("managerBase.createSession.ise"),

maxActiveSessions);

}

// 重用或者創(chuàng)建一個新的Session對象币喧,請注意在Tomcat中就是StandardSession

// 它是HttpSession的具體實現(xiàn)類轨域,而HttpSession是Servlet規(guī)范中定義的接口

Session session = createEmptySession();

// 初始化新Session的值

session.setNew(true);

session.setValid(true);

session.setCreationTime(System.currentTimeMillis());

// 設置Session過期時間是30分鐘

session.setMaxInactiveInterval(getContext().getSessionTimeout() *60);

String id = sessionId;

if(id ==null) {

id = generateSessionId();

}

session.setId(id);// 這里會將Session添加到ConcurrentHashMap中

sessionCounter++;

//將創(chuàng)建時間添加到LinkedList中,并且把最先添加的時間移除

//主要還是方便清理過期Session

SessionTiming timing =newSessionTiming(session.getCreationTime(),0);

synchronized(sessionCreationTiming) {

sessionCreationTiming.add(timing);

sessionCreationTiming.poll();

}

returnsession

}

到此我們明白了Session是如何創(chuàng)建出來的杀餐,創(chuàng)建出來后Session會被保存到一個ConcurrentHashMap中干发。可以看StandardSession類史翘。

protectedMap sessions =newConcurrentHashMap<>();

到這里大家應該對Session有簡單的了解了枉长。

> Session是存儲在Tomcat的容器中,所以如果后端機器是多臺的話琼讽,因此多個機器間是無法共享Session的必峰,此時可以使用Spring提供的分布式Session的解決方案,是將Session放在了Redis中钻蹬。

Token

Session是將要驗證的信息存儲在服務端吼蚁,并以SessionId和數(shù)據(jù)進行對應,SessionId由客戶端存儲脉让,在請求時將SessionId也帶過去桂敛,因此實現(xiàn)了狀態(tài)的對應。而Token是在服務端將用戶信息經(jīng)過Base64Url編碼過后傳給在客戶端溅潜,每次用戶請求的時候都會帶上這一段信息术唬,因此服務端拿到此信息進行解密后就知道此用戶是誰了,這個方法叫做JWT(Json Web Token)滚澜。

Token相比較于Session的優(yōu)點在于粗仓,當后端系統(tǒng)有多臺時,由于是客戶端訪問時直接帶著數(shù)據(jù)设捐,因此無需做共享數(shù)據(jù)的操作借浊。

Token的優(yōu)點

簡潔:可以通過URL,POST參數(shù)或者是在HTTP頭參數(shù)發(fā)送,因為數(shù)據(jù)量小萝招,傳輸速度也很快

自包含:由于串包含了用戶所需要的信息蚂斤,避免了多次查詢數(shù)據(jù)庫

因為Token是以Json的形式保存在客戶端的,所以JWT是跨語言的

不需要在服務端保存會話信息槐沼,特別適用于分布式微服務

JWT的結(jié)構(gòu)

實際的JWT大概長下面的這樣曙蒸,它是一個很長的字符串,中間用.分割成三部分

JWT是有三部分組成的

Header

是一個Json對象岗钩,描述JWT的元數(shù)據(jù)纽窟,通常是下面這樣子的

{

"alg":"HS256",

"typ":"JWT"

}

上面代碼中,alg屬性表示簽名的算法(algorithm)兼吓,默認是 HMAC SHA256(寫成 HS256)臂港;typ屬性表示這個令牌(token)的類型(type),JWT 令牌統(tǒng)一寫為JWT。最后审孽,將上面的 JSON 對象使用 Base64URL 算法轉(zhuǎn)成字符串县袱。

JWT 作為一個令牌(token),有些場合可能會放到 URL(比如 api.example.com/?token=xxx)瓷胧。Base64 有三個字符+显拳、/和=,在 URL 里面有特殊含義搓萧,所以要被替換掉:=被省略杂数、+替換成-,/替換成_ 瘸洛。這就是 Base64URL 算法揍移。

Payload

Payload部分也是一個Json對象,用來存放實際需要傳輸?shù)臄?shù)據(jù)反肋,JWT官方規(guī)定了下面幾個官方的字段供選用那伐。

iss (issuer):簽發(fā)人

exp (expiration time):過期時間

sub (subject):主題

aud (audience):受眾

nbf (Not Before):生效時間

iat (Issued At):簽發(fā)時間

jti (JWT ID):編號

當然除了官方提供的這幾個字段我們也能夠自己定義私有字段,下面就是一個例子

{

"name":"xiaoMing",

"age":14

}

默認情況下JWT是不加密的石蔗,任何人只要在網(wǎng)上進行Base64解碼就可以讀到信息罕邀,所以一般不要將秘密信息放在這個部分。這個Json對象也要用Base64URL 算法轉(zhuǎn)成字符串

Signature

Signature部分是對前面的兩部分的數(shù)據(jù)進行簽名养距,防止數(shù)據(jù)篡改诉探。

首先需要定義一個秘鑰,這個秘鑰只有服務器才知道棍厌,不能泄露給用戶肾胯,然后使用Header中指定的簽名算法(默認情況是HMAC SHA256),算出簽名以后將Header耘纱、Payload敬肚、Signature三部分拼成一個字符串,每個部分用.分割開來束析,就可以返給用戶了艳馒。

HS256可以使用單個密鑰為給定的數(shù)據(jù)樣本創(chuàng)建簽名。當消息與簽名一起傳輸時员寇,接收方可以使用相同的密鑰來驗證簽名是否與消息匹配弄慰。

Java 中如何使用 Token

上面我們介紹了關于 JWT 的一些概念,接下來如何使用呢丁恭?首先在項目中引入 jar 包曹动。

compile('io.jsonwebtoken:jjwt:0.9.0')

然后編碼如下斋日。

// 簽名算法 牲览,將對token進行簽名

SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

// 通過秘鑰簽名JWT

byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("SECRET");

Key signingKey =newSecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

Map claimsMap =newHashMap<>();

claimsMap.put("name","xiaoMing");

claimsMap.put("age",14);

JwtBuilder builderWithSercet = Jwts.builder()

.setSubject("subject")

.setIssuer("issuer")

.addClaims(claimsMap)

.signWith(signatureAlgorithm, signingKey);

System.out.printf(builderWithSercet.compact());

發(fā)現(xiàn)輸出的 Token 如下。

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwibmFtZSI6InhpYW9NaW5nIiwiYWdlIjoxNH0.3KOWQ-oYvBSzslW5vgB1D-JpCwS-HkWGyWdXCP5l3Ko

此時在網(wǎng)上隨便找個 Base64 解碼的網(wǎng)站就能將信息解碼出來。

總結(jié)

相信大家看到這應該對 Cookie第献、Session贡必、Token 有一定的了解了,接下來再回顧一下重要的知識點庸毫。

Cookie 是存儲在客戶端的

Session 是存儲在服務端的仔拟,可以理解為一個狀態(tài)列表。擁有一個唯一會話標識 SessionId飒赃±ǎ可以根據(jù) SessionId 在服務端查詢到存儲的信息。

Session 會引發(fā)一個問題载佳,即后端多臺機器時 Session 共享的問題炒事,解決方案可以使用Spring提供的框架。

Token 類似一個令牌蔫慧,無狀態(tài)的挠乳,服務端所需的信息被 Base64 編碼后放到 Token 中,服務器可以直接解碼出其中的數(shù)據(jù)姑躲。

轉(zhuǎn)載自https://my.oschina.net/u/4030990/blog/3136476

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末睡扬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子黍析,更是在濱河造成了極大的恐慌卖怜,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橄仍,死亡現(xiàn)場離奇詭異韧涨,居然都是意外死亡,警方通過查閱死者的電腦和手機侮繁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娩贷,“玉大人,你說我怎么就攤上這事≡残簦” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵羽历,是天一觀的道長。 經(jīng)常有香客問我秕磷,道長澎嚣,這世上最難降的妖魔是什么润歉? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任贩汉,我火速辦了婚禮,結(jié)果婚禮上匹舞,老公的妹妹穿的比我還像新娘赐稽。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布构资。 她就那樣靜靜地躺著迹淌,像睡著了一般荷鼠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天昨寞,我揣著相機與錄音享怀,去河邊找鬼添瓷。 笑死,一個胖子當著我的面吹牛值纱,可吹牛的內(nèi)容都是我干的鳞贷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼虐唠,長吁一口氣:“原來是場噩夢啊……” “哼悄晃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凿滤,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤妈橄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后翁脆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眷蚓,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年反番,在試婚紗的時候發(fā)現(xiàn)自己被綠了沙热。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叉钥。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖篙贸,靈堂內(nèi)的尸體忽然破棺而出投队,到底是詐尸還是另有隱情,我是刑警寧澤爵川,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布敷鸦,位于F島的核電站,受9級特大地震影響寝贡,放射性物質(zhì)發(fā)生泄漏扒披。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一圃泡、第九天 我趴在偏房一處隱蔽的房頂上張望碟案。 院中可真熱鬧,春花似錦颇蜡、人聲如沸价说。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熔任。三九已至,卻和暖如春唁情,著一層夾襖步出監(jiān)牢的瞬間疑苔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工甸鸟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惦费,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓抢韭,卻偏偏與公主長得像薪贫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刻恭,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容