有很多小伙伴還搞不清 Cookie、Session、Token 他們的區(qū)別,今天我就帶大家徹底搞懂他們。
圖片來自 Pexels
Cookie
夏洛:大爺,樓上322住的是馬冬梅家吧?
大爺:馬都什么?
夏洛:馬冬梅。
大爺:什么都沒?。?br>夏洛:馬冬梅啊。
大爺:馬什么沒?
夏洛:行,大爺你先涼快著吧。
瀏覽器第一次訪問服務端時,服務器此時肯定不知道他的身份,所以創(chuàng)建一個獨特的身份標識數(shù)據(jù),格式為 key=value,放入到 Set-Cookie 字段里,隨著響應報文發(fā)給瀏覽器。
瀏覽器看到有 Set-Cookie 字段以后就知道這是服務器給的身份標識,于是就保存起來,下次請求時會自動將此 key=value 值放入到 Cookie 字段中發(fā)給服務端。
接下來我們用代碼演示一下服務器是如何生成,我們自己搭建一個后臺服務器,這里用的是 Spring Boot 搭建的,并且寫入 SpringMVC 的代碼如下:
@RequestMapping('/testCookies')
public String cookies(HttpServletResponse response){
response.addCookie(new Cookie('testUser','xxxx'));
return 'cookies';
}
這樣服務器就能夠根據(jù) Cookie 中的值記住我們的信息了:
我們可以看到 Cookie 字段還是被帶過去了:
在計算機打開 Chrome
在右上角,一次點擊更多圖標→設置
在底部,點擊高級
在隱私設置和安全性下方,點擊網(wǎng)站設置
依次點擊 Cookie→查看所有 Cookie 和網(wǎng)站數(shù)據(jù)
如果此時你換成了 Firefox 等其他的瀏覽器,因為 Cookie 剛才是存儲在 Chrome 里面的,所以服務器又蒙圈了,不知道你是誰,就會給 Firefox 再次貼上小紙條。
Cookie 中的參數(shù)設置
所以 Cookie 需要用一些其他的手段用來保護,防止外泄或者竊取,這些手段就是 Cookie 的屬性。
所以 Cookie 才在請求頭中出現(xiàn),接下來我們訪問 http://localhost:8005,我們發(fā)現(xiàn)沒有 Cookie 字段了,這就是 Path 控制的路徑。
②Domain
我們發(fā)現(xiàn)下圖中左邊的是有 Cookie 的字段的,但是我們訪問 http://172.16.42.81:8005/testCookies,看下圖的右邊可以看到?jīng)]有 Cookie 的字段了。這就是 Domain 控制的域名發(fā)送 Cookie。
Session
所以就出現(xiàn)了 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 是如何產生的,我們在請求參數(shù)中加上 HttpSession session。
然后在瀏覽器中輸入 http://localhost:8005/testSession 進行訪問可以看到在服務器的返回頭中在 Cookie 中生成了一個 SessionId。
客戶端:和 Cookie 過期一致,如果沒設置,默認是關了瀏覽器就沒了,即再打開瀏覽器的時候初次請求頭中是沒有 SessionId 了。
服務端:服務端的過期是真的過期,即服務器端的 Session 存儲的數(shù)據(jù)結構多久不可用了,默認是 30 分鐘。
在 ManageBase 的 createSession 是用來創(chuàng)建 Session 的:
@Override
public Session createSession(String sessionId) {
//首先判斷Session數(shù)量是不是到了最大值,最大Session數(shù)可以通過參數(shù)設置
if ((maxActiveSessions >= 0) &&
(getActiveSessions() >= maxActiveSessions)) {
rejectedSessions++;
throw new TooManyActiveSessionsException(
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 = new SessionTiming(session.getCreationTime(), 0);
synchronized (sessionCreationTiming) {
sessionCreationTiming.add(timing);
sessionCreationTiming.poll();
}
return session
}
可以看 StandardSession 類:
protected Map<string, session> www.jintianxuesha.com sessions = new ConcurrentHashMap<>();
Token
而 Token 是在服務端將用戶信息經(jīng)過 Base64Url 編碼過后傳給客戶端,每次用戶請求的時候都會帶上這一段信息,因此服務端拿到此信息進行解密后就知道此用戶是誰了,這個方法叫做 JWT(Json Web Token)。
簡潔:可以通過 URL,POST 參數(shù)或者是在 HTTP 頭參數(shù)發(fā)送,因為數(shù)據(jù)量小,傳輸速度也很快。
自包含:由于串包含了用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫。
因為 Token 是以 Json 的形式保存在客戶端的,所以 JWT 是跨語言的。
不需要在服務端保存會話信息,特別適用于分布式微服務。
JWT 的結構
實際的 JWT 大概長下面的這樣,它是一個很長的字符串,中間用.分割成三部分。
Header 是一個 Json 對象,描述 JWT 的元數(shù)據(jù),通常是下面這樣子的:
{
'alg': 'HS256',
'typ': 'JWT'
}
alg 屬性表示簽名的算法(algorithm),默認是 HMAC SHA256(寫成 HS256)。
type 屬性表示這個令牌(Token)的類型(type),JWT 令牌統(tǒng)一寫為 JWT。最后,將上面的 Json 對象使用 Base64URL 算法轉成字符串。
iss (issuer):簽發(fā)人
exp (expiration time):過期時間
sub (subject):主題
aud (audience):受眾
nbf (Not Before):生效時間
iat (Issued At):簽發(fā)時間
jti (JWT ID):編號
當然除了官方提供的這幾個字段我們也能夠自己定義私有字段,下面就是一個例子:
{
'name': 'xiaoMing',
'age': 14
}
Java 中如何使用 Token
上面我們介紹了關于 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());
發(fā)現(xiàn)輸出的 Token 如下:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwibmFtZSI6InhpYW9NaW5nIiwiYWdlIjoxNH0.3KOWQ-oYvBSzslW5vgB1D-JpCwS-HkWGyWdXCP5l3Ko
此時在網(wǎng)上隨便找個 Base64 解碼的網(wǎng)站就能將信息解碼出來:
總結
Cookie 是存儲在客戶端的。
Session 是存儲在服務端的,可以理解為一個狀態(tài)列表。擁有一個唯一會話標識 SessionId??梢愿鶕?jù) SessionId 在服務端查詢到存儲的信息。
Session 會引發(fā)一個問題,即后端多臺機器時 Session 共享的問題,解決方案可以使用 Spring 提供的框架。
Token 類似一個令牌,無狀態(tài)的,服務端所需的信息被 Base64 編碼后放到 Token 中,服務器可以直接解碼出其中的數(shù)據(jù)。
https://github.com/modouxiansheng/Doraemon
作者:不學無數(shù)的程序員