JWT结构
标头(Header)、有效载荷(Payload)和签名(Signature)
Header
描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
"alg": "HS256",
"typ": "JWT"
}
Payload
JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据, JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
还可以自定义私有字段,一般会把包含用户信息的数据放到payload中,如下例:
{
"sub": "1234567890",
"userId": "123",
"admin": true
}
Payload是未加密的,只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息
Signature
签名哈希部分是对上面两部分数据签名。首先,需要指定一个密钥(secret),该密钥仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)对使用base64编码后的header和payload数据生成签名,以确保数据不会被篡改。
最后分别对以上3部分进行base64算法编码生成字符串,再用.拼接生成最终的JWT token字符串,样例如下:
eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NjEwMDIyMzAsInVzZXJJZCI6MSwiaWF0IjoxNjYwOTE1ODMwfQ.KnJDHdX7DCFiyeFfHDojy4anxDpG6HAykf-qIqlEvdc
JWT验证
前端将token放入headers的Authorization字段,在postman上如下:
后端通过@RequestHeader("Authorization")获取token:
@GetMapping("currentUser")
public Result currentUser(@RequestHeader("Authorization") String token){
// ...
}
在后端接收到客户端发送过来的JWT token之后:
header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据,比如userId,这样后端就知道了这个请求是哪个用户的了。
signature由于使用了不可逆的加密算法,无法解码出原文,因为密钥secretKey只保存在服务端,所以它可以校验header和payload部分的完整性和合法性。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值。
通俗的说,payload告诉我你是谁,我再通过我的密钥验证你说的对不对,我就知道你的身份了。最后通过payload里的userId去数据库里查具体的用户信息,即user对象。
代码实现
依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
工具类
public class JWTUtils {
private static final String jwtToken = "123456Mszlu!@#$$";
public static String createToken(Long userId){
Map<String,Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
.setClaims(claims) // body数据,要唯一,自行设置
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000));// 一天的有效时间
return jwtBuilder.compact();
}
public static Map<String, Object> checkToken(String token){
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
ps :
用户密码加密我采用的是md5Hex算法
String pwd = DigestUtils.md5Hex(password + slat);
依赖
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>