说明:本翻译仅仅作为个人笔记使用(因为每次看英文文档,看了第一遍,下次再去看又得慢慢翻译一遍,很反感,所以本人决定翻译的同时记录下来),本人水平有限,翻译的不好大家勿喷。当然这篇文章要能解决你的一些问题,那自然是最好的。
Java JWT
Jwt 的Java实现 JSON Web Token (JWT) - RFC 7519.
如果你想要了解Android版本的JWT,可以访问我们的另一个网址JWTDecode.Android .
安装
Maven
com.auth0
java-jwt
3.8.1
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 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 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 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
的值。 - 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
Auth0
License
This project is licensed under the MIT license. See the LICENSE file for more info.