Spring框架整合Java Web Token
Java Web Token
JSON Web Token(JWT)是一個非常輕巧的規(guī)范。這個規(guī)范允許我們使用JWT在用戶和服務(wù)器之間傳遞安全可靠的信息。
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的標(biāo)準(zhǔn)所定義的。
- iss: 該JWT的簽發(fā)者
- sub: 該JWT所面向的用戶
- aud: 接收該JWT的一方
- exp(expires): 什么時候過期,這里是一個Unix時間戳
- iat(issued at): 在什么時候簽發(fā)的
這些定義都可以在標(biāo)準(zhǔn)中找到。
將上面的JSON對象進行[base64編碼]可以得到下面的字符串。這個字符串我們將它稱作JWT的Payload(載荷)。
eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9頭部
JWT還需要一個頭部,頭部用于描述關(guān)于該JWT的最基本的信息,例如其類型以及簽名所用的算法等。這也可以被表示成一個JSON對象。
{“typ”: “JWT”,
“alg”: “HS256”
}
在這里,我們說明了這是一個JWT,并且我們所用的簽名算法(后面會提到)是HS256算法。(算法根據(jù)實際情況而變)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9簽名
簽名就是將荷載和頭部編碼用.號連接在一起就形成了
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0我們將上面拼接完的字符串用HS256算法進行加密。在加密的時候,我們還需要提供一個密鑰(secret)。如果我們用mystar作為密鑰的話,那么就可以得到我們加密后的內(nèi)容
rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM最后將這一部分簽名也拼接在被簽名的字符串后面,我們就得到了完整的JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM簽名的目的
最后一步簽名的過程,實際上是對頭部以及載荷內(nèi)容進行簽名。一般而言,加密算法對于不同的輸入產(chǎn)生的輸出總是不一樣的。對于兩個不同的輸入,產(chǎn)生同樣的輸出的概率極其地小(有可能比我成世界首富的概率還小)。所以,我們就把“不一樣的輸入產(chǎn)生不一樣的輸出”當(dāng)做必然事件來看待吧。
所以,如果有人對頭部以及載荷的內(nèi)容解碼之后進行修改,再進行編碼的話,那么新的頭部和載荷的簽名和之前的簽名就將是不一樣的。而且,如果不知道服務(wù)器加密的時候用的密鑰的話,得出來的簽名也一定會是不一樣的。
服務(wù)器應(yīng)用在接受到JWT后,會首先對頭部和載荷的內(nèi)容用同一算法再次簽名。那么服務(wù)器應(yīng)用是怎么知道我們用的是哪一種算法呢?別忘了,我們在JWT的頭部中已經(jīng)用alg字段指明了我們的加密算法了。
如果服務(wù)器應(yīng)用對頭部和載荷再次以同樣方法簽名之后發(fā)現(xiàn),自己計算出來的簽名和接受到的簽名不一樣,那么就說明這個Token的內(nèi)容被別人動過的,我們應(yīng)該拒絕這個Token,返回一個HTTP 401 Unauthorized響應(yīng)。
我們可以看到,JWT適合用于向Web應(yīng)用傳遞一些非敏感信息。例如在上面提到的完成加好友的操作,還有諸如下訂單的操作等等。
其實JWT還經(jīng)常用于設(shè)計用戶認(rèn)證和授權(quán)系統(tǒng),甚至實現(xiàn)Web應(yīng)用的單點登錄。
Spring使用JWT
Maven配置方式
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.2.0</version> </dependency>JWT算法(了解)
| 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值來避免指定公鑰。當(dāng)您需要驗證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): 它在令牌簽名驗證中調(diào)用,它應(yīng)該返回用于驗證令牌的密鑰。如果使用了關(guān)鍵的輪換,例如JWK,它可以使用id來獲取正確的輪換鍵(或者只是一直返回相同的鍵)。
- getPrivateKey(): 在令牌簽名期間調(diào)用它,它應(yīng)該返回用于簽署JWT的密鑰。
- getPrivateKeyId():在令牌簽名期間調(diào)用它,它應(yīng)該返回標(biāo)識由getPrivateKey()返回的鍵的id的id。這個值比JWTCreator.Builder和keyid(String)方法中的值更受歡迎。如果您不需要設(shè)置孩子的值,就避免使用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不能轉(zhuǎn)換為JSON,或者在簽名過程中使用的密鑰無效,那么將會拋出JWTCreationException異常。
驗證令牌
首先需要通過調(diào)用jwt.require()和傳遞算法實例來創(chuàng)建一個JWTVerifier實例。如果您要求令牌具有特定的Claim值,請使用構(gòu)建器來定義它們。方法build()返回的實例是可重用的,因此您可以定義一次,并使用它來驗證不同的標(biāo)記。最后調(diào)用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 instanceDecodedJWT jwt = verifier.verify(token); } catch (UnsupportedEncodingException exception){//UTF-8 encoding not supported } catch (JWTVerificationException exception){//Invalid signature/claims }時間驗證
當(dāng)驗證一個令牌時,時間驗證會自動發(fā)生,導(dǎo)致在值無效時拋出一個JWTVerificationException。如果前面的任何一個字段都丟失了,那么在這個驗證中就不會考慮這些字段。
要指定令牌仍然被認(rèn)為有效的余地窗口,在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中測試此行為,將驗證實例轉(zhuǎn)換為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”)
返回內(nèi)容的類型,如果沒有定義則返回null
String contentType = jwt.getContentType();Key Id (“kid”)
返回key的id值,如果沒有定義則返回null
String keyId = jwt.getKeyId();自定義字段
在令牌的頭部中定義的附加聲明可以通過調(diào)用getHeaderClaim() 獲取,即使無法找到,也會返回。您可以通過調(diào)用claim.isNull()來檢查聲明的值是否為null。
Claim claim = jwt.getHeaderClaim("owner");當(dāng)使用jwt.create()創(chuàng)建一個令牌時,您可以通過調(diào)用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的唯一標(biāo)志,如果在負載中沒有定義則返回null
String id = jwt.getId();自定義聲明
在令牌有效負載中定義的附加聲明可以通過調(diào)用getClaims()或 getClaim()和傳遞聲明名來獲得。即使無法找到聲明,也將會有返回值。您可以通過調(diào)用claim.isNull()來檢查聲明的值是否為null。
Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name Claim claim = claims.get("isAdmin");或者:
Claim claim = jwt.getClaim("isAdmin");當(dāng)使用jwt.create()創(chuàng)建一個令牌時,您可以通過調(diào)用withClaim()來指定自定義聲明,并同時傳遞名稱和值。
String token = JWT.create().withClaim("name", 123).withArrayClaim("array", new Integer[]{1, 2, 3}).sign(algorithm);您還可以通過調(diào)用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)建tokenpublic 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;}//驗證jwtpublic 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());} }總結(jié)
以上是生活随笔為你收集整理的Spring框架整合Java Web Token的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQL(10)_PRIMARY KEY
- 下一篇: Java岗大厂面试官常问的那些问题,真香