什么时候应该使用JWT?
- 授权:这是使用JWT的最常见方式。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
- 信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以通过签名(使用公钥/私钥对 )核实发送人的身份。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
JWT令牌结构
JWT令牌由Header、Payload、Signature三部分组成,每部分之间用点号分隔,通常的形式为xxxxx.yyyyy.zzzzz
,下面分别对每部分做详细介绍。
Header(头)
Header通常由两部分组成:令牌的类型,即JWT,以及使用的签名算法,例如HMAC SHA256或RSA。
{
"alg": "HS256",
"typ": "JWT"
}
这个JSON被编码为Base64Url,形成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令牌,虽然可以防止被篡改,但任何人都可以读取。除非加密,否则不要将秘密信息放在JWT的有效载荷或头元素中。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.
)分隔,就可以返回给用户。
@Component public class JwtTokenUtil implements Serializable { private static final long serialVersionUID = -2550185165626007488L; public static final long JWT_TOKEN_VALIDITY = 5*60*60; @Value("${jwt.secret}") private String secret; public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } // 获取签发时间 public Date getIssuedAtDateFromToken(String token) { return getClaimFromToken(token, Claims::getIssuedAt); } // 获取过期时间 public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } // apply 绑定函数的调用者 public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } // 解包 private Claims getAllClaimsFromToken(String token) { // Type parameters:<B> – the type of the JWT body contents, either a String or a Claims instance. return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } // 判断是否过期 private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } private Boolean ignoreTokenExpiration(String token) { // here you specify tokens, for that the expiration is ignored return false; } // 产生token public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY*1000)).signWith(SignatureAlgorithm.HS512, secret).compact(); } public Boolean canTokenBeRefreshed(String token) { return (!isTokenExpired(token) || ignoreTokenExpiration(token)); } // 校验token public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }