JWT源码初识

JWT概述介绍参考如下:

https://blog.csdn.net/weixin_41905047/article/details/129211587

JWT基础实践:

@Component
public class JwtTokenUtils {
    private final Logger log = LoggerFactory.getLogger(JwtTokenUtils.class);
    private static final String AUTH_USER_INFO_KEY = "USER_INFO";
    private static final String ACCOUNT_KEY = "account";
    private static final int SHA_256_SECRET_CHAR_SIZE = 32;
    private static final String DEFAULT_SECRET_FLAG = "default";
    @Value("${security.token.secret-key:default}")
    private String customSecretKeyStr;
    private SecretKey secretKey;
    private long tokenValidityInMilliseconds;

    public JwtTokenUtils() {
    }

    @PostConstruct
    public void init() {
        // 初始化,根据秘钥构建SecretKey
        if (this.customSecretKeyStr != null && !"default".equals(this.customSecretKeyStr)) {
            int size = this.customSecretKeyStr.length();
            int left = 32 - size;
            if (left > 0) {
                StringBuilder stringBuilder = new StringBuilder(this.customSecretKeyStr);

                for(int i = 0; i < left; ++i) {
                    stringBuilder.append(i % 10);
                }
                
                this.secretKey = Keys.hmacShaKeyFor(stringBuilder.toString().getBytes());
            } else {
                this.secretKey = Keys.hmacShaKeyFor(this.customSecretKeyStr.getBytes());
            }
        } else {
            this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        }

        this.tokenValidityInMilliseconds = 864000000L;
    }

    // 创建jwt令牌
    public String createToken(String account) {
        long now = System.currentTimeMillis();
        Date validity = new Date(now + this.tokenValidityInMilliseconds);
        return Jwts.builder().setSubject(account).claim("account", account).setExpiration(validity).signWith(this.secretKey, SignatureAlgorithm.HS256).compact();
    }

    // 解析令牌信息
    public String resolveAccount(String token) {
        Claims claims = (Claims)Jwts.parserBuilder().setSigningKey(this.secretKey).build().parseClaimsJws(token).getBody();
        String account = String.valueOf(claims.get("account"));
        return account;
    }

    // 验证令牌信息
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(this.secretKey).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException var3) {
            this.log.info("Invalid JWT signature.");
            this.log.trace("Invalid JWT signature trace: {}", var3);
        } catch (MalformedJwtException var4) {
            this.log.info("Invalid JWT token.");
            this.log.trace("Invalid JWT token trace: {}", var4);
        } catch (ExpiredJwtException var5) {
            this.log.info("Expired JWT token.");
            this.log.trace("Expired JWT token trace: {}", var5);
        } catch (UnsupportedJwtException var6) {
            this.log.info("Unsupported JWT token.");
            this.log.trace("Unsupported JWT token trace: {}", var6);
        } catch (IllegalArgumentException var7) {
            this.log.info("JWT token compact of handler are invalid.");
            this.log.trace("JWT token compact of handler are invalid trace: {}", var7);
        }

        return false;
    }
}

源码讲解主要包括两部分:

  1. JWT令牌创建; 2.JWT令牌校验解析
    JWT的结构有三部分组成( Header Payload Signature),中间用.分隔,如下面这种形式:
    xxxxx.yyyyy.zzzzz

JWT源码入口:

public final class Jwts {

    public static JwtParserBuilder parserBuilder() {
        // 通过反射创建DefaultJwtParserBuilder,用于解析令牌(token)
        return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder");
    }
    
    
    public static JwtBuilder builder() {
    // 通过反射创建DefaultJwtBuilder 用于创建JWT令牌
    return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder");
    }
}

JWT令牌创建核心源码:

public class DefaultJwtBuilder implements JwtBuilder {
    // 请求头信息
    private Header header;
    // 声明
    private Claims claims;
    // 载荷
    private String payload;

