Autor: Mars Sauce
Isenção de responsabilidade: Este artigo foi escrito por Mars Sauce e parte do conteúdo vem da Internet.Se você tiver alguma dúvida, entre em contato comigo.
Reimpressão: Bem-vindo à reimpressão, entre em contato comigo antes de reimprimir!
O que é JWT
O nome completo do JWT é Json Web Token. é baseado no padrão aberto RFC 7519, que define uma maneira compacta e independente de transmitir informações com segurança entre as partes na forma de objetos JSON. Essas informações podem ser usadas para verificação e confiança mútua porque são assinadas digitalmente. O JWT pode ser assinado com uma chave privada (usando o algoritmo HMAC ) ou com um par de chaves pública/privada usando RSA ou ECDSA .
Onde o JWT pode ser usado?
A seguir estão dois cenários de uso do JWT:
授权
: este é o caso de uso mais comum para usar o JWT. Depois que o usuário estiver logado, todas as solicitações subsequentes conterão o JWT, permitindo que o usuário acesse as rotas, serviços e recursos permitidos com esse token. O logon único é um recurso em que o JWT é amplamente usado atualmente devido à sua baixa sobrecarga e facilidade de uso em diferentes domínios.信息交换
: JWT é uma maneira relativamente conveniente de transmitir informações com segurança entre as partes. Como os JWTs podem ser assinados (por exemplo, usando um par de chaves pública/privada), é possível determinar se o remetente é autorizado por você. E, como a assinatura é calculada a partir do cabeçalho e do payload, também é possível verificar se o conteúdo não foi adulterado.
Composição do JWT
Esta é uma string de token JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
É muito complicado, não entende? Na verdade, essa string é a string criptografada do texto cifrado, que é .
dividida passando no meio. Cada .
string anterior representa os três componentes do JWT: Header
, Payload
, Signature
.
Cabeçalho (informações do cabeçalho)
A principal função do Header é identificar. Geralmente consiste em duas partes:
typ
: abreviação de tipo, tipo de token, ou seja, JWT.alg
: abreviação de Algorithm, algoritmo de assinatura criptografada. Geralmente use HS256, o site oficial jwt fornece 12 tipos de algoritmos de criptografia, a captura de tela é a seguinte:
Exemplo de cabeçalho em texto simples:
{
"alg": "HS256",
"typ": "jwt"
}
O texto simples após a codificação Base64 torna-se:
eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9
也就是第一个.
之前的密文串。以下是Header部分常用部分的声明:
key | name | 说明 |
---|---|---|
typ | 令牌类型 | 如果存在,则必须将其设置为已注册的 IANA 媒体类型。 |
cty | 内容类型 | 如果使用嵌套签名或加密,建议将其设置为 ;否则,请省略此字段。 |
alg | 消息身份验证代码算法 | 发行者可以自由设置算法来验证令牌上的签名。但是,某些受支持的算法不安全。 |
kid | 密钥标识 | 指示客户端用于生成令牌签名的密钥的提示。服务器将此值与文件上的密钥匹配,以验证签名是否有效以及令牌是否真实。 |
x5c | x.509 证书链 | RFC4945 格式的证书链,对应于用于生成令牌签名的私钥。服务器将使用此信息来验证签名是否有效以及令牌是否真实。 |
x5u | x.509 证书链网址 | 服务器可在其中检索与用于生成令牌签名的私钥对应的证书链的 URL。服务器将检索并使用此信息来验证签名是否真实。 |
crit | 危急 | 服务器必须理解的标头列表,以便接受令牌为有效令牌 |
Payload(负载信息)
也称为JWT claims,放置需要传输的信息,有三类:
保留claims
:主要包括iss发行者、exp过期时间、sub主题、aud用户等。公共claims
:定义新创的信息,比如用户信息和其他重要信息。私有claims
:用于发布者和消费者都同意以私有的方式使用的信息。
以下是Payload的官方定义内容:
key | name | 说明 |
---|---|---|
iss | 发送者 | 标识颁发 JWT 的发送主体。 |
sub | 主题 | 标识 JWT 的主题。 |
aud | 接收者 | 标识 JWT 所针对的接收者。每个在处理 JWT 的主体都必须使用受众声明中的值来标识自己。如果处理的主体在存在此声明时未将自己标识为声明中的值,则必须拒绝 JWT。 |
exp | 到期时间 | 标识不得接受 JWT 进行处理的过期时间。该值必须是日期类型,而且是1970-01-01 00:00:00Z 之后的日期秒。 |
nbf | jwt的开始处理的时间 | 标识 JWT 开始接受处理的时间。该值必须是日期。 |
iat | jwt发出的时间 | 标识 JWT 的发出的时间。该值必须是日期。 |
jti | jwt id | 令牌的区分大小写的唯一标识符,即使在不同的颁发者之间也是如此。 |
Payload明文示例:
{
"sub": "12344321",
"name": "Mars酱", // 私有claims
"iat": 1516239022
}
经过Base64加密之后的明文,变为:
eyJzdWIiOiIxMjM0NDMyMSIsIm5hbWUiOiJNYXJz6YWxIiwiaWF0IjoxNTE2MjM5MDIyfQ
也就是第一个.
和第二个.
之间的密文串内容。
Signatrue(签名信息)
Signature 部分是对Header和Payload两部分的签名,作用是防止 JWT 被篡改。这个部分的生成规则主要是是公式(伪代码)是:
Header中定义的签名算法(
base64编码(header) + "." + base64编码(payload),
secret
)
secret
是存放在服务端加密使用到的盐。
得到签名之后,把Header的密文、Payload的密文、Signatrue的密文按顺序拼接成为一个字符串,中间通过.
来连接并分割,整个串就是JWT了。
JWT实例
概念讲完了,我们来个实例吧,先来一个jwt的编码:
public static String encodeJWT(String key) {
// 1. 定义header部分内容
Map headerMap = new HashMap();
headerMap.put("alg", SignatureAlgorithm.HS256.getValue());
headerMap.put("typ", "JWT");
// 2. 定义payload部分内容
Map payloadMap = new HashMap();
payloadMap.put("sub", "mars酱让你爽一分钟");
payloadMap.put("iat", UUID.randomUUID());
payloadMap.put("exp", System.currentTimeMillis() + 24 * 60 * 60 * 1000);
payloadMap.put("name", "Mars酱");
payloadMap.put("role", "酱油王");
// 3.生成token
String jwtToken = Jwts.builder()
.setHeaderParams(headerMap)
.setClaims(payloadMap)
.signWith(SignatureAlgorithm.HS256, key)
.compact(); // 拼接header + payload
// System.out.println(jwtToken);
return jwtToken;
}
我们在main函数中调用这个,运行得到结果:
很长的密文字符串,就不截完整了。其中key是盐,jsonwebtoken的jar包规定,key必须字节数要大于等于你所用的加密算法的最小字节数,mars酱这里使用的是HS256,最小的key长度规定的就是256:
因此,key值我这里传入了一个超长的key:
String key = "marsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmarsmars";
如果你的key没这么长,你可能会报这样错误:
下面再来一个解密jwt的代码,入参为加密后的jwt和盐key:
public static void decodeJWT(String jwtToken, String key) {
try {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtToken)
.getBody();
Object sub = claims.get("sub");
Object name = claims.get("name");
Object role = claims.get("role");
Object exp = claims.get("exp");
System.out.println("sub:" + sub + "\nname:" + name.toString() + "\nrole:" + role + "\nexp:" + exp + "\n失效了?" + ((System.currentTimeMillis() - (Long)exp) > 0));
} catch (ExpiredJwtException e) {
System.out.println("mars酱提醒您:token已过期");
}
}
在main中调用后,得到结果:
把生成的jwt字符串放入官网( jwt.io )的解密界面,和程序解密的结果一样,是不是很完美?
优缺点
好了,概念说完了,实例也给了,想想jwt有什么优缺点?我总结了一下:
优点:
可扩展性好
应用程序分布式部署的情况下,如果使用session机制,那就要要做多台机器的数据共享,通常可以存在数据库或者redis里面。而使用jwt不需要共享。jwt是无状态的
jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。
缺点:
安全性
由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。一次性
无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。
jwt开源框架
mars酱这里使用的jwt是io.jsonwebtoken的,它需要在pom中引入依赖:
<!-- jwt api定义 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.2</version>
</dependency>
<!-- jwt api impl实现 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.2</version>
</dependency>
<!-- jwt json -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.2</version>
</dependency>
其余的jwt框架全在jwt官网列举了。基本覆盖了全语言
好好享受jwt的校验过程吧