先給出JWT的官方文檔
什麼是JWT?
JSON Web Token(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且獨立的方式,用於在各方之間作爲JSON對象安全地傳輸信息。
什麼時候應該使用JWT?
- 授權:這是使用JWT的最常見方式。一旦用戶登錄,每個後續請求將包括JWT,允許用戶訪問該令牌允許的路由,服務和資源。Single Sign On是一種現在廣泛使用JWT的功能,因爲它的開銷很小,並且能夠在不同的域中輕鬆使用。
- 信息交換:JSON Web令牌是在各方之間安全傳輸信息的好方法。因爲JWT可以通過簽名(使用公鑰/私鑰對 )覈實發送人的身份。此外,由於使用標頭和有效負載計算簽名,您還可以驗證內容是否未被篡改。
JWT令牌結構
JWT令牌由Header、Payload、Signature三部分組成,每部分之間用點號分隔,通常的形式爲xxxxx.yyyyy.zzzzz
,下面分別對每部分做詳細介紹。
Header(頭)
Header通常由兩部分組成:令牌的類型,即JWT,以及使用的簽名算法,例如HMAC SHA256或RSA。
{
"alg": "HS256",
"typ": "JWT"
}
這個JSON被編碼爲Base64Url,形成JWT的第一部分。
Payload(有效載荷)
JWT的第二部分是有效載荷,其中包含聲明( claims)。聲明包含實體(通常是用戶)和其他自定義信息。聲明有三種類型:registered, public和private claims 。
- registered claims :這是一組預定義聲明,不是強制性的,但建議使用,以提供一組有用的,可互操作的聲明。其中包括: iss(發行人), exp(到期時間),sub(主題), aud(受衆)。
請注意:聲明名稱只有三個字符,因爲JWT需要保持簡潔。
- public claims:這些可以由使用JWT的人隨意定義。但爲避免衝突,應在 IANA JSON Web Token Register中定義它們,或者將其定義爲包含防衝突命名空間的URI。
- private claims:這些是自定義聲明,用於在同意使用這些聲明的各方之間共享信息,這些信息既沒有註冊也沒有公開聲明。
Payload經過Base64Url編碼,形成JWT的第二部分。
請注意:對於JWT令牌,雖然可以防止被篡改,但任何人都可以讀取。除非加密,否則不要將祕密信息放在JWT的有效載荷或頭元素中。
Signature(簽名)
Signature由Base64Url加密的Header、Payload再使用Header中指定的算法加密之後再和secret組成。
如果要使用HMACSHA256算法,將按以下方式創建簽名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Signature用於驗證消息在此過程中未被更改,並且,在使用私鑰簽名的令牌的情況下,它還可以驗證JWT的發件人的身份。
總結
最後將JWT的三部分由點號分隔作爲一個字符串,其可以在HTML和HTTP環境中輕鬆傳遞,並且比基於XML的標準的Token(如SAML)更加簡潔。
JWT工作流程
在身份驗證中,當用戶使用其憑據成功登錄時,將返回JSON Web Token。由於令牌是憑證,因此必須非常小心以防止出現安全問題。一般情況下,您不應該將令牌保留的時間超過要求。
每當用戶想要訪問受保護的路由或資源時,用戶發送JWT到相應的地址,通常在Authorization標頭中。請求頭的的內容應如下所示:
Authorization: Bearer <token>
在某些情況下,這可以是無狀態授權機制。服務器的受保護路由將檢查Authorization
標頭中的有效JWT ,如果存在,則允許用戶訪問受保護資源。如果JWT包含必要的數據,則可以減少查詢數據庫以進行某些操作的需要,儘管可能並非總是如此。
如果在標Authorization
頭中發送Token,則跨域資源共享(CORS)將不會成爲問題,因爲它不使用cookie。
下圖顯示瞭如何獲取JWT並用於訪問API或資源:
- 應用程序或客戶端向授權服務器發送授權請求。
- 授予授權後,授權服務器會嚮應用程序返回JWT。
- 應用程序使用JWT來訪問受保護資源(如API)。
請注意:使用JWT時,Token中包含的所有信息都會向用戶或其他方公開,即使他們無法更改。所以您不應該在令牌中放置祕密信息。
JWT中的JAVA實現
JWT的java實現非常多,詳細何以查看官方文檔。
其中常用的有com.auth0.java-jwt和io.jsonwebtoken.jjwt
這裏我採用 jjwt 作爲演示,因爲他的Github中的Star比較多。
Maven依賴
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.5</version>
<scope>runtime</scope>
</dependency>
<!-- Uncomment this next dependency if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
<scope>runtime</scope>
</dependency>
-->
創建Token
//Sample method to construct a JWT
public static String createJWT(String id, String issuer, String subject, long ttlMillis) {
//The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary( APP_ID + APP_SECRET);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now)
.setSubject(subject)
.setIssuer(issuer)
.signWith(signatureAlgorithm, signingKey);
//if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
解析Token
//Sample method to validate and read the JWT
public static Claims parseJWT(String jwt) {
//This line will throw an exception if it is not a signed JWS (as expected)
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(APP_ID + APP_SECRET))
.parseClaimsJws(jwt).getBody();
// System.out.println("ID: " + claims.getId());
// System.out.println("Subject: " + claims.getSubject());
// System.out.println("Issuer: " + claims.getIssuer());
// System.out.println("Expiration: " + claims.getExpiration());
return claims;
}
其中APP_ID
和APP_SECRET
可以自定義爲你想要的任何值,但是不能過於簡單。
你會發現我們再使用的時候沒有設置Header的值,因爲jjwt爲了我們使用方便會根據使用的簽名算法或壓縮算法自動設置它們。
使用
@Test
public void createJWT(){
String jwt = JWTUtil.createJWT("1", "111", "admin", JWTUtil.DAY_TTL);
System.out.println(jwt);
}
@Test
public void parseJWT(){
Claims claims = JWTUtil.parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwiaWF0IjoxNTQ4Mjk1NjQ0LCJzdWIiOiJhZG1pbiIsImlzcyI6IjExMSIsImV4cCI6MTU0ODMzODg0NH0.WRkyeG3MfVor02Ya4732fgGydXhtkkKSDwbxOIZ2i9Y");
System.out.println(claims);
}
如果想使用jjwt更復雜的功能或者其他的Java實現可以去他們相應的Github上學習。