    // 算法
    private SignatureAlgorithm algorithm;
    // 秘钥
    private Key key;
    // 序列化信息
    private Serializer<Map<String,?>> serializer;

    private Encoder<byte[], String> base64UrlEncoder = Encoders.BASE64URL;

    private CompressionCodec compressionCodec;

    // 设置主体
    @Override
    public JwtBuilder setSubject(String sub) {
        if (Strings.hasText(sub)) {
            ensureClaims().setSubject(sub);
        } else {
            if (this.claims != null) {
                claims.setSubject(sub);
            }
        }
        return this;
    }

    // 创建自定义的声明
    @Override
    public JwtBuilder claim(String name, Object value) {
        Assert.hasText(name, "Claim property name cannot be null or empty.");
        if (this.claims == null) {
            if (value != null) {
                ensureClaims().put(name, value);
            }
        } else {
            if (value == null) {
                this.claims.remove(name);
            } else {
                this.claims.put(name, value);
            }
        }

        return this;
    }

    // 设置过期时间
    @Override
    public JwtBuilder setExpiration(Date exp) {
        if (exp != null) {
            ensureClaims().setExpiration(exp);
        } else {
            if (this.claims != null) {
                //noinspection ConstantConditions
                this.claims.setExpiration(exp);
            }
        }
        return this;
    }

    // 设置算法和秘钥信息
    @Override
    public JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException {
        Assert.notNull(key, "Key argument cannot be null.");
        Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
        alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334
        this.algorithm = alg;
        this.key = key;
        return this;
    }

    // 核心方法创建jwt令牌
    @Override
    public String compact() {
        // 序列化处理
        if (this.serializer == null) {
            // try to find one based on the services available
            // TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0
            // use the previous commented out line instead
            this.serializer = LegacyServices.loadFirst(Serializer.class);
        }
        // 载荷必要性校验
        if (payload == null && Collections.isEmpty(claims)) {
            payload = "";
        }
        if (payload != null && !Collections.isEmpty(claims)) {
            throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one.");
        }
        
        // jwt第一部分头部创建
        Header header = ensureHeader();
        JwsHeader jwsHeader;
        if (header instanceof JwsHeader) {
            jwsHeader = (JwsHeader) header;
        } else {
            //noinspection unchecked
            jwsHeader = new DefaultJwsHeader(header);
        }
        // 头部赋值算法信息
        if (key != null) {
            jwsHeader.setAlgorithm(algorithm.getValue());
        } else {
            //no signature - plaintext JWT:
            jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
        }
        if (compressionCodec != null) {
            jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
        }

        // 用base64编码头部信息,组成jwt令牌第一部分
        String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");

        // 用base64编码载荷信息,组成jwt令牌第二部分
        byte[] bytes;
        try {
            bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
        } catch (SerializationException e) {
            throw new IllegalArgumentException("Unable to serialize claims object to json: " + e.getMessage(), e);
        }
        if (compressionCodec != null) {
            bytes = compressionCodec.compress(bytes);
        }
        String base64UrlEncodedBody = base64UrlEncoder.encode(bytes);

        // 组装jwt令牌前两部分信息,中间用.划分
        String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
        
        if (key != null) { //jwt must be signed:
            // 根据算法和秘钥,创建jwt签名处理器
            JwtSigner signer = createSigner(algorithm, key);
            // 把jwt令牌的前两部分,作为入参进行签名,返回base64处理后的签名部分(作为jwt令牌的第三部分)
            String base64UrlSignature = signer.sign(jwt);
            // 组装jwt的第三部分
            jwt += JwtParser.SEPARATOR_CHAR + base64UrlSignature;
        } else {
            // no signature (plaintext), but must terminate w/ a period, see
            // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1
            jwt += JwtParser.SEPARATOR_CHAR;
        }
        // 返回jwt令牌
        return jwt;
    }
}

