JWT的数据结构
JWT是一个很长的字符串,中间用点(.)分隔成三个部分。
示例:
eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MTQyMzczNzQsImlhdCI6MTYxNDIzNzA3NH0.KgV4_BjO_fTYoqP2mmIhf3vA8cN2ml736aoLxYaxu6miwihYsbKXrvwClmoP3QVNXvIfGTen0eGqC3FYWoh1WA
header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{
"alg": "HS256",
"typ": "JWT"
}
- alg属性:签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256)
- typ属性:这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
JAVA 工具类封装
import com.google.common.collect.Maps;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwt Exception ;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
public class TokenUtils {
private static Logger LOGGER = LoggerFactory.getLogger(TokenUtils.class);
/**
* 签名密钥
*/ private static final String SECRET = "mySecret";
/**
* 签发者
*/ private static final String ISSUER = "jenny";
/**
* 过期时间:5分钟
*/ private static Long expiration = new Long(300000);
/**
* 生成 Token
*/ public static String generateToken(String username) {
return generateToken(username, Maps.newHashMap());
}
/**
* 生成 Token
*/ public static String generateToken(String username, Map <String, Object> claims) {
return Jwts.builder()
// 受众
.setAudience(username)
// 面向的用户
.setSubject(username)
// 签发者
.setIssuer(ISSUER)
.setClaims(claims)
// 签发时间
.setIssuedAt(new Date())
// 过期时间
.setExpiration(expiration())
// 签名算法、签名密钥
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
/**
* 从 Token 中获取 username
*/ public static String getUserCodeFromToken(String token) {
String userCode = "";
try {
final Claims claims = getClaimsFromToken(token);
if (Objects.isNull(claims) || StringUtils.isEmpty(claims.getSubject())) {
return userCode;
}
userCode = claims.getSubject();
} catch (Exception e) {
LOGGER.error("ger username from token error. ", e);
}
return userCode;
}
/**
* 从 Token 中获取创建时间
*/ public static Date getIssuedAt(String token) {
Date issuedAt;
try {
final Claims claims = getClaimsFromToken(token);
if (Objects.isNull(claims) || Objects.isNull(claims.getIssuedAt())) {
return null;
}
issuedAt = claims.getIssuedAt();
} catch (Exception e) {
// TODO
issuedAt = null;
}
return issuedAt;
}
/**
* 从 Token 中获取过时时间
*/ public static Date getExpiration(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
if (Objects.isNull(claims) || Objects.isNull(claims.getExpiration())) {
return null;
}
expiration = claims.getExpiration();
} catch (Exception e) {
// TODO
expiration = null;
}
return expiration;
}
private static Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
if (e instanceof ExpiredJwtException) {
throw new RuntimeException("TOKEN超时,请重新登录。");
} else {
LOGGER.error("解析Token信息时发生异常:", e);
claims = null;
}
}
return claims;
}
/**
* 生成过期时间
*/ private static Date expiration() {
return new Date(System.currentTimeMillis() + expiration);
}
/**
* 验证 Token 是否过期
*/ private static Boolean isTokenExpired(String token) {
final Date expiration = getExpiration(token);
if (Objects.isNull(expiration)) {
return true;
}
return expiration.before(new Date());
}
}