> 筆者文筆功力尚淺,若有不妥,請慷慨指出,一定感激涕零html
洛:大爺,樓上322住的是馬冬梅家吧? 大爺:馬都什麼? 夏洛:馬冬梅。 大爺:什麼都沒啊? 夏洛:馬冬梅啊。 大爺:馬什麼沒? 夏洛:行,大爺你先涼快着吧。
在瞭解這三個概念以前咱們先要了解HTTP是無狀態的Web服務器,什麼是無狀態呢?就像上面夏洛特煩惱中經典的一幕對話同樣,一次對話完成後下一次對話徹底不知道上一次對話發生了什麼。若是在Web服務器中只是用來管理靜態文件還好說,對方是誰並不重要,把文件從磁盤中讀取出來發出去便可。可是隨着網絡的不斷髮展,好比電商中的購物車只有記住了用戶的身份纔可以執行接下來的一系列動做。因此此時就須要咱們無狀態的服務器記住一些事情。git
那麼Web服務器是如何記住一些事情呢?既然Web服務器記不住東西,那麼咱們就在外部想辦法記住,至關於服務器給每一個客戶端都貼上了一個小紙條。上面記錄了服務器給咱們返回的一些信息。而後服務器看到這張小紙條就知道咱們是誰了。那麼Cookie
是誰產生的呢?Cookies是由服務器產生的。接下來咱們描述一下Cookie
產生的過程github
key=value
,放入到Set-Cookie
字段裏,隨着響應報文發給瀏覽器。Set-Cookie
字段之後就知道這是服務器給的身份標識,因而就保存起來,下次請求時會自動將此key=value
值放入到Cookie
字段中發給服務端。Cookie
字段中有值,就能根據此值識別用戶的身份而後提供個性化的服務。接下來咱們用代碼演示一下服務器是如何生成,咱們本身搭建一個後臺服務器,這裏我用的是SpringBoot搭建的,而且寫入SpringMVC的代碼以下。web
@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
字段仍是被帶過去了。json
那麼瀏覽器的Cookie
是存放在哪呢?若是是使用的是Chrome
瀏覽器的話,那麼能夠按照下面步驟。segmentfault
Chrome
更多
圖標->設置
高級
隱私設置和安全性
下方,點擊網站設置Cookie
->查看全部Cookie和網站數據
而後能夠根據域名進行搜索所管理的Cookie
數據。因此是瀏覽器替你管理了Cookie
的數據,若是此時你換成了Firefox
等其餘的瀏覽器,由於Cookie
剛纔是存儲在Chrome
裏面的,因此服務器又蒙圈了,不知道你是誰,就會給Firefox
再次貼上小紙條。後端
說到這裏,應該知道了Cookie
就是服務器委託瀏覽器存儲在客戶端裏的一些數據,而這些數據一般都會記錄用戶的關鍵識別信息。因此Cookie
須要用一些其餘的手段用來保護,防止外泄或者竊取,這些手段就是Cookie
的屬性。api
參數名 | 做用 | 後端設置方法 |
---|---|---|
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) |
下面我就簡單演示一下這幾個參數的用法及現象。
設置爲cookie.setPath("/testCookies")
,接下來咱們訪問http://localhost:8005/testCookies
,咱們能夠看到在左邊和咱們指定的路徑是同樣的,因此Cookie
纔在請求頭中出現,接下來咱們訪問http://localhost:8005
,咱們發現沒有Cookie
字段了,這就是Path
控制的路徑。
設置爲cookie.setDomain("localhost")
,接下來咱們訪問http://localhost:8005/testCookies
咱們發現下圖中左邊的是有Cookie
的字段的,可是咱們訪問http://172.16.42.81:8005/testCookies
,看下圖的右邊能夠看到沒有Cookie
的字段了。這就是Domain
控制的域名發送Cookie
。
接下來的幾個參數就不一一演示了,相信到這裏你們應該對Cookie
有一些瞭解了。
> 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://localhost:8005/testSession
進行訪問能夠看到在服務器的返回頭中在Cookie
中生成了一個SessionId
。而後瀏覽器記住此SessionId
下次訪問時能夠帶着此Id,而後就能根據此Id找到存儲在服務端的信息了。
此時咱們訪問路徑http://localhost:8005/testGetSession
,發現獲得了咱們上面存儲在Session
中的信息。那麼Session
何時過時呢?
Cookie
過時一致,若是沒設置,默認是關了瀏覽器就沒了,即再打開瀏覽器的時候初次請求頭中是沒有SessionId
了。Session
存儲的數據結構多久不可用了,默認是30分鐘。既然咱們知道了Session
是在服務端進行管理的,那麼或許大家看到這有幾個疑問,Session
是在在哪建立的?Session
是存儲在什麼數據結構中?接下來帶領你們一塊兒看一下Session
是如何被管理的。
Session
的管理是在容器中被管理的,什麼是容器呢?Tomcat
、Jetty
等都是容器。接下來咱們拿最經常使用的Tomcat
爲例來看下Tomcat
是如何管理Session
的。在ManageBase
的createSession
是用來建立Session
的。
@Override public Session createSession(String sessionId) { //首先判斷Session數量是否是到了最大值,最大Session數能夠經過參數設置 if ((maxActiveSessions >= 0) && (getActiveSessions() >= maxActiveSessions)) { rejectedSessions++; throw new TooManyActiveSessionsException( sm.getString("managerBase.createSession.ise"), maxActiveSessions); } // 重用或者建立一個新的Session對象,請注意在Tomcat中就是StandardSession // 它是HttpSession的具體實現類,而HttpSession是Servlet規範中定義的接口 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++; //將建立時間添加到LinkedList中,而且把最早添加的時間移除 //主要仍是方便清理過時Session SessionTiming timing = new SessionTiming(session.getCreationTime(), 0); synchronized (sessionCreationTiming) { sessionCreationTiming.add(timing); sessionCreationTiming.poll(); } return session }
到此咱們明白了Session
是如何建立出來的,建立出來後Session
會被保存到一個ConcurrentHashMap
中。能夠看StandardSession
類。
protected Map<string, session> sessions = new ConcurrentHashMap<>();
到這裏你們應該對Session
有簡單的瞭解了。
> Session是存儲在Tomcat的容器中,因此若是後端機器是多臺的話,所以多個機器間是沒法共享Session的,此時可使用Spring提供的分佈式Session的解決方案,是將Session放在了Redis中。
Session
是將要驗證的信息存儲在服務端,並以SessionId
和數據進行對應,SessionId
由客戶端存儲,在請求時將SessionId
也帶過去,所以實現了狀態的對應。而Token
是在服務端將用戶信息通過Base64Url編碼事後傳給在客戶端,每次用戶請求的時候都會帶上這一段信息,所以服務端拿到此信息進行解密後就知道此用戶是誰了,這個方法叫作JWT(Json Web Token)。
> Token
相比較於Session
的優勢在於,當後端系統有多臺時,因爲是客戶端訪問時直接帶着數據,所以無需作共享數據的操做。
URL
,POST
參數或者是在HTTP
頭參數發送,由於數據量小,傳輸速度也很快實際的JWT大概長下面的這樣,它是一個很長的字符串,中間用.
分割成三部分
JWT是有三部分組成的
是一個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部分也是一個Json對象,用來存放實際須要傳輸的數據,JWT官方規定了下面幾個官方的字段供選用。
固然除了官方提供的這幾個字段咱們也可以本身定義私有字段,下面就是一個例子
{ "name": "xiaoMing", "age": 14 }
默認狀況下JWT是不加密的,任何人只要在網上進行Base64解碼就能夠讀到信息,因此通常不要將祕密信息放在這個部分。這個Json對象也要用Base64URL
算法轉成字符串
Signature部分是對前面的兩部分的數據進行簽名,防止數據篡改。
首先須要定義一個祕鑰,這個祕鑰只有服務器才知道,不能泄露給用戶,而後使用Header中指定的簽名算法(默認狀況是HMAC SHA256),算出簽名之後將Header、Payload、Signature三部分拼成一個字符串,每一個部分用.
分割開來,就能夠返給用戶了。
> HS256可使用單個密鑰爲給定的數據樣本建立簽名。當消息與簽名一塊兒傳輸時,接收方可使用相同的密鑰來驗證簽名是否與消息匹配。
上面咱們介紹了關於JWT的一些概念,接下來如何使用呢?首先在項目中引入Jar包
compile('io.jsonwebtoken:jjwt:0.9.0')
而後編碼以下
// 簽名算法 ,將對token進行簽名 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 經過祕鑰簽名JWT byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("SECRET"); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); Map<string,object> claimsMap = new HashMap<>(); 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());
發現輸出的Token以下
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwibmFtZSI6InhpYW9NaW5nIiwiYWdlIjoxNH0.3KOWQ-oYvBSzslW5vgB1D-JpCwS-HkWGyWdXCP5l3Ko
此時在網上隨便找個Base64解碼的網站就能將信息解碼出來
相信你們看到這應該對Cookie
、Session
、Token
有必定的瞭解了,接下來再回顧一下重要的知識點
SessionId
。能夠根據SessionId
在服務端查詢到存儲的信息。