java JWT api

(只可以做权限时使用,敏感数据不要使用)

Java JWT

  • 安装Maven
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.7.0</version>
</dependency>
  • 可用的算法
JWSAlgorithmDescription
HS256HMAC256HMAC with SHA-256
HS384HMAC384HMAC with SHA-384
HS512HMAC512HMAC with SHA-512
RS256RSA256RSASSA-PKCS1-v1_5 with SHA-256
RS384RSA384RSASSA-PKCS1-v1_5 with SHA-384
RS512RSA512RSASSA-PKCS1-v1_5 with SHA-512
ES256ECDSA256ECDSA with curve P-256 and SHA-256
ES384ECDSA384ECDSA with curve P-384 and SHA-384
ES512ECDSA512ECDSA 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算法签署新令牌的密钥。这是通过实施或者实现RSAKeyProviderECDSAKeyProvider方法:

  • getPublicKeyById(String kid):在令牌签名验证期间调用它,它应该返回用于验证令牌的密钥。如果正在使用键旋转,例如JWK,它可以使用id获取正确的旋转键。(或者只是一直返回相同的密钥)。
  • getPrivateKey():在令牌签名期间调用它,它应该返回将用于签署JWT的密钥。
  • getPrivateKeyId():在令牌签名期间调用它,它应该返回标识返回的密钥的id getPrivateKey()。该值优于方法中的一个值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则会引发。

标题声明

 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);

algtyp值将始终包含在签名过程后的头。

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的值。对于集合,您应该使用asArrayasList方法。
  • 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"));
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值