JWT(即json web token),大家先看下面这张图
大家可以观察到,jwt String就是生成后的jwt字符集,其中有两个 "."(注意:jwt校验会对"."个数校验,多或少都会校验失败),被"."分割的就是jwt的三个构成部分,即:header、payload、sign。
接下来,给大家讲下jwt的生成规则和校验规则
JWT生成规则:
1、设置加密方式、claims信息(即payload)和signingkey
2、设置加密方式为header,并进行base编码
3、设置claims信息为payload,并进行base64编码
4、对header和payload用"."拼接成jwt,,使用signingkey按照加密方式进行加密,生成sign
5、对Jwt和sign用"."生成最终的jwt
JWT校验规则:
1、设置jwt和signingkey
2、按"."对jwt分成三部分,即:header、payload、sign
3、取第一部分进行base64解码,获取加密方式
4、取第二部分进行base64解码,获取业务参数,即payload
5、使用加密方式和signingkey创建校验器,对header+payload进行加密,并与sign(即第三部分)进行对比
6、取出payload的有效期进行校验,是否过期
7、通过校验,返回claims信息
附:
1、进行base64编码时,会判断是否为android客户端,使用base64的库不一样,这点可以自行看源码。
2、jwt会对生成的base64字符集的特殊符号进行转换,"-"换为"+",“_”换位"/",去掉尾部"="
3、jwt校验时,会判断是否有且只有两个".",否则校验失败
原理总结:
1、三部分里的header和payload是独立的,无须signingkey,只需base64解码即可看到payload的信息,所以千万不要在payload里放敏感信息
2、如果是使用jwt作为登录态校验,建议使用对称加密,因为非对称解密效率相对较慢,较多请求下会影响性能
3、因jwt是无状态的,之前见很多同学使用redis进行存储,我不是很明白,这岂不是违反了jwt当初的设计原则
4、保证密钥不要泄露,否则jwt可以被伪造
5、必要情况下,建议使用https
另附上个人的代码供大家参考
package com.yhc.demo.plugin; import java.io.UnsupportedEncodingException; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; /** * JwtToken工具类 */ @Configuration public class JwtService { private static final Logger log = LoggerFactory.getLogger(JwtService.class); @Value("${jwt.secret:123456}") private String secret; @Value("${jwt.expiration:60}") private Long expiration; /** * 生成token * * @param username * @return token */ public String generateToken(String username) { Claims claims = Jwts.claims(); claims.setIssuer(username); // jwt发行人 claims.setIssuedAt(new Date()); // jwt生成时间 claims.setExpiration(getExp()); // jwt过期时间 claims.setSubject("auth"); // jwt主题 claims.setAudience("yhc"); // jwt接受方 claims.setId("uuid"); // jwt唯一身份标识 claims.setNotBefore(new Date()); // jwt在此之前不可用 return generateToken(claims); } /** * 刷新token * * @param old token * @return new token */ public String refreshToken(String token) { Claims claims = validToken(token); if (claims == null) { return null; } claims.setIssuedAt(new Date()); claims.setExpiration(getExp()); return generateToken(claims); } /** * 根据token获取发行人 * * @param token * @return issuer */ public String getIssuer(String token) { Claims claims = validToken(token); return claims.getIssuer(); } /** * 校验jwtToken,如无效,则返回Null,反之,返回负载对象 */ private Claims validToken(String token) { Claims claims = getClaimsFromToken(token); if (claims != null) { Date exp = claims.getExpiration(); if (exp.before(new Date())) { log.warn("# jwtToken已失效:{}", token); throw new RuntimeException("invalid token"); } } return claims; } /** * 从token中获取JWT中的负载参数 */ private Claims getClaimsFromToken(String token) { Claims claims = null; try { claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { log.warn("# jwtToken格式验证失败:{}", token, e); throw new RuntimeException("token verification failed"); } return claims; } /** 根据负载参数生成token */ private String generateToken(Claims claims) { return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact(); } /** 获取token有效期 */ private Date getExp() { return new Date(System.currentTimeMillis() + expiration * 1000); } public static void main(String[] args) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException, UnsupportedEncodingException { String username = "yhc"; Claims claims = Jwts.claims(); claims.setIssuer(username); // jwt发行人 claims.setIssuedAt(new Date()); // jwt生成时间 claims.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)); // jwt过期时间 claims.setSubject("test"); // jwt主题 claims.setAudience("yhc"); // jwt接受方 claims.setId("uuid"); // jwt唯一身份标识 // claims.setNotBefore(new Date()); // jwt在此之前不可用 String visitTK = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, "sad12f").compact(); System.out.println(visitTK);// SystemValue.JWT_HEADER_VALUE_PREFIX + Claims claimsDecode = Jwts.parser().setSigningKey("sad12f").parseClaimsJws(visitTK).getBody(); System.out.println(claimsDecode.getIssuer()); } }
以上纯为个人总结,如有错误,还请指出,谢谢。