(只可以做权限时使用,敏感数据不要使用)
Java JWT
- 安装Maven
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>
- 可用的算法
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 |
- 用法
选择算法
该算法定义了如何签名和验证令牌。在HMAC算法或密钥对KeyProvider
的情况下或在RSA和ECDSA算法的情况下,它可以用秘密的原始值来实例化。创建后,该实例可重复用于令牌签名和验证操作。
使用RSA或ECDSA算法时,您只需要签署 JWT,就可以避免通过传递null
值来指定公钥。当您只需要验证 JWT 时,可以使用私钥完成相同的操作。
使用静态密码或密钥:
//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);
使用KeyProvider:
通过使用一个KeyProvider
您可以在运行时更改用于验证令牌签名或为RSA或ECDSA算法签署新令牌的密钥。这是通过实施或者实现RSAKeyProvider
或ECDSAKeyProvider
方法:
getPublicKeyById(String kid)
:在令牌签名验证期间调用它,它应该返回用于验证令牌的密钥。如果正在使用键旋转,例如JWK,它可以使用id获取正确的旋转键。(或者只是一直返回相同的密钥)。getPrivateKey()
:在令牌签名期间调用它,它应该返回将用于签署JWT的密钥。getPrivateKeyId()
:在令牌签名期间调用它,它应该返回标识返回的密钥的idgetPrivateKey()
。该值优于方法中的一个值JWTCreator.Builder#withKeyId(String)
。如果您不需要设置kid
值,请避免使用a实例化算法KeyProvider
。
以下示例显示了这将如何使用JwkStore
,一个虚构的JWK Set实现。要使用JWKS进行简单的键旋转,请尝试使用jwks-rsa-java库。
final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}");
final RSAPrivateKey privateKey = //Get the key instance
final String privateKeyId = //为上面的key创建id
RSAKeyProvider keyProvider = new RSAKeyProvider() {
@Override
public RSAPublicKey getPublicKeyById(String kid) {
//如果未在令牌的标头中定义, 则接收到的子值可能为空
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);
//使用该算法创建和验证jwt。
创建并签署令牌
首先, 您需要通过调用 JWT.create()
来创建 jwt实例。使用生成器定义令牌所需的自定义声明。最后获取 string 令牌调用sign
() 并传递算法实例。
- 使用示例
HS256
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//无效的签名配置/无法转换声明
}
- 使用示例
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){
//无效的签名配置/无法转换声明
}
如果无法将声明转换为JSON或签名过程中使用的密钥无效,JWTCreationException
则会引发。
验证令牌
您首先需要JWTVerifier
通过调用JWT.require()
并传递Algorithm
实例来创建实例。如果您需要令牌具有特定的Claim值,请使用构建器来定义它们。该方法返回的实例build()
是可重用的,因此您可以定义一次并使用它来验证不同的令牌。最后调用verifier.verify()
传递令牌。
- 使用示例
HS256
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //可重用的验证程序实例
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//无效签名/声明
}
- 使用示例
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
}
如果令牌的签名无效或不满足声明要求, 则将引发 jwtvericationexception。
时间验证
JWT令牌可能包含DateNumber字段,可用于验证:
该令牌是在过去的日期发布的 "iat" < TODAY
令牌还没有过期"exp" > TODAY
和
令牌已经可以使用了。 "nbf" < TODAY
验证令牌时,时间验证会自动进行,导致JWTVerificationException
值无效时抛出。如果缺少任何先前的字段,则不会在此验证中考虑这些字段。
指定一个回旋余地窗口(新开的窗口), 其中的令牌仍应被视为有效,请使用构建器中的acceptLeeway()
方法JWTVerifier
并传递正秒值。这适用于上面列出的每个项目。
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) // 1 秒为 nbf, iat 和 exp
.build();
还可以为给定的 date 声明指定自定义值, 并仅覆盖该声明的默认值。
JWTVerifier verifier = JWT.require(algorithm)
.acceptLeeway(1) //1 秒 为 nbf 和 iat
.acceptExpiresAt(5) //5秒为 exp
.build();
如果需要在lib / app中测试此行为,请将Verification
实例强制转换为a BaseVerification
以获取verification.build()
接受自定义的方法的可见性Clock
。例如:
BaseVerification verification = (BaseVerification) JWT.require(algorithm)
.acceptLeeway(1)
.acceptExpiresAt(5);
Clock clock = new CustomClock(); //必须实现 Clock 接口
JWTVerifier verifier = verification.build(clock);
解码令牌
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
//无效token
}
如果令牌的语法无效或者标头或有效负载不是JSON,JWTDecodeException
则会引发。
标题声明
A Algorithm ("alg")
返回Algorithm值,如果未在Header中定义,则返回null。
String algorithm = jwt.getAlgorithm();
Type ("typ")
返回Type值,如果未在Header中定义,则返回null。
String type = jwt.getType();
Content Type ("cty")
返回Content Type值,如果未在Header中定义,则返回null。
String contentType = jwt.getContentType();
Key Id ("kid")
返回Key Id值,如果未在Header中定义,则返回null。
String keyId = jwt.getKeyId();
Private Claims 私人索赔
可以通过调用getHeaderClaim()
并传递Claim名称来获取令牌的Header中定义的其他声明。即使无法找到,也会始终返回索赔。您可以通过调用检查Claim的值是否为null claim.isNull()
。
Claim claim = jwt.getHeaderClaim("owner");
使用 JWT.create()
创建令牌时, 可以通过使用withHeader()
调用同时传递声明映射来指定标头声明。
Map<String, Object> headerClaims = new HashMap();
headerClaims.put("owner", "auth0");
String token = JWT.create()
.withHeader(headerClaims)
.sign(algorithm);
该alg
和typ
值将始终包含在签名过程后的头。
Payload Claims 有效负债索赔
Issuer ("iss")
返回Issuer值,如果未在Payload中定义,则返回null。
String issuer = jwt.getIssuer();
Subject ("sub")
返回Subject值,如果未在Payload中定义,则返回null。
String subject = jwt.getSubject();
Audience ("aud")
返回Audience值,如果未在Payload中定义,则返回null。
List<String> audience = jwt.getAudience();
Expiration Time ("exp")
返回Expiration Time值,如果未在Payload中定义,则返回null。
Date expiresAt = jwt.getExpiresAt();
Not Before ("nbf")
返回Not Before值,如果未在Payload中定义,则返回null。
Date notBefore = jwt.getNotBefore();
Issued At ("iat")
返回Issued At值,如果未在Payload中定义,则返回null。
Date issuedAt = jwt.getIssuedAt();
JWT ID ("jti")
返回JWT ID值,如果未在Payload中定义,则返回null。
String id = jwt.getId();
Private Claims 私人索赔
可以通过调用getClaims()
或getClaim()
传递Claim名称来获取令牌的Payload中定义的其他声明。即使无法找到,也会始终返回索赔。您可以通过调用检查Claim的值是否为null claim.isNull()
。
Map<String, Claim> claims = jwt.getClaims(); //Key is the Claim name
Claim claim = claims.get("isAdmin");
或
Claim claim = jwt.getClaim("isAdmin");
使用 JWT.create() 创建令牌时, 可以通过调用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");
目前支持的自定义JWT声明创建和验证类包括:String和Integer类型的Boolean,Integer,Double,String,Date和Arrays。
索赔等级
Claim类是Claim值的包装器。它允许您将Claim作为不同的类类型。可用的助手是:
原函数
- asBoolean():返回布尔值,如果无法转换,则返回null。
- asInt():返回Integer值,如果无法转换,则返回null。
- asDouble():返回Double值,如果无法转换,则返回null。
- asLong():返回Long值,如果无法转换,则返回null。
- asString():返回String值,如果无法转换,则返回null。
- asDate():返回Date值,如果无法转换,则返回null。这必须是NumericDate(Unix Epoch / Timestamp)。请注意,JWT标准指定所有NumericDate值必须以秒为单位。
自定义类和集合
要获得声明作为集合,您需要提供要转换的内容的类类型。
- 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
则会引发。
public class JWTUtils {
/*
* jwt就是基于json签发token和校验token的一种机制。主要功能是权限验证和存储加密的信息。
* jwt由3部分组成(base64解密工具可以解密):
* eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
* eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
* SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
* header(头信息):解密为{"alg": "HS256","typ": "JWT"}是算法和类型。
* playload(荷载):解密为{"sub": "1234567890","name": "John Doe","iat": 1516239022} 载荷就是存放有效信息的地方。
* verify signature(校验签名):由 base64UrlEncode(header) + "." +base64UrlEncode(payload)。
*/
private JWTUtils() {
}
private final static Logger logger = LoggerFactory.getLogger(JWTUtils.class);
//在验证或签名实例中使用的密钥(自定义和密码加盐差不多)
private static final String SECRET = "";
private static final String KEYID = "";
//使用HS256算法
private static Algorithm algorithm = Algorithm.HMAC256(SECRET);
/**
* 加密
*
* @param name
* @param secret
* @param data
*/
public static String encrypt(String name, Object data) {
try {
//通过调用 JWT.create()来创建 jwt实例
JWTCreator.Builder builder = JWT.create();
builder.withJWTId(KEYID);
//设置过期时间一个小时
builder.withExpiresAt(new Date(System.currentTimeMillis() + 60 * 60 * 1000));
//索赔:添加自定义声明值,完成荷载的信息
builder.withClaim(name, JSONUtils.getJson(data));
//签署:调用sign()传递算法实例
return builder.sign(algorithm);
} catch (JWTCreationException e) {
logger.error("无效的签名配置!", e.getMessage());
}
return null;
}
/***
*校验
* @param token
* @param value
* @return json
*/
public static String verify(String token, String key) {
try {
//这将用于验证令牌的签名
JWTVerifier verifier = JWT.require(algorithm).build();
//针对给定令牌执行验证
DecodedJWT jwt = verifier.verify(token);
//获取令牌中定义的声明
Map<String, Claim> claims = jwt.getClaims();
//返回指定键映射到的值
return claims.get(key).asString();
} catch (JWTVerificationException e) {
logger.error("校验失败或token已过期!", e.getMessage());
}
return null;
}
public static void main(String[] args) {
System.out.println(encrypt("123", "123"));
System.out.println(verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyIxMjMiOiJcIjEyM1wiIiwiZXhwIjoxNTUwODI4ODgyfQ.iWWYDC69IR4KWtcxzqiWTWsIlkw5BlCQq9FpQYPTro0", "123"));
}
}