說明:本翻譯僅僅作為個人筆記使用(因為每次看英文文檔,看了第一遍,下次再去看又得慢慢翻譯一遍,很反感,所以本人決定翻譯的同時記錄下來),本人水平有限,翻譯的不好大家勿噴。當然這篇文章要能解決你的一些問題,那自然是最好的。
Java JWT
Jwt 的Java實現 JSON Web Token (JWT) - RFC 7519.
如果你想要了解Android版本的JWT,可以訪問我們的另一個網址JWTDecode.Android .
安裝
Maven
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
Gradle
implementation 'com.auth0:java-jwt:3.8.1'
可用的算法
本庫實現JWT的驗證與簽名使用以下算法:
JWS | Algorithm | Description |
---|---|---|
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 |
使用
算法的使用
算法決定了token如何進行簽名與驗證。
在使用RSA或者ECDSA算法的情況下,可以用密碼(未經過處理)的對其進行實例化:
//HMAC
Algorithm algorithmHS = Algorithm.HMAC256("secret");
在使用HMAC算法的情況下,可以用公私秘鑰對或者KeyProvider
的對其進行實例化:
//HMAC
//RSA
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);
當選用RSA或者ECDSA算法時,當你僅僅只需要進行簽名,你可以使用null
值來避免指定公鑰。反過來,當你僅僅需要進行驗證時,你可以用相同方式來避免指定私鑰。例如:
Algorithm algorithmRS1 = Algorithm.RSA256(null, privateKey);//該算法僅僅用于簽名的情況下
Algorithm algorithmRS2 = Algorithm.RSA256(publicKey, null);//該算法僅僅用于驗證的情況下
使用KeyProvider:
通過使用KeyProvider
你可以在運行狀態下動態改變秘鑰(RSA或者ECDSA算法下用于Token
簽名驗證的秘鑰或者用于對一個新的Token
進行簽名的秘鑰)。這是通過實現RSAKeyProvider
或者ECDSAKeyProvider
方法來完成的。
-
getPublicKeyById(String kid)
:在Token簽名
驗證期間對其進行調用,該方法的返回值是一個用于驗證Token簽名
的公鑰。 如果使用了 key rotation(Key轉置), 例如. JWK 它能夠通過id獲得正確的轉置秘鑰(或者每次都只返回相同秘鑰)。 -
getPrivateKey()
: 在Token
簽名期間對其進行調用,該方法返回用于對JWT進行簽名的秘鑰。 -
getPrivateKeyId()
: 在Token簽名期間對其進行調用,該方法返回秘鑰的Id,該Id用于標識getPrivateKey()返回的值。此值優于JWTCreator.Builder #withKeyId(String)
方法中的值。如果您不需要設置kid值,請避免使用KeyProvider實例化算法。
下邊的例子將展示JwtStore
是如何進行進行工作的(JwtStore是JWK Set的抽象實現)。要使用JWKS進行簡單的Key轉置,請嘗試使用jwks-rsa-java庫。
final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}");
final RSAPrivateKey privateKey = //Get the key instance
final String privateKeyId = //Create an Id for the above key
RSAKeyProvider keyProvider = new RSAKeyProvider() {
@Override
public RSAPublicKey getPublicKeyById(String kid) {
//Received 'kid' value might be null if it wasn't defined in the Token's header
RSAPublicKey publicKey = jwkStore.get(kid);
return (RSAPublicKey) publicKey;
}
@Override
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
@Override
public String getPrivateKeyId() {
return privateKeyId;
}
};
Algorithm algorithm = Algorithm.RSA256(keyProvider);
//Use the Algorithm to create and verify JWTs.
創建Token并對其進行簽名
首先你需要使用JWT.create()
來創建一個JWTCreator
實例。其次使用其自帶的builder來自定義你的Token
所需要的聲明。最后通過調用sign()
獲取Token字符串并且傳遞Algorithm
實例。
- 以下例子使用
HS256
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}
*以下例子使用RS256
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}
如果無法將Claim轉換為JSON或簽名過程中使用的密鑰無效,則會引發JWTCreationException類型的異常。
驗證Token
首先你需要調用JWT.require()
來創建一個JWTVerifier
實例,并且傳遞0Algorithm
實例。如果你想將一些指定的Claim囊括在Token
中,可以使用builder
來定義這些Calim。通過build()
方法獲得的實例是可以重復使用的,因此你只需要定義它一次,便可以用其來驗證不同的Token.最后調用verify()
傳遞Token.
- 例子( 使用
HS256
)
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 (JWTVerificationException exception){
//Invalid signature/claims
}
*例子(使用RS256
)
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
如果令牌具有無效簽名或未滿足Claim要求,則會引發JWTVerificationException
。
Time Validation
JWT token中可能包含DateNumber 字段,該字段主要用來驗證:
- 該Token是在過去發行的,即
"iat" < TODAY
- 該Token目前為止沒有過期,即
"exp" > TODAY
- 該Token已經可以使用了,即
"nbf" < TODAY
在驗證Token
時,時間驗證會自動執行驗證,當Token
的值被驗證為無效時會拋出JWTVerificationException
類型的異常信息。如果缺少任何先前的字段,則不會進行時間驗證。
給應該仍然被視為有效的Token指定一個 leeway window(說心理話,原諒我水平真的不夠,我在google找了很多關于 leeway window的含義,卻沒能找到合理的解答,我也不知道其指的是什么意思。如果有哪位大神知道,歡迎留言幫忙,現在我只能硬生生的進行翻譯
),使用JWTVerifier
builder的方法acceptLeeway()
,并傳遞一個正數值(表示秒數).
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) // 1 sec for nbf, iat and exp
.build();
您還可以為給定的Date聲明指定自定義值,并僅覆蓋該聲明的默認值。
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) //1 sec for nbf and iat
.acceptExpiresAt(5) //5 secs for exp
.build();
如果您需要在lib / app中測試這個特性,你可以通過將Verification
實例強制轉換為Verification
來獲得能接受自定義Clock的verification.build()方法。例如。:
BaseVerification verification = (BaseVerification) JWT.require(algorithm)
.acceptLeeway(1)
.acceptExpiresAt(5);
Clock clock = new CustomClock(); //Must implement Clock interface
JWTVerifier verifier = verification.build(clock);
Decode a Token
Token解碼
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//Invalid token
}
如果Token中帶有無效語法或者標頭或有效負載不是JSON,則會引發JWTDecodeException`類型的異常。
Header Claims(Header 聲明)
Algorithm(算法) ("alg")
返回算法的值(如果Header中沒有定義算法(alg
)則返回null
)
String algorithm = jwt.getAlgorithm();
Type(類型) ("typ")
返回類型的值(如果Header中沒有定義類型(typ
)則返回null
)
String type = jwt.getType();
Content Type(內容類型) ("cty")
返回內容類型(Content Type)的值(如果Header中沒有定義cty
則返回null
)
String contentType = jwt.getContentType();
Key Id(秘鑰ID) ("kid")
返回秘鑰ID(Key Id)的值(如果Header中沒有定義秘鑰Id(kid
)則返回null
)
String keyId = jwt.getKeyId();
Private Claims(私有聲明)
如果你想獲得其他在Token頭部中定義的聲明,可以調用getHeaderClaim()
方法,并以聲明的名稱作為參數。無論什么情況下,該方法都會返回一個Claim(即使沒有找到該名稱的Claim),并且你可以通過調用claim.isNull()
來驗證返回的Claim的值是否為空。
Claim claim = jwt.getHeaderClaim("owner");
使用JWT.create()
創建Token
時,您可以通過調用withHeader()
并傳遞兩個Claim Map來指定標題聲明。
Map<String, Object> headerClaims = new HashMap();
headerClaims.put("owner", "auth0");
String token = JWT.create()
.withHeader(headerClaims)
.sign(algorithm);
簽名完成后,
alg
和typ
值將始終包含在Header
中。
Payload Claims(Payload 聲明)
Issuer(發布者) ("iss")
返回發布者(Issuer)的值(如果Payload
中沒有定義發布者(iss
)則返回null
)
String issuer = jwt.getIssuer();
Subject(主題) ("sub")
返回主題(sub
)的值(如果Payload
中沒有定義主題(sub
)則返回null
)
String subject = jwt.getSubject();
Audience(受眾,即為誰發布的) ("aud")
返回Audience(aud
)的值(如果Payload
中沒有定義Audience(aud
)則返回null
)
List<String> audience = jwt.getAudience();
Expiration Time(過期時間) ("exp")
返回過期時間(exp
)的值(如果Payload
中沒有定義過期時間(exp
)則返回null
)
Date expiresAt = jwt.getExpiresAt();
Not Before (生效時間)("nbf")
返回生效時間(nbf
)的值(如果Payload
中沒有定義生效時間(nbf
)則返回null
)
Date notBefore = jwt.getNotBefore();
Issued At (簽發時間)("iat")
返回簽發時間(iat
)的值(如果Payload
中沒有定義簽發時間(iat
)則返回null
)
Date issuedAt = jwt.getIssuedAt();
JWT ID(編號) ("jti")
返回編號(jti
)的值(如果Payload
中沒有定義編號(jti
)則返回null
)
String id = jwt.getId();
Private Claims(私有聲明)
如果你想獲得其他在Token Payload
中定義的聲明,可以調用getClaims()
或者getClaim()
方法,并以聲明的名稱作為參數。無論什么情況下,該方法都會返回一個Claim(即使沒有找到該名稱的Claim),并且你可以通過調用claim.isNull()
來驗證返回的Claim的值是否為空。
Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name
Claim claim = claims.get("isAdmin");
or
Claim claim = jwt.getClaim("isAdmin");
在使用 JWT.create()
創建Token
時,可以通過調用withClaim()
來指定自定義聲明,并通過傳遞name
和value
兩個參數來指定自定義Claim的名稱和值。
String token = JWT.create()
.withClaim("name", 123)
.withArrayClaim("array", new Integer[]{1, 2, 3})
.sign(algorithm);
你也可以在JWT.require()
中調用withClaim()
并傳遞name
和必要的值作為參數來驗證自定義的聲明。
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("name", 123)
.withArrayClaim("array", 1, 2, 3)
.build();
DecodedJWT jwt = verifier.verify("my.jwt.token");
目前支持的自定義JWT聲明創建和驗證的類型有:
Boolean, Integer, Double, String, Date 和String and Integer兩種類型的數組.
Claim Class(聲明類)
Claim class is a wrapper for the Claim values. It allows you to get the Claim as different class types. The available helpers are:
Claim類是Claim值的包裝器。它允許您將Claim作為不同的類類型。可用的助手是:
Primitives(原函數)
- asBoolean(): Returns the Boolean value or null if it can't be converted.
- asInt(): Returns the Integer value or null if it can't be converted.
- asDouble(): Returns the Double value or null if it can't be converted.
- asLong(): Returns the Long value or null if it can't be converted.
- asString(): Returns the String value or null if it can't be converted.
- asDate(): Returns the Date value or null if it can't be converted. This must be a NumericDate (Unix Epoch/Timestamp). Note that the JWT Standard specified that all the NumericDate values must be in seconds.
- asBoolean():返回布爾值,如果無法轉換,則返回null。
- asInt():返回Integer值,如果無法轉換,則返回null。
- asDouble():返回Double值,如果無法轉換,則返回null。
- asLong():返回Long值,如果無法轉換,則返回null。
- asString():返回String值,如果無法轉換,則返回null。
- asDate():返回Date值,如果無法轉換,則返回null。這必須是NumericDate(Unix Epoch / Timestamp)。請注意,JWT標準指定所有NumericDate類型的值必須以秒為單位。
自定義類和集合
要獲得聲明作為集合,您需要提供要轉換的內容的類類型(Class Type)。
- as(class): 返回強制轉換為Class Type的值。對于集合,您應該使用asArray和asList方法。
- asMap(): 返回強制轉換為Map <String,Object>的值。
- asArray(class): 返回強制轉換為Class Type類型的Array數組的值,如果該值不是JSON數組,則返回null。
- asList(class): 返回強制轉換為Class Type類型的List的值,如果值不是JSON Array,則返回null。
如果值無法轉換為給定的類類型,則會引發JWTDecodeException類型的異常。
關于Auth0(以下我就不翻譯了
)
Auth0可以 :
- Add authentication with multiple authentication sources, either social like Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, among others, or enterprise identity systems like Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider.
- Add authentication through more traditional username/password databases.
- Add support for linking different user accounts with the same user.
- Support for generating signed Json Web Tokens to call your APIs and flow the user identity securely.
- Analytics of how, when and where users are logging in.
- Pull data from other sources and add it to the user profile, through JavaScript rules.
Create a free account in Auth0
- Go to Auth0 and click Sign Up.
- Use Google, GitHub or Microsoft Account to login.
Issue Reporting
If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The Responsible Disclosure Program details the procedure for disclosing security issues.
Author
License
This project is licensed under the MIT license. See the LICENSE file for more info.