2

前言

相信大家都知道 jwt(json web token)协议(如果不太了解可以看 10分钟了解JSON Web令牌(JWT))是目前业界常用于跨域身份验证的约定方案,但是由于 json 解析性能太低,可能产生许多性能无法优化的问题。
· 为了能避免 json 解析所带来的性能损耗,我们可以使用自定义的分隔符区分不同元素,同时固定元素排序,达到原有 json 元素的能力。
· 同时缺省 header 部分,因为 header 部分通常用于声明 token 格式,但如果颁发 token 和校验 token 都是自己系统,完全可以缺省掉。

Encoder

Encoder 部分主要实现根据设置的 sub、exp、bnf 等元素生成 token 如下列简要实现:

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
· 校对签名
· 校对有效时间
针对上述三步操作的简要实现如下:

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;
    }
}

sinlong
9 声望0 粉丝