Spring框架整合Java Web Token問題
Java Web Token
JSON Web Token(JWT)是一個非常輕巧的規(guī)范。
這個規(guī)范允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息。
JWT組成
一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。
荷載
{
"iss": "John Wu JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"from_user": "B",
"target_user": "A"
}這里面的前五個字段都是由JWT的標準所定義的。
- iss: 該JWT的簽發(fā)者
- sub: 該JWT所面向的用戶
- aud: 接收該JWT的一方
- exp(expires): 什么時候過期,這里是一個Unix時間戳
- iat(issued at): 在什么時候簽發(fā)的
這些定義都可以在標準中找到。
將上面的JSON對象進行[base64編碼]可以得到下面的字符串。
這個字符串我們將它稱作JWT的Payload(載荷)。
eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9
頭部
JWT還需要一個頭部,頭部用于描述關于該JWT的最基本的信息,例如其類型以及簽名所用的算法等。
這也可以被表示成一個JSON對象。
{
“typ”: “JWT”,
“alg”: “HS256”
}在這里,我們說明了這是一個JWT,并且我們所用的簽名算法(后面會提到)是HS256算法。(算法根據(jù)實際情況而變)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
簽名
簽名就是將荷載和頭部編碼用.號連接在一起就形成了
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0
我們將上面拼接完的字符串用HS256算法進行加密。
在加密的時候,我們還需要提供一個密鑰(secret)。
如果我們用mystar作為密鑰的話,那么就可以得到我們加密后的內容
rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
最后將這一部分簽名也拼接在被簽名的字符串后面,我們就得到了完整的JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
簽名的目的:
最后一步簽名的過程,實際上是對頭部以及載荷內容進行簽名。一般而言,加密算法對于不同的輸入產生的輸出總是不一樣的。對于兩個不同的輸入,產生同樣的輸出的概率極其地小(有可能比我成世界首富的概率還?。?。所以,我們就把“不一樣的輸入產生不一樣的輸出”當做必然事件來看待吧。
所以,如果有人對頭部以及載荷的內容解碼之后進行修改,再進行編碼的話,那么新的頭部和載荷的簽名和之前的簽名就將是不一樣的。而且,如果不知道服務器加密的時候用的密鑰的話,得出來的簽名也一定會是不一樣的。
服務器應用在接受到JWT后,會首先對頭部和載荷的內容用同一算法再次簽名。那么服務器應用是怎么知道我們用的是哪一種算法呢?別忘了,我們在JWT的頭部中已經用alg字段指明了我們的加密算法了。
如果服務器應用對頭部和載荷再次以同樣方法簽名之后發(fā)現(xiàn),自己計算出來的簽名和接受到的簽名不一樣,那么就說明這個Token的內容被別人動過的,我們應該拒絕這個Token,返回一個HTTP 401 Unauthorized響應。
我們可以看到,JWT適合用于向Web應用傳遞一些非敏感信息。例如在上面提到的完成加好友的操作,還有諸如下訂單的操作等等。
其實JWT還經常用于設計用戶認證和授權系統(tǒng),甚至實現(xiàn)Web應用的單點登錄。
Spring使用JWT
Maven配置方式
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>JWT算法(了解)
| JWS | 算法 | 介紹 |
|---|---|---|
| HS256 | HMAC256 | HMAC with SHA-256 |
| HS384 | HMAC384 | HMAC with SHA-384 |
| HS512 | HMAC512 | HMAC with SHA-512 |
| RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
| RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 |
| RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 |
| ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 |
| ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 |
| ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 |
使用方法
選擇算法:
算法定義了一個令牌是如何被簽名和驗證的。它可以用HMAC算法的原始值來實例化,也可以在RSA和ECDSA算法的情況下對密鑰對或密鑰提供程序進行實例化。創(chuàng)建后,該實例可用于令牌簽名和驗證操作。
在使用RSA或ECDSA算法時,只需要簽署JWTs,就可以通過傳遞null值來避免指定公鑰。當您需要驗證JWTs時,也可以使用私鑰進行操作
使用靜態(tài)的字符密文或者key來獲取算法器:
//HMAC
Algorithm algorithmHS = Algorithm.HMAC256("secret");
//RSA
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);使用一個key提供者來獲取算法:
通過使用KeyProvider,您可以在運行時更改密鑰,用于驗證令牌簽名或為RSA或ECDSA算法簽署一個新的令牌。
這是通過實現(xiàn)RSAKeyProvider或ECDSAKeyProvider方法實現(xiàn)的:
getPublicKeyById(String kid): 它在令牌簽名驗證中調用,它應該返回用于驗證令牌的密鑰。如果使用了關鍵的輪換,例如JWK,它可以使用id來獲取正確的輪換鍵(或者只是一直返回相同的鍵)。getPrivateKey(): 在令牌簽名期間調用它,它應該返回用于簽署JWT的密鑰。getPrivateKeyId():在令牌簽名期間調用它,它應該返回標識由getPrivateKey()返回的鍵的id的id。這個值比JWTCreator.Builder和keyid(String)方法中的值更受歡迎。如果您不需要設置孩子的值,就避免使用KeyProvider實例化算法。
創(chuàng)建JWT
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (UnsupportedEncodingException exception){
//UTF-8 encoding not supported
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}如果Claim不能轉換為JSON,或者在簽名過程中使用的密鑰無效,那么將會拋出JWTCreationException異常。
驗證令牌
首先需要通過調用jwt.require()和傳遞算法實例來創(chuàng)建一個JWTVerifier實例。
如果您要求令牌具有特定的Claim值,請使用構建器來定義它們。
方法build()返回的實例是可重用的,因此您可以定義一次,并使用它來驗證不同的標記。
最后調用verifier.verify()來驗證token
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (UnsupportedEncodingException exception){
//UTF-8 encoding not supported
} catch (JWTVerificationException exception){
//Invalid signature/claims
}時間驗證
當驗證一個令牌時,時間驗證會自動發(fā)生,導致在值無效時拋出一個JWTVerificationException。如果前面的任何一個字段都丟失了,那么在這個驗證中就不會考慮這些字段。
要指定令牌仍然被認為有效的余地窗口,在JWTVerifier builder中使用accept回旋()方法,并傳遞一個正值的秒值。這適用于上面列出的每一項。
JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) // 1 sec for nbf, iat and exp .build();
您還可以為給定的日期聲明指定一個自定義值,并為該聲明覆蓋缺省值。
JWTVerifier verifier = JWT.require(algorithm) .acceptLeeway(1) //1 sec for nbf and iat .acceptExpiresAt(5) //5 secs for exp .build();
信息解析
Algorithm (“alg”)
返回jwt的算法值或,如果沒有定義則返回null
String algorithm = jwt.getAlgorithm();
如果您需要在您的lib/app中測試此行為,將驗證實例轉換為basever可視化,以獲得verific.build()方法的可見性,該方法可以接受定制的時鐘。
例如:
BaseVerification verification = (BaseVerification) JWT.require(algorithm) .acceptLeeway(1) .acceptExpiresAt(5); Clock clock = new CustomClock(); //Must implement Clock interface JWTVerifier verifier = verification.build(clock);
Type (“typ”)
返回jwt的類型值,如果沒有定義則返回null(多數(shù)情況類型值為jwt)
String type = jwt.getType();
Content Type (“cty”)
返回內容的類型,如果沒有定義則返回null
String contentType = jwt.getContentType();
Key Id (“kid”)
返回key的id值,如果沒有定義則返回null
String keyId = jwt.getKeyId();
自定義字段
在令牌的頭部中定義的附加聲明可以通過調用getHeaderClaim() 獲取,即使無法找到,也會返回。您可以通過調用claim.isNull()來檢查聲明的值是否為null。
Claim claim = jwt.getHeaderClaim("owner");當使用jwt.create()創(chuàng)建一個令牌時,您可以通過調用withHeader()來指定頭聲明,并同時傳遞聲明的映射。
Map<String, Object> headerClaims = new HashMap();
headerClaims.put("owner", "auth0");
String token = JWT.create()
.withHeader(headerClaims)
.sign(algorithm);提示:在簽名過程之后,alg和typ值將始終包含在Header中。
JWT的負載(Payload)聲明
Issuer ("iss")返回簽發(fā)者的名稱值,如果沒有在負載中定義則返回null
String issuer = jwt.getIssuer();
Subject ("sub")返回jwt所面向的用戶的值,如果沒有在負載中定義則返回null
String subject = jwt.getSubject();
Audience ("aud")返回該jwt由誰接收,如果沒有在負載中定義則返回null
List<String> audience = jwt.getAudience();
Expiration Time ("exp")返回該jwt的過期時間,如果在負載中沒有定義則返回null
Date expiresAt = jwt.getExpiresAt();
Not Before ("nbf")Returns the Not Before value or null if it’s not defined in the Payload.
Date notBefore = jwt.getNotBefore();
Issued At ("iat")返回在什么時候簽發(fā)的,如果在負載中沒有定義則返回null
Date issuedAt = jwt.getIssuedAt();
JWT ID ("jti")返回該jwt的唯一標志,如果在負載中沒有定義則返回null
String id = jwt.getId();
自定義聲明
在令牌有效負載中定義的附加聲明可以通過調用getClaims()或 getClaim()和傳遞聲明名來獲得。即使無法找到聲明,也將會有返回值。您可以通過調用claim.isNull()來檢查聲明的值是否為null。
Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name
Claim claim = claims.get("isAdmin");或者:
Claim claim = jwt.getClaim("isAdmin");當使用jwt.create()創(chuàng)建一個令牌時,您可以通過調用withClaim()來指定自定義聲明,并同時傳遞名稱和值。
String token = JWT.create()
.withClaim("name", 123)
.withArrayClaim("array", new Integer[]{1, 2, 3})
.sign(algorithm);您還可以通過調用withClaim()來驗證jwt.require()的自定義聲明,并傳遞該名稱和所需的值。
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("name", 123)
.withArrayClaim("array", 1, 2, 3)
.build();
DecodedJWT jwt = verifier.verify("my.jwt.token"); 實例
package course.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.collect.Maps;
import course.pojo.User;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
//創(chuàng)建token
public static String creatToken(User user) throws IllegalArgumentException, UnsupportedEncodingException{
Algorithm algorithm = Algorithm.HMAC256("secret");
String username = user.getUsername();
Map<String, Object> map = Maps.newHashMap();
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create().withHeader(map)
.withClaim("username", username)
.withExpiresAt(new Date(System.currentTimeMillis()+360000))
.sign(algorithm);
return token;
}
//驗證jwt
public static DecodedJWT verifyJwt(String token){
DecodedJWT decodedJWT = null;
try{
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier jwtVerifier = JWT.require(algorithm).build();
decodedJWT = jwtVerifier.verify(token);
}catch(IllegalArgumentException e){
e.printStackTrace();
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}catch(JWTVerificationException e) {
e.printStackTrace();
}
return decodedJWT;
}
public static void main(String[] args) throws UnsupportedEncodingException{
// String username = "root";
// Integer id =1;
// System.out.println(creatToken(username,id));
// String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDgxMzgxNDAsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJyb290In0.OeRdHJZKmxFBqIN-A-uSNQK8JyKdzX-wcFR883oMqFA";
// System.out.println(verifyJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDgxMzgxNDAsInVzZXJJZCI6MSwidXNlcm5hbWUiOiJyb290In0.OeRdHJZKmxFBqIN-A-uSNQK8JyKdzX-wcFR883oMqFA"));
// System.out.println(verifyJwt(token).getClaims().get("username").asString());
}
}總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
數(shù)據(jù)庫連接池c3p0配置_動力節(jié)點Java學院整理
這篇文章主要為大家詳細介紹了數(shù)據(jù)庫連接池c3p0配置的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
Spring Boot 微服務架構設計與實現(xiàn)代碼
這篇文章主要介紹了本文介紹了SpringBoot微服務架構設計與實現(xiàn)的核心概念與使用方法,重點內容包括微服務架構的定義與特點、SpringBoot與微服務的集成、SpringBoot與微服務的配置和基本方法,感興趣的朋友跟隨小編一起看看吧2026-03-03
SpringBoot整合Mybatis使用Druid數(shù)據(jù)庫連接池
這篇文章主要介紹了SpringBoot整合Mybatis使用Druid數(shù)據(jù)庫連接池,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03
SpringBoot使用@Valid或者@Validated時自定義校驗的場景分析
文章介紹了在Java開發(fā)中,如何處理需要根據(jù)多個字段進行校驗的自定義場景,通過創(chuàng)建自定義注解和實現(xiàn)相應的約束類,可以在DTO類中對這些字段進行復雜校驗,最后,通過全局異常處理機制,可以統(tǒng)一處理校驗失敗的情況,本文給大家介紹的非常詳細,感興趣的朋友一起看看吧2025-12-12
Spring?Cloud?Ribbon?負載均衡使用策略示例詳解
Spring?Cloud?Ribbon?是基于Netflix?Ribbon?實現(xiàn)的一套客戶端負載均衡工具,Ribbon客戶端組件提供了一系列的完善的配置,如超時,重試等,這篇文章主要介紹了Spring?Cloud?Ribbon?負載均衡使用策略示例詳解,需要的朋友可以參考下2023-03-03
ConcurrentModificationException日志關鍵字報警思考分析
本文將記錄和分析日志中的ConcurrentModificationException關鍵字報警,還有一些我的思考,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-12-12
Java利用策略模式實現(xiàn)條件判斷,告別if else
策略模式定義了一系列算法,并且將每個算法封裝起來,使得他們可以相互替換,而且算法的變化不會影響使用算法的客戶端。本文將通過案例講解如何利用Java的策略模式實現(xiàn)條件判斷,告別if----else條件硬編碼,需要的可以參考一下2022-02-02
SpringBoot整合Swagger Api自動生成文檔的實現(xiàn)
本文主要介紹了SpringBoot整合Swagger Api自動生成文檔的實,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06

