什么是JWT?JWT,全称JsonWebToken,是一种基于json的开发标准。下面的文章,将为大家详细地介绍一下JWT的特点、原理和数据结构以及在Java中是怎么去使用的。
JWT特点
JWT默认是不加密,但也是可以加密的。生成原始Token以后,可以用密钥再加密一次。
JWT不加密的情况下,不能将秘密数据写入JWT。
JWT不仅可以用于认证,也可以用于交换信息。有效使用JWT,可以降低服务器查询数据库的次数。
JWT的最大缺点是,由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限。也就是说,一旦JWT签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
1.JWT的原理
Jwt官网:https://jwt.io/
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,就像下面这样。
{
"name":"JohnDoe",
"角色":"管理员",
"到期时间":"2018年7月1日0点0分"
}
以后,用户与服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何session数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
2.JWT的数据结构
JWT大概就像下面这样。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT内部是没有换行的。
JWT的三个组成部分依次如下。
·Header(头部)
·Payload(负载)
·Signature(签名)
写成一行,就是下面的样子
Header.Payload.Signature
2.1Header
Header部分是一个JSON对象,描述JWT的元数据,通常是下面的样子。
{
"alg":"HS256",
"typ":"JWT"
}
上面代码中,alg属性表示签名的算法(algorithm),默认是HMACSHA256(写成HS256);typ属性表示这个令牌(token)的类型(type),JWT令牌统一写为JWT。
最后,将上面的JSON对象使用Base64URL算法转成字符串。
2.2Payload
Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用。
iss(issuer):签发人
exp(expirationtime):过期时间
sub(subject):主题
aud(audience):受众
nbf(NotBefore):生效时间
iat(IssuedAt):签发时间
jti(JWTID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子
{
"sub":"1234567890",
"name":"JohnDoe",
"admin":true
}
JWT默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个JSON对象也要使用Base64URL算法转成字符串。
2.3Signature
Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是HMACSHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
`
算出签名以后,把Header、Payload、Signature三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
3.在Java中使用
3.1引入依赖
<!-- Jwt https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
3.2代码例子
package com.suibibk.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.Base64Codec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
class DemoApplicationTests {
// 加盐秘钥
private static String secret = "jwtSecretValueqweruqwyeiquweyi";
public static void main(String[] args) throws InterruptedException {
// 创建token
String token = createToken(5);
System.out.println("token: " + token);
// 解析token, jwt是经过Base64编码的
String[] ts = token.split("\\.");
System.out.println("Header(头部): " + Base64Codec.BASE64.decodeToString(ts[0]));
System.out.println("Payload(负载): " + Base64Codec.BASE64.decodeToString(ts[1]));
System.out.println("Signature(签名): " + Base64Codec.BASE64.decodeToString(ts[2]));
//这里休息六秒后会导致抛出异常ExpiredJwtException,表明token已经失效了
//Thread.sleep(6 * 1000);
// 解析token
parseToken(token);
}
// 创建token
public static String createToken(Integer time) {
// 自定义信息
Map<String, Object> map = new HashMap<>();
map.put("name", "admin");
String token = Jwts.builder()
.setClaims(map)// 自定义内容接受一个map
.setId("9527") // 唯一id
.setSubject("jwtSubject") // JWT的主体
.setExpiration(new Date(System.currentTimeMillis() + time * 1000))// 设置过期时间 time秒
.setIssuedAt(new Date())//设置签发时间
.signWith(SignatureAlgorithm.HS256, secret)// 设置签名算法和加盐秘钥
.compact();
return token;
}
// 解析token
public static void parseToken(String token) {
System.out.println("====================开始解析JWT====================");
try {
Claims body = Jwts.parser()
.setSigningKey(secret)// 签名秘钥
.parseClaimsJws(token)// 要解析的jwt
.getBody();
System.out.println("id: " + body.getId());
System.out.println("sub: " + body.getSubject());
System.out.println("自定义内容 name: " + body.get("name"));
System.out.println("令牌签发时间: " + body.getIssuedAt());
System.out.println("令牌过期时间: " + body.getExpiration());
} catch (Exception e) {
e.printStackTrace();
System.out.println("无效Token");
}
System.out.println("====================JWT解析结束====================");
}
}
运行输出
token: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqd3RTdWJqZWN0IiwibmFtZSI6ImFkbWluIiwiZXhwIjoxNjg5MDY2OTk3LCJpYXQiOjE2ODkwNjY5OTIsImp0aSI6Ijk1MjcifQ.-b2wY6_Zjeq0GV1xxMK7TEtYYAN_5VXwVj99MwV6CV8
Header(头部): {"alg":"HS256"}
Payload(负载): {"sub":"jwtSubject","name":"admin","exp":1689066997,"iat":1689066992,"jti":"9527"
Signature(签名): ????[乱码,这是因为这里是签名]
====================开始解析JWT====================
id: 9527
sub: jwtSubject
自定义内容 name: admin
令牌签发时间: Tue Jul 11 17:16:32 CST 2023
令牌过期时间: Tue Jul 11 17:16:37 CST 2023
====================JWT解析结束====================
如果放开
//Thread.sleep(6 * 1000);
我们设置的过期时间是5秒,所以休息6秒后就会抛出异常
====================开始解析JWT====================
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2023-07-11T17:19:57Z. Current time: 2023-07-11T17:19:59Z, a difference of 2164 milliseconds. Allowed clock skew: 0 milliseconds.
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:385)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)
at com.suibibk.jwt.DemoApplicationTests.parseToken(DemoApplicationTests.java:55)
at com.suibibk.jwt.DemoApplicationTests.main(DemoApplicationTests.java:30)
无效Token
====================JWT解析结束====================
我们捕获到这个异常就表明已过期了,如果有人篡改就会报签名异常
====================开始解析JWT====================
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:354)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)
at com.suibibk.jwt.DemoApplicationTests.parseToken(DemoApplicationTests.java:55)
at com.suibibk.jwt.DemoApplicationTests.main(DemoApplicationTests.java:30)
无效Token
====================JWT解析结束====================