核心创建源码概述:

  • 载荷(Payload)不能为空校验;
  • jwt头部(Header)创建,并赋值;
  • 用base64编码头部(Header)信息,组成jwt令牌第一部分;
  • 用base64编码载荷(Payload)信息,组成jwt令牌第二部分;
  • 组装jwt令牌前两部分信息,中间用.划分;
  • 根据算法和秘钥,创建jwt签名处理器;
  • 把jwt令牌的前两部分,作为入参进行签名,返回base64处理后的签名部分(作为jwt令牌的第三部分),创建完成。

JWT令牌解析+校验核心源码:

public class DefaultJwtParserBuilder implements JwtParserBuilder {
    
    private byte[] keyBytes;
    // 秘钥
    private Key key;

    private SigningKeyResolver signingKeyResolver;

    private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();

    private Decoder<String, byte[]> base64UrlDecoder = Decoders.BASE64URL;

    private Deserializer<Map<String, ?>> deserializer;


    // 设置秘钥
    @Override
    public JwtParserBuilder setSigningKey(Key key) {
        Assert.notNull(key, "signing key cannot be null.");
        this.key = key;
        return this;
    }

}
public class DefaultJwtParser implements JwtParser {

    private static final int MILLISECONDS_PER_SECOND = 1000;

    // 秘钥byte信息
    private byte[] keyBytes;
    // 秘钥信息
    private Key key;
    // 签名解析
    private SigningKeyResolver signingKeyResolver;

    private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();

    private Decoder<String, byte[]> base64UrlDecoder = Decoders.BASE64URL;

    private Deserializer<Map<String, ?>> deserializer;
    
    private Claims expectedClaims = new DefaultClaims();

    private Clock clock = DefaultClock.INSTANCE;

    private long allowedClockSkewMillis = 0;

    // 解析jwt令牌入口
    @Override
    public Jws<Claims> parseClaimsJws(String claimsJws) {
        return parse(claimsJws, new JwtHandlerAdapter<Jws<Claims>>() {
            @Override
            public Jws<Claims> onClaimsJws(Jws<Claims> jws) {
                return jws;
            }
        });
    }


    @Override
    public <T> T parse(String compact, JwtHandler<T> handler)
        throws ExpiredJwtException, MalformedJwtException, SignatureException {
        Assert.notNull(handler, "JwtHandler argument cannot be null.");
        Assert.hasText(compact, "JWT String argument cannot be null or empty.");
        // 解析jwt token
        Jwt jwt = parse(compact);

        // 结果返回
        if (jwt instanceof Jws) {
            Jws jws = (Jws) jwt;
            Object body = jws.getBody();
            if (body instanceof Claims) {
                return handler.onClaimsJws((Jws<Claims>) jws);
            } else {
                return handler.onPlaintextJws((Jws<String>) jws);
            }
        } else {
            Object body = jwt.getBody();
            if (body instanceof Claims) {
                return handler.onClaimsJwt((Jwt<Header, Claims>) jwt);
            } else {
                return handler.onPlaintextJwt((Jwt<Header, String>) jwt);
            }
        }
    }

