JWT是什么?

JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。

JWT有助于在clear(例如在URL中)发送这样的信息,可以被信任为不可读(即加密的)、不可修改的(即签名)和URL – safe(即Base64编码的)。

JWT的定义:

JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。

JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。

因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。

JWT特点:

简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快

自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

JWT构成

JWT主要包含三个部分之间用英语句号’.’隔开
Header 头部
Payload 负载
Signature 签名
注意,顺序是 header.payload.signature

JWT的负载(Payload)

负载(Payload)为JWT的第二部分。JWT的标准所定义了一下几个基本字段:
iss: 该JWT的签发者
sub: 该JWT的使用者
aud: 该JWT的接收者
exp(expires): Unix时间戳 过期时间
iat(issued at): Unix时间戳 签发时间

Java-Jwt中特别定义Payload

nbf(not before):Unix时间戳 生效时间
jti:JwtID JWT的唯一标识
kid: 返回KeyID

Jwt使用三部曲

添加依赖

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>

生成Jwt

  1. 创建加密算法实例

HMAC256 表示加密算法 参数为加密密钥

Algorithm algorithm = Algorithm.HMAC256("securityKey");
  1. 设置Jwt负载
    通过withClaim 或 with 相应熟悉来设置头负载和负载

    JWT.create().withClaim("key", "value")
  2. 签名
    通过sign方法进行签名,需要传入加密算法实例

   JWT.create().withClaim("key", "value").sign(algorithm);
// 或
JWT.create().withClaim("key", "value").sign(Algorithm.HMAC256("securityKey"));

校验Jwt

  1. 创建加密算法实例

HMAC256 表示加密算法 参数为加密密钥

Algorithm algorithm = Algorithm.HMAC256("securityKey");
  1. 创建校验器实例
    通过传入加密算法实例来创建校验器实例
JWTVerifier jwtVerifier = JWT.require(algorithm).build();

在调用build()方法前可添加指定校验参数

如withSubject()方法,当sub属性值不为”user”时将抛出异常InvalidClaimException

JWTVerifier jwtVerifier = JWT.require(algorithm).withSubject("user").build();

需要特别注意关于调整校验签名时间的几个方法

acceptLeeway()、acceptExpiresAt()、acceptNotBefore()、acceptIssuedAt()参数为Long类型参数,且必须是正整数,表示调整时间(单位/秒)

acceptLeeway() nbf、iat 和 exp 延迟时间(单位/秒)

acceptExpiresAt() exp 延迟时间(单位/秒)

acceptNotBefore() nbf 延迟时间(单位/秒)

acceptIssuedAt() iat 延迟时间(单位/秒)

  1. 校验签名

jwtVerifier.verify() 校验器实例方法返回解码后的结果

如果签名内容失效则会抛出SignatureVerificationException异常

当头部信息中包含iat、exp、nbf等属性时,verify()将自动进行校验。不包含的属性不进行校验。

如JWT时间失效,则会抛出TokenExpiredException异常

异常的父类为JWTVerificationException,可用于JWT失效处理。

DecodedJWT decodedJWT = jwtVerifier.verify("JWTString");
// 或
jwtVerifier.verify("JWTString");

解码Jwt

首先在调用校验方法时即可返回解码后类型为DecodedJWT的解码结果

DecodedJWT decodedJWT = jwtVerifier.verify("JWTString");

也可以使用decode()方法进行解码

DecodedJWT decodedJWT = JWT.decode(JwtResoult);

之后便可调用decodedJWT的get方法获取负载的相关内容

Claim (声明,断言的意思)中的asXXX()方法

asBoolean(): 返回 Boolean 值,如果不能转换则返回null。
asInt(): 返回 Int 值,如果不能转换则返回null。
asDouble(): 返回 Double 值,如果不能转换则返回null。
asLong(): 返回 Long 值,如果不能转换则返回null。
asString(): 返回 String值,如果不能转换则返回null。
asDate(): 返回 Date值,如果不能转换则返回null。 必须是一个数字日期 (Unix 系统时间戳)。

注意,JWT标准指定所有的数字日期值必须以秒为单位。

自定义类型和集合:

要获得作为集合的声明,您需要提供要转换的内容的类类型

as(class): 返回指定类型的对象。 对于集合,应该使用asArray和asList方法。
asMap(): 返回被转换为 Map<String, Object> 的集合
asArray(class): 返回被转换为 Class [] 的数组,如果转换失败则返回null。
asList(class): 返回被转换为 List 的集合,如果转换失败则返回null。

由于Java-Jwt默认使用Jackson来处理负载中的Json。所以未进行配置的Jackson可能会由于转义字符的问题而产生下面的异常。

no String-argument constructor/factory method to deserialize from String value

解决办法是使用asString()转换为字符串再使用Json工具进行转换或者配置Jackson的以下配置

允许出现特殊字符和转义符:
configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);

允许出现单引号:
configure(Feature.ALLOW_SINGLE_QUOTES, true);