JWT介绍和使用
JWT介绍
JWT(JSON Web Token)是一个开放的标准(RFC 7519),JWT定义了一种简介的、自包含的协议格式。可以用于在通信的双方传递json对象,传递的信息可以被信任,因为信息是被数字签名的。JWT可以使用HMAC算法或者使用RSA/ECDSA算法的公钥/私钥对签名。
JWT格式
JWT包含三部分内容,Header、Plyload和Signature。
-
Header
- 包括令牌的类型以及使用的哈希算法;使用base64url编码后就是JWT的第一部分内容
-
Payload
- Payload的内容是一个json对象,它是存放有效信息的地方,可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳信) ,sub(面向的用户)等;同时也可以自定义字段存放数据,但是不建议存放敏感数据,因为此部分可以解码还原原始内容;使用base64url编码后就是JWT的第二部分内容
-
Signature
- Signature是签名,用于防止jwt内容被篡改,这个部分使用base64url将前两部分进行编码,编码后使用“.” 连接组成字符串,最后使用header中声明的签名算法进行签名。
假设现在一个JWT的Header和Payload部分内容如下
Header:
{
"alg": "HS384",
"typ": "JWT"
}
payload
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
那么Signature的的内容如下所示,“yguyhdqwodqwddqw1”是秘钥
HMACSHA384(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
yguyhdqwodqwddqw1
)
最后生成的JWT如下:
eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.f7IalcwE38sVPor1FBUwAwso6exiwF80y0nM6bLkAvooNFGjCoJXRL0DMpaQB42C
payload和claims介绍
payload和claims的概念在JJWT开源库中使用了,所以这里简单介绍一下。
前面已经讲过了payload是一个JSON对象,包含了实际传输的数据。而Payload的主要目的是携带与特定用户或请求相关的信息,这些信息就被称为“claims”。
Claims(声明)是Payload中包含的具体键值对数据项,它们用来表述特定的声明或声明集,这些声明描述了与JWT相关的事实。根据其用途和规范要求,claims可以分为以下几类:
- Reserved Claims(保留声明): 这些是JWT标准中预定义的、具有特殊含义的claims。虽然它们是可选的,但如果使用,应遵循标准规定的名称和格式。常见的保留claims包括:
- iss(issuer):签发者。
- sub(subject):主题,标识JWT所代表的个体。
- aud(audience):接收方,标识预期的JWT接收者。
- exp(expiration time):过期时间,定义了JWT的有效期截止时间。
- nbf(not before):生效时间,在此时间之前JWT不应被接受处理。
- iat(issued at):签发时间,表明JWT的创建时间。
- jti(JWT ID):唯一标识符,用于防止JWT重放攻击。
- Public Claims(公共声明)
- 这些claims没有在JWT标准中明确规定,但可以通过IANA JSON Web Token Registry进行注册,以确保其名称在全球范围内是唯一的,避免不同系统间的冲突。公共claims通常用于满足特定的应用程序或行业需求。
- Private Claims(私有声明)
- 开发者可以自由定义的claims,用于承载应用程序所需的任何额外信息。私有claims无需注册,其名称由开发者自行决定,如示例中提到的user_id和user_name。这些声明对JWT的使用者具有特定意义,但对JWT标准本身来说是无特殊含义的。
JWT使用
jjwt是Java Json Web Tokens的缩写,它所示一个用于在Java中实现JWT的开源库。
jjwt依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<!-- javax.xml.bind.DatatypeConverter 类已被弃用并从 Java SE 9 中移除-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
这是自己写的JJWT工具类
public class JJWTUtils {
private static final Logger logger = LoggerFactory.getLogger(JJWTUtils.class);
private static final String SECRET = "mpd';dqw823djq3d902";
/**
* 生成token
*/
public static String getToken(Map<String, Object> map) {
//设置内容
JwtBuilder jwtBuilder = Jwts.builder().setClaims(map);
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 100);
//设置过期时间, 100s后过期
jwtBuilder.setExpiration(instance.getTime());
String token = jwtBuilder.signWith(SignatureAlgorithm.HS256, SECRET.getBytes()).compact();
return token;
}
/**
* 校验token
* @param token jwt字符串
* @return
*/
public static boolean verifyToken(String token) {
String[] tokenSplit = token.split(".");
String header = tokenSplit[0];
String payload = tokenSplit[1];
String signature = tokenSplit[2];
String headerJson = new String(Base64.getDecoder().decode(header));
String payloadJson = new String(Base64.getDecoder().decode(payload));
byte[] keyBytes = SECRET.getBytes(StandardCharsets.UTF_8);
Key signingKey = Keys.hmacShaKeyFor(keyBytes);
// 验证JWT签名,如果JWT签名无效,这里会抛出SignatureException异常
boolean result = true;
try {
Jwts.parser().setSigningKey(signingKey).parse(token);
} catch (Exception e) {
result = false;
}
return result;
}
/**
* 获取token中的携带的信息
*/
public static Map<String, Object> getPayload(String token) {
//claims是一个Map,存放了通过JwtBuilder设置进去的内容
Claims claims = Jwts.parser().setSigningKey(SECRET.getBytes())
.parseClaimsJws(token).getBody();
return claims;
}
public static void main(String[] args) {
HashMap<String, Object> payload = new HashMap<>();
payload.put("name", "张三");
payload.put("id", "1");
String token = JJWTUtils.getToken(payload);
logger.info("token: " + token);
Map<String, Object> payload1 = JJWTUtils.getPayload(token);
logger.info("payload1: " + payload1);
}
}
jjwt使用的坑
问题1
java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) … 6 more
解决方法
这个错是由于在Java8以后,javax.xml.bind.DatatypeConverter 类已被弃用并从 Java SE 9 中移除而导致的,所以加入缺少的依赖即可
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
问题2
ava.lang.IllegalStateException: Both ‘payload’ and ‘claims’ cannot both be specified. Choose either one. at io.jsonwebtoken.impl.DefaultJwtBuilder.compact(DefaultJwtBuilder.java:262) at com.example.sso.utils.JJWTUtils.getToken(JJWTUtils.java:38) at com.example.sso.utils.JJWTUtils.main(JJWTUtils.java:58)
解决方法
不能同时设置payload和claims。