    // 解析+校验jwt token核心处理方法(jwt令牌格式header.payload.signature)
    @Override
    public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {

        // 序列化赋值
        if (this.deserializer == null) {
            this.deserializer = LegacyServices.loadFirst(Deserializer.class);
        }

        Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
        // 判断jwt token是否合法
        if ("..".equals(jwt)) {
            String msg = "JWT string '..' is missing a header.";
            throw new MalformedJwtException(msg);
        }

        String base64UrlEncodedHeader = null;
        String base64UrlEncodedPayload = null;
        String base64UrlEncodedDigest = null;

        int delimiterCount = 0;

        StringBuilder sb = new StringBuilder(128);

        // jwt token解析Header Payload
        for (char c : jwt.toCharArray()) {
            if (c == SEPARATOR_CHAR) {
                CharSequence tokenSeq = Strings.clean(sb);
                String token = tokenSeq != null ? tokenSeq.toString() : null;

                if (delimiterCount == 0) {
                    base64UrlEncodedHeader = token;
                } else if (delimiterCount == 1) {
                    base64UrlEncodedPayload = token;
                }
                delimiterCount++;
                sb.setLength(0);
            } else {
                sb.append(c);
            }
        }
        // 合法性校验
        if (delimiterCount != 2) {
            String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
            throw new MalformedJwtException(msg);
        }
        // 签名解析
        if (sb.length() > 0) {
            base64UrlEncodedDigest = sb.toString();
        }


        // =============== Header =================
        Header header = null;
        CompressionCodec compressionCodec = null;
        // 头部信息解析
        if (base64UrlEncodedHeader != null) {
            // base64解码
            byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader);
            String origValue = new String(bytes, Strings.UTF_8);
            // 转化为map结构
            Map<String, Object> m = (Map<String, Object>) readValue(origValue);

            if (base64UrlEncodedDigest != null) {
                header = new DefaultJwsHeader(m);
            } else {
                header = new DefaultHeader(m);
            }

            compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
        }

