JWT单点登录源码分析

目录

前言

一、创建token

二、验证token

总结



前言

        JWT在创建的token由header、payload和signature三部分组成,每一部分使用"."分隔,其中header中存储加密的模型,payload中存储需要传输的信息和过期时间等,signature中存储加密后的字符串,使用base64编码提高安全性。在校验的时候也是根据上述规则进行分割,分别进行校验。


一、创建token

       在项目中,我们通过调用JWT.create.sign()方法,创建token,并返给前端写在header中,用作身份证明。

public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException {
            if (algorithm == null) {
                throw new IllegalArgumentException("The Algorithm cannot be null.");
            }
            headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
            headerClaims.put(PublicClaims.TYPE, "JWT");
            String signingKeyId = algorithm.getSigningKeyId();
            if (signingKeyId != null) {
                withKeyId(signingKeyId);
            }
            return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();
        }

           在sign方法中,返回的token由三部分组成,中间用"."隔开,分别是header、payload和signature,其中header中存储algorithm,headerClaims中存储用户的校验规则以及一些自定义字段,signature中存储加密后的字符串,在校验时使用。 

    private String sign() throws SignatureGenerationException {
        String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
        String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
        String content = String.format("%s.%s", header, payload);

        byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
        String signature = Base64.encodeBase64URLSafeString((signatureBytes));

        return String.format("%s.%s", content, signature);
    }


二、验证token

            项目中通过verify方法校验token的有效性。

 public DecodedJWT verify(String token) throws JWTVerificationException {
        DecodedJWT jwt = JWT.decode(token);
        verifyAlgorithm(jwt, algorithm);
        algorithm.verify(jwt);
        verifyClaims(jwt, claims);
        return jwt;
    }

            在verify方法中,首先创建了一个DecodedJWT,基本就是将传进来的token字符串以"."作为分隔符进行分割,然后将前两个部分分别作为headerJson和payloadJson。如果token的格式不正确,即token不是以"."分割的,会抛出JWTDecodeException异常。

    JWTDecoder(String jwt) throws JWTDecodeException {
        parts = TokenUtils.splitToken(jwt);
        final JWTParser converter = new JWTParser();
        String headerJson;
        String payloadJson;
        try {
            headerJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[0]));
            payloadJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[1]));
        } catch (NullPointerException e) {
            throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e);
        }
        header = converter.parseHeader(headerJson);
        payload = converter.parsePayload(payloadJson);
    }
    static String[] splitToken(String token) throws JWTDecodeException {
        String[] parts = token.split("\\.");
        if (parts.length == 2 && token.endsWith(".")) {
            //Tokens with alg='none' have empty String as Signature.
            parts = new String[]{parts[0], parts[1], ""};
        }
        if (parts.length != 3) {
            throw new JWTDecodeException(String.format("The token was expected to have 3 parts, but got %s.", parts.length));
        }
        return parts;
    }

       在verify方法中,将上个方法中获取的header和payload组合成content,然后使用给定方法进行加密,将结果与传入的signature对比,如果相同,校验通过。

    public void verify(DecodedJWT jwt) throws SignatureVerificationException {
        byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
        byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());

        try {
            boolean valid = crypto.verifySignatureFor(getDescription(), secret, contentBytes, signatureBytes);
            if (!valid) {
                throw new SignatureVerificationException(this);
            }
        } catch (IllegalStateException | InvalidKeyException | NoSuchAlgorithmException e) {
            throw new SignatureVerificationException(this, e);
        }
    }

    boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
        return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, contentBytes), signatureBytes);
    }

       在verifyClaims方法中,默认情况下,JWTVerifier就会将过期时间相关的校验添加进去,基本就是对用户规定的过期时间,字段等进行校验,在前面已经将这些字段从token字符串中解析出来,这里只需要做一下判断。

    private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException {
        for (Map.Entry entry : claims.entrySet()) {
            switch (entry.getKey()) {
                case PublicClaims.AUDIENCE:
                    //noinspection unchecked
                    assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue());
                    break;
                case PublicClaims.EXPIRES_AT:
                    assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true);
                    break;
                case PublicClaims.ISSUED_AT:
                    assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false);
                    break;
                case PublicClaims.NOT_BEFORE:
                    assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false);
                    break;
                case PublicClaims.ISSUER:
                    assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue());
                    break;
                case PublicClaims.JWT_ID:
                    assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue());
                    break;
                case PublicClaims.SUBJECT:
                    assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue());
                    break;
                default:
                    assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue());
                    break;
            }
        }
    }


总结

          本文主要对JWT的token生成和校验过程进行分析,如有不足之处,欢迎大家指出。

你可能感兴趣的:(spring,java,java)