目錄
- 1.實現登錄驗證的引出
- 原因
- 2.JWT令牌
- 2.1 使用JWT令牌時
- 2.2 令牌的組成
- 3. JWT令牌(token)生成和校驗
- 3.1 引入JWT令牌的依賴
- 3.2 使用Jar包中提供的API來實現JWT令牌的生成和校驗
- 3.3 使用JWT令牌驗證登錄
- 3.4 令牌的優缺點
1.實現登錄驗證的引出
傳統思路下:
- 登錄頁面把用戶名和密碼交給服務器。
- 服務器驗證用戶名和密碼是否正確,并返回校驗結果給后端。
- 如果密碼正確,就會在服務器創建Session,通過Cookie把sessionId返回給客戶端。
原因
但是在集群環境下,無法直接使用Session。因為如果只部署在一臺機器時,容易發生單點故障(一旦這臺服務器掛了,整個應用就無法訪問),所以通常情況下,一個Web應用會部署在多個服務器上,通過Nginx等進行負載均衡。此時,來自一個用戶的請求就會分發到不同的服務器上。
- 使用Session時:
- 用戶登錄: 用戶登錄請求,經過負載均衡發送給服務器1,服務器1進行用戶名和密碼驗證,驗證成功后,把Session存在了服務器1上。
- 查詢操作:用戶登錄之后,攜帶Cookie(里面帶有SessionId)繼續執行查詢操作,假如進行查詢博客列表,此時請求經過負載均衡發到服務器2上,服務器2會先通過SessionId驗證用戶是否登錄,此時第二臺機器上沒有該用戶的Session,即出現查詢不了的問題。
2.JWT令牌
JWT全稱:JSON Web Token,用于客戶端和服務器之間傳遞安全可靠的信息,本質是一個token,也叫token,令牌的本質就是一個字符串。相當于現在人們的身份證,出門在外驗證身份的時候,拿出身份證即可。
2.1 使用JWT令牌時
- 用戶登錄 : 用戶登錄請求,經過負載均衡,把請求發給服務器1,服務器1進行賬號密碼驗證,驗證成功之后,生成一個令牌,并返回給客戶端。
- 客戶端收到令牌時,把令牌存儲起來,可以存儲在Cookie中,也可以存儲在其它的存儲空間,典型的如(localStorage)
- 查詢操作 用戶登錄之后,攜帶令牌繼續執行查詢操作,假如查詢博客列表,此時請求經過負載均衡發到服務器2,服務器2先進行權限驗證操作。服務器驗證令牌是否有效,如果有效,說明用戶已經執行了登錄操作,如果無效,說明用戶之前未執行登錄操作。
2.2 令牌的組成
令牌官網所示,token,本質上一個字符串中間使用 符號 點 . 來分割,令牌由三部分組成,header、payload和verify signature。
-
第一部分:Header(頭),令牌的類型和使用的簽名算法,如"alg": “HS256(哈希算法)”, “typ”: “JWT”。
-
第二部分:Payload(負載),存放一些有效的信息(自定義信息,默認信息)如{“id”:“1”,“username”:“zhangsan”},還存在JWT提供的現場字段,如過期時間戳等。
-
第三部分:Signature(簽名),防止token被篡改,確保安全性。
簽名的目的就是為了防止token被篡改,而正因為token最后一個部分簽名存在, 所以整個token是非常安全可靠的,一旦token當中的任何一部分被修改, 整個token在校驗的時候都會失敗。
3. JWT令牌(token)生成和校驗
3.1 引入JWT令牌的依賴
- 在pom.xml文件中引入依賴
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is
preferred --><version>0.11.5</version><scope>runtime</scope></dependency>
3.2 使用Jar包中提供的API來實現JWT令牌的生成和校驗
- 生成token之后,獲取token進行解析,創建解釋器,設置簽名密鑰,如果解析token的claims內容不為null,說明校驗成功,否則失敗。
package com.example.blog.utils;import com.example.blog.constant.Constants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Slf4j
public class JwtUtils {// JWT過期時間public static final long JWT_EXPIRATION = 60*60*60*1000;// 生成keyprivate static final String secretStr = "DuJXRS2W3AJHqyFhAplBmsPNawnEdFYFNmlNdMbyU9w=";private static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretStr));/*** 生成token*/public static String genJwtToken(Map<String,Object> claim) {String token = Jwts.builder().setClaims(claim).setExpiration(new Date(System.currentTimeMillis()+JWT_EXPIRATION)).signWith(key).compact();return token;}/*** 校驗token* Claims 為空,表示jwt校驗失敗**/public static Claims parseToken(String token) {// 創建解析器,設置簽名密鑰JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();Claims claims = null;try {// 解析tokenclaims = build.parseClaimsJws(token).getBody();}catch (Exception e){log.error("解析token失敗,token:{}",token);return null;}return claims;}
}
3.3 使用JWT令牌驗證登錄
3.4 令牌的優缺點
- 優點:
- 解決了集群環境下認證的問題
- 不需要在服務器端存儲,從而減輕了服務器的存儲壓力
- 缺點:
- 需要自己實現令牌的生成、傳遞、校驗