簡介
JSON Web Token (JWT)是一個開放標准(RFC 7519),它定義了一種緊湊且自包含的方式,用於將信息作為JSON對象在各方之間安全地傳輸。可以對該信息進行驗證和信任,因為它是數字簽名的。JWT可以使用密鑰(使用HMAC算法)或使用RSA或ECDSA的公鑰/私鑰對進行簽名。
JWT優點
-
簡潔(Compact):可以通過URL、POST參數或在HTTP標頭內發送。由於它的大小,它的傳輸速度也很快。
-
自包含(Self-contained):有效負載包含有關用戶的所有必需信息,可以避免多次查詢數據庫。
-
JWT是跨語言的。
-
不需要在服務端保存會話信息,特別適用於分布式微服務。
JWT使用場景
1.授權
這是JWT的典型使用場景,一旦用戶登錄,每個后續請求都將包含JWT,允許用戶訪問該令牌允許的路由、服務和資源。單點登錄是現在廣泛使用JWT的一個特性,因為它的開銷很小,並且能夠在不同域的系統之間輕松使用。
具體流程如下圖:
a.用戶通過登錄將用戶名和密碼發送給服務端。
b.服務端驗證用戶名和密碼,驗證通過則生成JWT,將生成的JWT返回給客戶端。
c.客戶端收到JWT后,將它保存在本地,當退出登錄時再清除保存的JWT。
d.當客戶端訪問服務端受保護的資源時,需要帶上JWT,一般將JWT放入HTTP Header的Authorization標頭中(使用Bearer模式)。
e.服務端接收到請求時,首先會驗證JWT的有效性,驗證通過則正常執行對應邏輯返回對應資源,驗證失敗則拒絕訪問對應資源。
2.信息交換
JWT是在各方之間安全傳輸信息的好方法,因為JWT可以被簽名(例如:使用公鑰/私鑰對),您可以確定發送者就是他們所說的那個人。此外,由於使用標頭和有效負載計算簽名,因此還可以驗證內容是否被篡改。
JWT結構
JWT由用點(.)分隔的以下三個部分組成:
-
標頭(Header)
-
有效載荷(Payload)
-
簽名(Signature)
因此,JWT通常如下所示:
Header.Payload.Signature
標頭(Header)
Header通常由兩部分組成:令牌的類型,即JWT,以及簽名算法,如HMAC SHA256或RSA。如下所示:
{
"alg": "HS256",
"typ": "JWT"
}
這個JSON將被Base64編碼,組成JWT的第一部分。
有效載荷(Payload)
Payload包含有關實體和其他數據的聲明。如下所示:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
同樣這個JSON將被base64編碼,組成JWT的第二部分。
關於Payload應該注意兩點,由於只是使用Base64編碼並沒有加密,所以不應該存儲敏感數據。其次是應該設置到期時間。
簽名(Signature)
Signature需要使用編碼后的header和Payload以及我們提供的一個密鑰,然后使用header中指定的簽名算法進行簽名,該簽名字符串將作為JWT中的第三部分。如下所示:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Java中使用JWT
maven依賴:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.16.0</version>
</dependency>
JwtUtils:
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
public class JwtUtils {
//加密密鑰
private static final String SECRET = "5b6u5L+h5YWs5LyX5Y+377ya5oiR55yf55qE5LiN5LyaSmF2YeWVig==";
//Token過期時間
private static final int EXP_TIME_MINUTE = 30;
/**
* 創建Token
* @param payloadDataMap
* @return
*/
public static String createToken(Map<String, String> payloadDataMap) {
JWTCreator.Builder builder = JWT.create();
//設置自定義的數據
payloadDataMap.forEach((key, value) -> builder.withClaim(key, value));
//設置token有效期 60s
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, EXP_TIME_MINUTE);
builder.withExpiresAt(calendar.getTime());
//生成Token
return builder.sign(Algorithm.HMAC256(SECRET));
}
/**
* 驗證Token
* @param token
* @return
*/
public static DecodedJWT verifyToken(String token) {
return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
}
}
創建Token:
@Test
public void testCreateToken(){
Map<String, String> dataMap = new HashMap<>();
dataMap.put("id", "7");
dataMap.put("username", "buhe");
dataMap.put("wechat", "tryjava");
System.out.println(JwtUtils.createToken(dataMap));
}
輸出:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3ZWNoYXQiOiJ0cnlqYXZhIiwiaWQiOiI3IiwiZXhwIjoxNjI0ODY2MTE2LCJ1c2VybmFtZSI6ImJ1aGUifQ._hELqy_DpBL4IlE5N-ZtUNFia72MOxHK220ISuzSfKE
驗證Token,並輸出Payload中的數據:
@Test
public void testVerifyToken(){
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3ZWNoYXQiOiJ0cnlqYXZhIiwiaWQiOiI3IiwiZXhwIjoxNjI0ODY2MTE2LCJ1c2VybmFtZSI6ImJ1aGUifQ._hELqy_DpBL4IlE5N-ZtUNFia72MOxHK220ISuzSfKE";
DecodedJWT decodedJWT = JwtUtils.verifyToken(token);
decodedJWT.getClaims().forEach((key, value) -> System.out.println(key + " : " + value));
}
輸出:
wechat : "tryjava"
id : "7"
exp : 1624866116
username : "buhe"
驗證失敗的情況
token驗證成功時會返回DecodedJWT對象,從DecodedJWT中,我們能獲取到Token相關的一些信息,token驗證失敗時則會拋出對應異常。
token驗證異常分類如下圖: