JWT
1、什么是JWT
JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
2、什么时候使用JWT
-
授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够轻松跨不同域使用。
-
信息交换:JSON Web Tokens 是一种在各方之间安全传输信息的好方法。因为 JWT 可以被签名——例如,使用公钥/私钥对——你可以确定发件人就是他们所说的那样。此外,由于使用标头和有效负载计算签名,因此您还可以验证内容是否未被篡改。
3、JWT长什么样?
JWT是由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串。就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
4、JWT的构成
-
头部(header)
-
载荷(payload, 类似于飞机上承载的物品)
-
签证(signature)
header
jwt的头部承载两部分信息:
- 声明类型(typ),这里是jwt
- (alg)声明加密的算法 通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用) :
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后将其进行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
请注意,对于已签名的令牌,此信息虽然受到防篡改保护,但任何人都可以读取。除非加密,否则不要将机密信息放在 JWT 的负载或标头元素中。
signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64加密后的header和base64加密后的payload使用.
连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.
连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
5、JWT应该存储在哪里
每当用户想要访问受保护的路由或资源时,用户代理应该发送 JWT,通常在使用 Bearer
模式的 Authorization
标头中。标题的内容应如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查Authorization
标头中的有效 JWT ,如果存在,则用户将被允许访问受保护的资源。如果 JWT 包含必要的数据,则可能会减少为某些操作查询数据库的需要,尽管情况并非总是如此。
令牌在Authorization
标头中发送,因为它不使用 cookie
,所以跨源资源共享 (CORS) 不会成为问题。
6、JWT 工具类编写
导入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
工具类的编写
public class JWTUtil {
private final static String SECRET = "ZHOUZHAO"; //签名秘钥
private static Integer expiresTimeSeconds = 60 * 30; //有效时长(秒)
/**
* 生成Token的工具类
* @param params payload中负载的个人信息
* @return 返回生成的Token
*/
public static String createToken(Map<String, Object> params) {
//获取过期时间是多少
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, expiresTimeSeconds);
return JWT.create()
.withPayload(params) //可以将基本信息放到claims中 payload
.withExpiresAt(instance.getTime()) //过期时间
.withIssuedAt(new Date()) //签证时间
.sign(Algorithm.HMAC256(SECRET)); //设置HMAC256签名
}
/**
* 验证Token并返回负载信息
* @param token 要验证的Token字符串
* @return 返回负载信息
*/
public static Map<String, Claim> verifyToken(String token) {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaims();
}
/**
* 设置过期时长,单位为秒
* @param seconds 秒
*/
public static void setExpiresTimeSeconds(Integer seconds) {
expiresTimeSeconds = seconds;
}
}
7、总结
-
首先,前端传输账号密码
-
后端核对用户名和密码成功后,将用户的id等其他信息作为
JWT Payload(负载)
,将其与头部分别进行Base64
编码拼接后签名,形成一个JWT(Token)
。 -
后端将
Token
字符串作为登录成功的返回结果返回给前端。前端可以将token
保存在localStorage
或sessionStorage
上,退出登录时前端删除保存的JWT
即可。 -
前端在每次请求时将
token
放入HTTP Header
中的Authorization
位。(解决XSS和XSRF问题) -
后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己
-
验证通过后后端使用
token
中包含的用户信息进行其他逻辑操作,返回相应结果。
评论区