根據 jwt 協議而改進的高性能 token 協議

前言

相信你們都知道 jwt(json web token)協議(若是不太瞭解能夠看 10分鐘瞭解JSON Web令牌(JWT))是目前業界經常使用於跨域身份驗證的約定方案,可是因爲 json 解析性能過低,可能產生許多性能沒法優化的問題。
· 爲了能避免 json 解析所帶來的性能損耗,咱們可使用自定義的分隔符區分不一樣元素,同時固定元素排序,達到原有 json 元素的能力。
· 同時缺省 header 部分,由於 header 部分一般用於聲明 token 格式,但若是頒發 token 和校驗 token 都是本身系統,徹底能夠缺省掉。web

Encoder

Encoder 部分主要實現根據設置的 sub、exp、bnf 等元素生成 token 以下列簡要實現:json

public class SimpleJwtEncoder {

    /**
     * 生成 jwt token
     * 實現 sub、exp、bnf 三種元素的傳入,如需增長能夠繼續增長
     * @param content
     * @return
     */
    public String createToken(String content, String exp, String nbf) throws Exception {

        // 逗號區分 3 個元素,第一項是 sub,第二項是 exp,第三項是 nbf,不管是否有值,都會有對應的分隔符
        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());
    }

    /**
     * 生成 jwt token
     * 省略 bnf,容許不傳 bnf 來構建 token
     * @param content
     * @return
     */
    public String createToken(String content, String exp) throws Exception {
        return createToken(content, exp, "");
    }

而簽名實現使用標準規範 Hmac256 也行,使用 md5 都行:跨域

public class SignGenerator {

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

Decoder

Decoder 須要對傳入的 token 進行解析,主要階段有 3 個:
· 轉義 payload
· 校對簽名
· 校對有效時間
針對上述三步操做的簡要實現以下:app

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 解碼錯誤");
        }

        byte[] contentByte = Base64.getUrlDecoder().decode(bContent);
        if (contentByte == null) {
            throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "content 解碼錯誤");
        }

        String payload = new String(contentByte);

        //  驗證簽名是否正確
        validateSign(payload, signToken);

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

        // 若是隻有兩個元素就是 sub 和 exp,再加一個元素的話就是 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_DECRPTY, "content 解碼錯誤");
        }
        this.payload = payload;
    }

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

        // 已超過限定時間
        if (getExp() != null && now > getExp().toInstant(ZoneOffset.of("+8")).toEpochMilli()) {
            throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "token 已過時");
        }

        // 還沒到開始時間
        if (getNbf() != null && now < getNbf().toInstant(ZoneOffset.of("+8")).toEpochMilli()) {
            throw new Exception(ErrorCode.ERROR_TOKEN_DECRPTY, "token 還沒開始");
        }
    }

    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, "驗籤失敗");
        }
    }

    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;
    }
}
相關文章
相關標籤/搜索