        // =============== Body =================
        // 负荷解析
        String payload = ""; // https://github.com/jwtk/jjwt/pull/540
        if (base64UrlEncodedPayload != null) {
            byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedPayload);
            if (compressionCodec != null) {
                bytes = compressionCodec.decompress(bytes);
            }
            payload = new String(bytes, Strings.UTF_8);
        }

        Claims claims = null;

        if (!payload.isEmpty() && payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it:
            // 提取负荷中声明信息
            Map<String, Object> claimsMap = (Map<String, Object>) readValue(payload);
            claims = new DefaultClaims(claimsMap);
        }

        // =============== Signature =================
        // 校验签名部分
        if (base64UrlEncodedDigest != null) { //it is signed - validate the signature
            // 获取头部签名算法
            JwsHeader jwsHeader = (JwsHeader) header;
            SignatureAlgorithm algorithm = null;
            if (header != null) {
                String alg = jwsHeader.getAlgorithm();
                if (Strings.hasText(alg)) {
                    algorithm = SignatureAlgorithm.forName(alg);
                }
            }
            // 算法校验
            if (algorithm == null || algorithm == SignatureAlgorithm.NONE) {
                //it is plaintext, but it has a signature.  This is invalid:
                String msg = "JWT string has a digest/signature, but the header does not reference a valid signature " +
                    "algorithm.";
                throw new MalformedJwtException(msg);
            }
            // 秘钥校验
            if (key != null && keyBytes != null) {
                throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either.");
            } else if ((key != null || keyBytes != null) && signingKeyResolver != null) {
                String object = key != null ? "a key object" : "key bytes";
                throw new IllegalStateException("A signing key resolver and " + object + " cannot both be specified. Choose either.");
            }
            
            // 秘钥处理
            Key key = this.key;
            if (key == null) { //fall back to keyBytes

                byte[] keyBytes = this.keyBytes;

                if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver
                    if (claims != null) {
                        key = signingKeyResolver.resolveSigningKey(jwsHeader, claims);
                    } else {
                        key = signingKeyResolver.resolveSigningKey(jwsHeader, payload);
                    }
                }

                if (!Objects.isEmpty(keyBytes)) {

                    Assert.isTrue(algorithm.isHmac(),
                        "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");

                    key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
                }
            }

            Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed.");

            //组装jwt 令牌前两部分 header.payload
            String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR;
            if (base64UrlEncodedPayload != null) {
              jwtWithoutSignature += base64UrlEncodedPayload;
            }

            JwtSignatureValidator validator;
            try {
                // 创建签名校验器
                algorithm.assertValidVerificationKey(key); //since 0.10.0: https://github.com/jwtk/jjwt/issues/334
                validator = createSignatureValidator(algorithm, key);
            } catch (WeakKeyException e) {
                throw e;
            } catch (InvalidKeyException | IllegalArgumentException e) {
                String algName = algorithm.getValue();
                String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
                    "algorithm, but the specified signing key of type " + key.getClass().getName() +
                    " may not be used to validate " + algName + " signatures.  Because the specified " +
                    "signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
                    "this algorithm, it is likely that the JWT was not expected and therefore should not be " +
                    "trusted.  Another possibility is that the parser was configured with the incorrect " +
                    "signing key, but this cannot be assumed for security reasons.";
                throw new UnsupportedJwtException(msg, e);
            }
            // 校验传过来的令牌签名部分和令牌传过来的前两部分签名后是否一致
            if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) {
                String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " +
                    "asserted and should not be trusted.";
                throw new SignatureException(msg);
            }
        }

        final boolean allowSkew = this.allowedClockSkewMillis > 0;

        // 声明不为空
        if (claims != null) {

            final Date now = this.clock.now();
            long nowTime = now.getTime();

            // 获取过期时间,校验jwt 令牌是否过期
            Date exp = claims.getExpiration();
            if (exp != null) {
                
                long maxTime = nowTime - this.allowedClockSkewMillis;
                Date max = allowSkew ? new Date(maxTime) : now;
                // 校验是否过期
                if (max.after(exp)) {
                    String expVal = DateFormats.formatIso8601(exp, false);
                    String nowVal = DateFormats.formatIso8601(now, false);

                    long differenceMillis = maxTime - exp.getTime();

                    String msg = "JWT expired at " + expVal + ". Current time: " + nowVal + ", a difference of " +
                        differenceMillis + " milliseconds.  Allowed clock skew: " +
                        this.allowedClockSkewMillis + " milliseconds.";
                    throw new ExpiredJwtException(header, claims, msg);
                }
            }

            Date nbf = claims.getNotBefore();
            if (nbf != null) {

                long minTime = nowTime + this.allowedClockSkewMillis;
                Date min = allowSkew ? new Date(minTime) : now;
                // 校验是否过期
                if (min.before(nbf)) {
                    String nbfVal = DateFormats.formatIso8601(nbf, false);
                    String nowVal = DateFormats.formatIso8601(now, false);

                    long differenceMillis = nbf.getTime() - minTime;

                    String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal +
                        ", a difference of " +
                        differenceMillis + " milliseconds.  Allowed clock skew: " +
                        this.allowedClockSkewMillis + " milliseconds.";
                    throw new PrematureJwtException(header, claims, msg);
                }
            }

            validateExpectedClaims(header, claims);
        }

        Object body = claims != null ? claims : payload;
        // 校验成功,组装后的令牌信息
        if (base64UrlEncodedDigest != null) {
            return new DefaultJws<>((JwsHeader) header, body, base64UrlEncodedDigest);
        } else {
            return new DefaultJwt<>(header, body);
        }
    }


     // 把json转化成map结构
     protected Map<String, ?> readValue(String val) {
        try {
            byte[] bytes = val.getBytes(Strings.UTF_8);
            return deserializer.deserialize(bytes);
        } catch (DeserializationException e) {
            throw new MalformedJwtException("Unable to read JSON value: " + val, e);
        }
    }

  
}

核心解析+校验源码概述:

  • 判断jwt token是否合法;
  • 根据分隔符.,解析jwt token的头部(Header)、负荷(Payload),签名(Signature);
  • base64解码头部信息,并把相应值赋值头部对象;
  • base64解码负荷信息并提取负荷中声明部分;
  • 提取头部签名算法及传递过来的秘钥信息,组装签名校验器;
  • 组装jwt令牌前两部分(header.payload);
  • 校验传过来的令牌签名部分和上一步组装的信息通过签名器签名后是否一致;
  • 负荷(Payload)中声明(Claim)中过期时间不为空,校验令牌是否失效。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值