目录
一、JWT 简介
二、JWT 的构成
2.1 头部(Header)
2.2 载荷 (Payload)
2.3 签名
2.4 最后生成出的 jwt
2.5 测试和验证
三、工具类代码
一、JWT 简介
JSON Web Token(JWT) 是一种十分轻巧的规范,这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。
即 JWT 是用于在客户端和服务器之间传递用户信息的一段 JSON 格式的加密字符串,同时 JWT 可以用于让各个微服务识别用户的身份信息。即 JWT 中封装有用户的身份信息。
二、JWT 的构成
一个 JWT 实际上就是一个字符串,它由三部分组成:头部、载荷和签名
2.1 头部(Header)
头部用于描述关于该 JWT 的最基本信息,例如它的类型以及签名所用的算法等,这一部分被可以被表示成一个 JSON 对象。
{"typ":"JWT", "alg":"HS256"}
这段 JSON 字符串指明了签名算法是 HS256 算法,实际使用中这段字符串会被 base64编码,被编码后的字符串为:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
转换地址:http://base64.xpcha.com/
2.2 载荷 (Payload)
载荷就是存放有效信息的地方
假设真实内容为:
{"sub":"1234567890","name":"John Doe","admin":true}
base64 加密后:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
标准中的注册声明(建议但不强制使用):
声明名称 | 作用 |
iss | jwt 的签发者 |
sub | 当前令牌的描述说明 |
aud | 接收 jwt 的一方 |
exp | jwt 的过期时间,这个时间必须大于签发时间 |
nbf | 定义在什么时间之前,这个 jwt 是不可用的 |
iat | jwt 的签发时间 |
jti | jwt 的唯一身份标识,主要用来作为一次性 token,避免重放攻击 |
公共声明:
公共的声明可以添加任何信息,一般添加用户的相关信息或其他业务需要的必要信息,但由于这部分信息在客户端可以被解密,设置这部分可以不加密,故不建议添加敏感信息。这部分信息不参与令牌校验。
私有声明:
私有声明是提供者和消费者共同定义的声明,由于 base64是对称解密,即这部分信息可以被解密,故不建议存放敏感信息。这部分信息不参与令牌校验。
一个载荷的构成:
(标准中注册的声明 + 公共的声明 + 私有声明 ) => base64 加密 => 一个载荷
2.3 签名
jwt 的第三部分是一个签证信息,用于校验数据是否被篡改。签证信息由三部分组成:
- header(base64后的)
- payload(base64后的)
- secret (秘钥,或称盐)
签名的生成方式是:
Base64(Header) . Base64(Payload) + 秘钥(盐) => 加密(使用 Header 中指定的算法加密) => 密文(签名)
例如(数据不准确):
Base64(Header)为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Base64(Payload)为:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
secret为: abcdefg
则签名为: HS256(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.abcdefg) => TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:
secret 是保存在服务器端的, jwt 的签发也是发生在服务器端,可以认为 secret 就是服务器端用于签名的私钥,在任何场景中都不应该泄漏出去,一旦客户端得知这个 secret,就意味着客户端可以自我签发 jwt 了。
签名的作用:
用于校验令牌是否被篡改
由于 secret 是保存在服务器的,若认为篡改了 jwt,则签名部分肯定发生了变化,服务器只要对比签名部分就能够判断出 jwt 是否被篡改。
2.4 最后生成出的 jwt
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7Hg
Q
2.5 测试和验证
/**
* @File: JwtTest
* @Description: 令牌生成和解析测试
* @Author: tom
**/
@Slf4j
public class JwtTest {
private String signKey = "tomTest";
/**
* 创建令牌
*/
@Test
public void testCreateToken() throws InterruptedException {
// 构建jwt令牌对象
JwtBuilder builder = Jwts.builder();
builder.setIssuer("tom"); // 颁发者
builder.setIssuedAt(new Date()); // 颁发日期
builder.setExpiration(new Date(System.currentTimeMillis() + 20000)); // 设置定时,2秒过期
builder.setSubject("jwt 令牌测试"); // 主题测试
builder.signWith(SignatureAlgorithm.HS256, signKey); // 1. 签名算法 2. 秘钥
// 自定义载荷信息
Map<String, Object> userInfo = new HashMap<String,Object>();
userInfo.put("company", "dis");
userInfo.put("address", "北京");
userInfo.put("money", 3500);
// 添加载荷
builder.addClaims(userInfo);
// 获取生成 token
String token = builder.compact();
System.out.println(token);
// Thread.sleep(3000);
parseToken(token);
}
/**
* 令牌解析
*/
public void parseToken(String token) {
Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token).getBody();
System.out.println(claims.toString());
}
}
三、工具类代码
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
/**
* JWT工具类
*/
public class JwtUtil {
// 有效期为
public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时
// 设置秘钥明文
public static final String JWT_KEY = "tomcast";
// 设置颁发者
public static final String JWT_ISS = "tom";
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer(JWT_ISS) // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);// 设置过期时间
return builder.compact();
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析令牌数据
* @param jwt
* @return
*/
public static Claims parseJWT(String jwt) {
SecretKey secretKey = generalKey();
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
}
}