jwt
jwt介绍
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
JSON Web令牌(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,以JSON对象的形式在各方之间安全地传输信息。由于该信息是数字签名的,因此可以对其进行验证和信任。
------[摘自官网]
-
jwt的结构
# 令牌构成 1. Header 2. Payload 3. Signature jwt令牌由以上3部分组成-逗号分割(Header.Payload.Signature),其中header和payload使用了Base64编码。 header: 由两部分组成:alg/typ (typ-令牌的类型,alg-签名算法), 一般header部分默认即可。 payload: 包含了用户等相关的信息。可以自定义根据需要添加相关数据 signature: 包含了header, payload的Base64编码结果 和 secret-密钥 如下图所示:
jjwt包介绍
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
java-utils封装
import io.jsonwebtoken.*;
import java.util.Date;
import java.util.Map;
import static io.jsonwebtoken.Jwts.builder;
/**
* Created with IntelliJ IDEA.
*
* @author: HP
* Date: 2021/10/11
* Time: 15:40
* Description: 基于jjwt分装的jwt工具类
*/
public class JwtUtil {
// 可以将其写入配置文件中,在这里直接读取
private static final String SECRET = "@@#!@#@!$!@";
private static final String ISSUER = "paul";
/**
* 生成jws
* @param subject
* @param claims
* @param expiration 有效时间 单位-分钟
* @return
*/
public static String generateJws(String subject, Map<String, Object> claims, Integer expiration) {
SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;
JwtBuilder jwtBuilder = builder(); // 获取 JwtBuilder 实例
if (claims != null && !claims.isEmpty()) {
jwtBuilder.setClaims(claims);
}
if (expiration>0){
long curr = System.currentTimeMillis();
Date date = new Date(curr + expiration * 1000 * 60);
jwtBuilder.setExpiration(date);
}
jwtBuilder.setHeaderParam("typ", "JWT") // 设置header的type
.setSubject(subject) // JWT的主体(一般为用户ID)
.setIssuer(ISSUER) // 签发人
.signWith(hs256, SECRET.getBytes());// 设置密钥
return jwtBuilder.compact();
}
/**
* 获取数据声明
* @param token
* @return
*/
public static Claims getClaims(String token) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return claims;
}
/**
* 合法性校验
* @param token
* @return
* true: 合法
*/
public static Boolean validity(String token){
Claims claims = getClaims(token);
return claims != null;
}
/**
* 是否过期
* @param token
* @return
* (false : 未过期)
*/
public static Boolean isExpired(String token){
Claims claims = getClaims(token);
return !claims.getExpiration().before(new Date());
}
/**
* 获取剩余有效时间
* @param token
* @return
*/
public static Long getRemainTime(String token){
if (isExpired(token)){
Claims claims = getClaims(token);
Date expiration = claims.getExpiration();
return expiration.getTime() - System.currentTimeMillis();
}
return -1L;
}
/**
* 获取用户id (获取subject)
* @return
*/
public static String getSubject(String token){
Claims claims = getClaims(token);
return claims.getSubject();
}
/**
* 获取其他claims字段
* @return
*/
public static Object get(String token,Object key){
Claims claims = getClaims(token);
return claims.get(key);
}
}
常见异常
-
io.jsonwebtoken.MalformedJwtException
io.jsonwebtoken.MalformedJwtException: Unable to read JSON value: {"typ��)]P��������!L��؉
异常原因: token非法 (可以在https://jwt.io/#debugger-io中测试一下)
-
io.jsonwebtoken.SignatureException
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
异常原因:token签名异常 - 签发token时的
签名(signWith)
和解析时的签名(setSigningKey)
的不一致
常见的问题
签名的作用
- 将
Base64编码的header 和 payload
+secret(密钥)
然后将其结果用header中指定的签名算法(默认HS256
) 进行签名,保证JWT没有被篡改过。 (签名不能泄露出去)。 - 注意: jwt的没有任何安全性可言(Base64很容易解密),故payload不要存放用户敏感信息。而,签名(secret)的作用是防止第三方(黑客)拦截请求篡改jwt,篡改后的jwt无法通过校验。
注意事项
-
setClaims
(jjwt)@Test public void testSetClaims(){ JwtBuilder builder = builder() .signWith(SignatureAlgorithm.HS256,"@@!@#".getBytes()) .setSubject("15e154-515a1-12wda"); HashMap<String, Object> claims = new HashMap<>(); claims.put("username", "小明"); claims.put("avator", "www.xxx.xxx"); String jws = builder.setClaims(claims).compact(); System.out.println(jws); } /* /jws: eyJhbGciOiJIUzI1NiJ9.eyJhdmF0b3IiOiJ3d3cueHh4Lnh4eCIsInVzZXJuYW1lIjoi5bCP5piOIn0.kjNcwOm8e44hOLW6jSGiInr2pLqTwIbPa6TCuQ7WRyg */ //解析结果: 其中前面的claims中没有 subject
NOTE: Calling
setClaims
will overwrite any existing claim name/value pairs with the same names that might have already been set. ----[摘自官网]意思就是:调用
setClaims
方法会覆盖所有的payload(清空claims)// 关键源码 public JwtBuilder setClaims(Map<String, Object> claims) { // 直接改变了claims的指向(相当于即清空了原来的claims, 创建了指定的claims) this.claims = Jwts.claims(claims); return this; } public static Claims claims(Map<String, Object> claims) { return new DefaultClaims(claims); }
使用时,要注意
setClaims
方法的调用位置 -
setExpiration
-
问题描述:
setExpiration
无效 (最终生成的jws中没有exp) -
原因:
setExpiration
与setClaims
的顺序错误-
详细分析
// 相关源码 分析: // 显然在ensureClaims中,如果先setExpiration 那么会创建一个新的claims对象并设置expiration,而在后面调用setClaims时会清除掉expiration。导致setExpiration失效 public JwtBuilder setExpiration(Date exp) { if (exp != null) { this.ensureClaims().setExpiration(exp); } else if (this.claims != null) { this.claims.setExpiration(exp); } return this; } public Claims setExpiration(Date exp) { this.setDate("exp", exp); return this; } protected Claims ensureClaims() { if (this.claims == null) { this.claims = new DefaultClaims(); } return this.claims; }
-
-
示例
-
错误实例
public static String generateJws(String subject, Map<String, Object> claims, Integer expiration) { SignatureAlgorithm hs256 = SignatureAlgorithm.HS256; JwtBuilder jwtBuilder = builder(); // 获取 JwtBuilder 实例 if (expiration>0){ long curr = System.currentTimeMillis(); Date date = new Date(curr + expiration * 1000 * 60); jwtBuilder.setExpiration(date); } if (claims != null && !claims.isEmpty()) { jwtBuilder.setClaims(claims); } jwtBuilder.setHeaderParam("typ", "JWT") // 设置header的type .setSubject(subject) // JWT的主体(一般为用户ID) .setIssuer(ISSUER) // 签发人 .signWith(hs256, SECRET.getBytes());// 设置密钥 return jwtBuilder.compact(); }
-
正确实例
// .... JwtBuilder jwtBuilder = builder(); // 获取 JwtBuilder 实例 // 先设置claims if (claims != null && !claims.isEmpty()) { jwtBuilder.setClaims(claims); } // 后设置expiration if (expiration>0){ long curr = System.currentTimeMillis(); Date date = new Date(curr + expiration * 1000 * 60); jwtBuilder.setExpiration(date); } // .....
-
-