Improved high performance token protocol based on JWT protocol

Time:2020-9-23

preface

I believe you all know that JWT (JSON web token) protocol (if you don’t know it well, you can look at it for 10 minutes to learn about JSON web token (JWT)) is currently used in the industry for cross domain authentication. However, due to the low parsing performance of JSON, many performance problems may not be optimized.
·In order to avoid the performance loss caused by JSON parsing, we can use custom separators to distinguish different elements, and fix the sorting of elements to achieve the ability of the original JSON elements.
·At the same time, the header part is default, because the header part is usually used to declare the token format. However, if both the issuing token and the verifying token are their own system, they can be omitted by default.

Encoder

The encoder part mainly implements the generation of token according to the set sub, exp, BNF and other elements, as follows:

public class SimpleJwtEncoder {

    /**
     *Generate JWT token
     *Implement the introduction of sub, exp and BNF elements. If you need to increase them, you can continue to increase them
     * @param content
     * @return
     */
    public String createToken(String content, String exp, String nbf) throws Exception {

        //Commas distinguish three elements. The first is sub, the second is exp, and the third is NBF. Whether there is a value or not, there will be a corresponding separator
        StringBuilder sb = new StringBuilder();
        sb.append(content);

        sb.append(SimpleJwt.PAYLOAD_SEPARATOR);
        if (exp != null) {
            sb.append(exp);
        }

        sb.append(SimpleJwt.PAYLOAD_SEPARATOR);
        if (nbf != null) {
            sb.append(nbf);
        }

        return Base64.getUrlEncoder().encodeToString(sb.toString().getBytes())
                + "."
                + SignGenerator.genJwtSign(sb.toString());
    }

    /**
     *Generate JWT token
     *Omit BNF and allow building token without passing BNF
     * @param content
     * @return
     */
    public String createToken(String content, String exp) throws Exception {
        return createToken(content, exp, "");
    }

The standard specification hmac256 can be used for signature implementation, and MD5 is also applicable

public class SignGenerator {

    public static String genJwtSign(String content) {
        String originSign = content + CommonConstants.JWT_TOKEN_SECRET;
        return DigestUtils.md5DigestAsHex(originSign.getBytes());
    }
}

Decoder

The decoder needs to parse the incoming token. There are three main stages:
·Escape payload
·Proofread signature
·Effective time of proofreading
The implementation of the above three steps is as follows:

public class SimpleJwtDecoder {
    private String payload;
    private String sub;
    private LocalDateTime exp;
    private LocalDateTime nbf;

    public SimpleJwtDecoder(String token) throws Exception {
        String[] tokenList = StringUtils.split(token, ".");
        String bContent = null;
        String signToken = null;
        if (tokenList.length == 2) {
            bContent = tokenList[0];
            signToken = tokenList[1];
        } else {
            throw new Exception( ErrorCode.ERROR_ TOKEN_ Decrpty, "token decoding error");
        }

        byte[] contentByte = Base64.getUrlDecoder().decode(bContent);
        if (contentByte == null) {
            throw new Exception( ErrorCode.ERROR_ TOKEN_ Decrypty, "content decoding error");
        }

        String payload = new String(contentByte);

        //Verify that the signature is correct
        validateSign(payload, signToken);

        String[] payloadArray = StringUtils.split(payload, SimpleJwt.PAYLOAD_SEPARATOR);

        //If there are only two elements, sub and exp, and one more element is NBF
        if (payloadArray.length == 2) {
            setSub(payloadArray[0]);
            setExp(LocalDateTime.ofEpochSecond(Integer.valueOf(payloadArray[1]),0, ZoneOffset.ofHours(8)));
        } else if (payloadArray.length == 3) {
            setSub(payloadArray[0]);
            setExp(LocalDateTime.ofEpochSecond(Integer.valueOf(payloadArray[1]),0, ZoneOffset.ofHours(8)));
            setNbf(LocalDateTime.ofEpochSecond(Integer.valueOf(payloadArray[2]),0, ZoneOffset.ofHours(8)));
        } else {
            throw new Exception( ErrorCode.ERROR_ TOKEN_ Decrypty, "content decoding error");
        }
        this.payload = payload;
    }

    public void verify() throws Exception {
        long now = System.currentTimeMillis();

        //The time limit has been exceeded
        if (getExp() != null && now > getExp().toInstant(ZoneOffset.of("+8")).toEpochMilli()) {
            throw new Exception( ErrorCode.ERROR_ TOKEN_ Decrpty, "token expired");
        }

        //It's not time to start
        if (getNbf() != null && now < getNbf().toInstant(ZoneOffset.of("+8")).toEpochMilli()) {
            throw new Exception( ErrorCode.ERROR_ TOKEN_ Decrpty, "token has not started yet");
        }
    }

    private void validateSign(String payload, String signToken) throws Exception {
        String sign = SignGenerator.genJwtSign(payload);

        if (!sign.equals(signToken)) {
            throw new Exception( ErrorCode.ERROR_ TOKEN_ Decrpty, "signature verification failed");
        }
    }

    public String getPayload() {
        return payload;
    }

    public void setPayload(String payload) {
        this.payload = payload;
    }

    public String getSub() {
        return sub;
    }

    public void setSub(String sub) {
        this.sub = sub;
    }

    public LocalDateTime getExp() {
        return exp;
    }

    public void setExp(LocalDateTime exp) {
        this.exp = exp;
    }

    public LocalDateTime getNbf() {
        return nbf;
    }

    public void setNbf(LocalDateTime nbf) {
        this.nbf = nbf;
    }
}