前言
無狀態(tài)的HTTP協(xié)議
一天,你有個需求,你要去超市買一瓶可樂。 到了超市買了可樂,你告訴售貨員,下次給我準(zhǔn)備下雷碧,我下次來拿。 第二次,你去超市拿雷碧,售貨員說他不記得你什么時候說要準(zhǔn)備雷碧。 這次你學(xué)聰明了,售貨員給你寫了個紙條,上面有超市的章印,下次你帶著紙條來,買上了超市 給你準(zhǔn)備的雷碧
一、cookie
cookie 是一個非常具體的東西,指的就是瀏覽器里面能永久存儲的一種數(shù)據(jù)。跟服務(wù)器沒啥關(guān)系,僅僅是瀏覽器實現(xiàn)的一種數(shù)據(jù)存儲功能。
cookie由服務(wù)器生成,發(fā)送給瀏覽器,瀏覽器把cookie以KV形式存儲到某個目錄下的文本文件中,下一次請求同一網(wǎng)站時會把該cookie發(fā)送給服務(wù)器。由于cookie是存在客戶端上的,所以瀏覽器加入了一些限制確保cookie不會被惡意使用,同時不會占據(jù)太多磁盤空間。所以每個域的cookie數(shù)量是有限制的。
如何設(shè)置
客戶端設(shè)置
document.cookie = “name=xiaoming; age=12 “
- 客戶端可以設(shè)置cookie的一下選項: expires, domain, path, secure(只有在https協(xié)議的網(wǎng)頁中, 客戶端設(shè)置secure類型cookie才能生效), 但無法設(shè)置httpOnly選項
設(shè)置cookie => cookie被自動添加到request header中 => 服務(wù)端接收到cookie
服務(wù)端設(shè)置
不管你是請求一個資源文件(如html/js/css/圖片), 還是發(fā)送一個ajax請求, 服務(wù)端都會返回response.而response header中有一項叫set-cookie, 是服務(wù)端專門用來設(shè)置cookie的;
- 一個set-cookie只能設(shè)置一個cookie, 當(dāng)你想設(shè)置多個, 需要添加同樣多的set-cookie
- 服務(wù)端可以設(shè)置cookie的所有選項: expires, domain, path, secure, HttpOnly
Cookie,SessionStorage,LocalStorage
HTML5提供了兩種本地存儲的方式 sessionStorage 和 localStorage;
二、Session
Cookie是存儲在客戶端方,Session是存儲在服務(wù)端方,客戶端只存儲SessionId
在上面我們了解了什么是Cookie,既然瀏覽器已經(jīng)通過Cookie實現(xiàn)了有狀態(tài)這一需求,那么為什么又來了一個Session呢?這里我們想象一下,如果將賬戶的一些信息都存入Cookie中的話,一旦信息被攔截,那么我們所有的賬戶信息都會丟失掉。所以就出現(xiàn)了Session,在一次會話中將重要信息保存在Session中,瀏覽器只記錄SessionId一個SessionId對應(yīng)一次會話請求。
1@RequestMapping(“/testSession”) 2@ResponseBody 3public String testSession(HttpSession session){ 4 session.setAttribute(“testSession”,”this is my session”); 5 return “testSession”; 6} 7 8 9@RequestMapping(“/testGetSession”)10@ResponseBody11public String testGetSession(HttpSession session){12 Object testSession = session.getAttribute(“testSession”);13 return String.valueOf(testSession);14}
這里我們寫一個新的方法來測試Session是如何產(chǎn)生的,我們在請求參數(shù)中加上HttpSession session,然后再瀏覽器中輸入http://localhost:8005/testSession進行訪問可以看到在服務(wù)器的返回頭中在Cookie中生成了一個SessionId。然后瀏覽器記住此SessionId下次訪問時可以帶著此Id,然后就能根據(jù)此Id找到存儲在服務(wù)端的信息了。
此時我們訪問路徑http://localhost:8005/testGetSession,發(fā)現(xiàn)得到了我們上面存儲在Session中的信息。那么Session什么時候過期呢?
- 客戶端:和Cookie過期一致,如果沒設(shè)置,默認是關(guān)了瀏覽器就沒了,即再打開瀏覽器的時候初次請求頭中是沒有SessionId了。
- 服務(wù)端:服務(wù)端的過期是真的過期,即服務(wù)器端的Session存儲的數(shù)據(jù)結(jié)構(gòu)多久不可用了,默認是30分鐘。
既然我們知道了Session是在服務(wù)端進行管理的,那么或許你們看到這有幾個疑問,Session是在在哪創(chuàng)建的?Session是存儲在什么數(shù)據(jù)結(jié)構(gòu)中?接下來帶領(lǐng)大家一起看一下Session是如何被管理的。
Session的管理是在容器中被管理的,什么是容器呢?Tomcat、Jetty等都是容器。接下來我們拿最常用的Tomcat為例來看下Tomcat是如何管理Session的。在ManageBase的createSession是用來創(chuàng)建Session的。
1@Override 2public Session createSession(String sessionId) { 3 //首先判斷Session數(shù)量是不是到了最大值,最大Session數(shù)可以通過參數(shù)設(shè)置 4 if ((maxActiveSessions >= 0) && 5 (getActiveSessions() >= maxActiveSessions)) { 6 rejectedSessions++; 7 throw new TooManyActiveSessionsException( 8 sm.getString(“managerBase.createSession.ise”), 9 maxActiveSessions);10 }1112 // 重用或者創(chuàng)建一個新的Session對象,請注意在Tomcat中就是StandardSession13 // 它是HttpSession的具體實現(xiàn)類,而HttpSession是Servlet規(guī)范中定義的接口14 Session session = createEmptySession();151617 // 初始化新Session的值18 session.setNew(true);19 session.setValid(true);20 session.setCreationTime(System.currentTimeMillis());21 // 設(shè)置Session過期時間是30分鐘22 session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);23 String id = sessionId;24 if (id == null) {25 id = generateSessionId();26 }27 session.setId(id);// 這里會將Session添加到ConcurrentHashMap中28 sessionCounter++;2930 //將創(chuàng)建時間添加到LinkedList中,并且把最先添加的時間移除31 //主要還是方便清理過期Session32 SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);33 synchronized (sessionCreationTiming) {34 sessionCreationTiming.add(timing);35 sessionCreationTiming.poll();36 }37 return session38}
到此我們明白了Session是如何創(chuàng)建出來的,創(chuàng)建出來后Session會被保存到一個ConcurrentHashMap中??梢钥碨tandardSession類。
1protected Map sessions = new ConcurrentHashMap();
到這里大家應(yīng)該對Session有簡單的了解了。
Session是存儲在Tomcat的容器中,所以如果后端機器是多臺的話,因此多個機器間是無法共享Session的,此時可以使用Spring提供的分布式Session的解決方案,是將Session放在了Redis中。
三、Token
1、什么的Token
Token是首次登陸時由服務(wù)器下發(fā),作為客戶端進行請求的一個令牌,當(dāng)交互時用于身份驗證的一種驗證機制,當(dāng)?shù)谝淮蔚卿浐螅?wù)器生成一個Token便將此Token返回給客戶端,以后客戶端只需帶上這個Token前來請求數(shù)據(jù)即可,無需再次帶上用戶名和密碼。
2、Token的作用
- Token完全由應(yīng)用程序進行管理,所以它可以避開同源策略
- Token可以避免CSRF(跨站請求訪問)攻擊
- Token可以是無狀態(tài)的,可以在多個服務(wù)器之間共享
- 使用Token減輕服務(wù)器的壓力,減少頻繁的查詢數(shù)據(jù)庫。
3、Token身份認證的過程
- 用戶通過用戶名和密碼發(fā)送請求
- 程序進行驗證
- 程序返回一個簽名的token給客戶端
- 客戶端進行存儲token,并且用于每次發(fā)送請求
- 服務(wù)器端進行驗證token并返回數(shù)據(jù)
4、Token的存儲位置
- 存儲在localStorage中
- // 存儲token到ls const { token } = res.data; window.localStorage.setItem(‘jwtToken’, token);
優(yōu)點:沒有時間限制的存儲,會一直存放在瀏覽器中。
缺點:由于LocalStorage 可以被 javascript 訪問,所以容易受到XSS攻擊。所以可以在一個統(tǒng)一的地方復(fù)寫請求頭,讓每次請求都在header中帶上這個token, 當(dāng)token失效的時候,后端會返回401,這個時候在你可以在前端代碼中操作返回登陸頁面,清除localstorage中的token。(適用于 ajax請求或者 api請求,可以方便的存入 localstorage)另外,需要應(yīng)用程序來保證Token只在HTTPS下傳輸。
- 存儲在cookie中
優(yōu)點:可以防止 csrf攻擊,因為 csrf只能在請求中攜帶 cookie,而這里必須從 cookie中拿出相應(yīng)的值并放到 authorization 頭中。實際上cookie不能跨站(同源策略)被取出,因此可以避免 csrf 攻擊。(適用于 ajax請求或者 api請求,可以方便的設(shè)置 auth頭)
5、Token處理過期時間
在我的vue項目中,我將Token存儲在了localStorage中,有處理過Token過期,我是這樣做的:
created() { if (localStorage.jwtToken) { const decoded = jwt_decode(localStorage.jwtToken); //獲取當(dāng)前時間 const currentTime = Date.now() / 1000; //檢測token是否過期 if (decoded.exp < currentTime) { //跳轉(zhuǎn)到登錄頁面 this.$router.push('/login'); //其他處理 … } else{ //沒有過期的處理; } }
四、總結(jié)
cookie,session,Token沒有絕對的好與壞之分,只要還是要結(jié)合實際的業(yè)務(wù)場景和需求來決定采用哪種方式來管理回話,當(dāng)然也可以三種都用。