java原生手写jwt

时间:2019-11-18
本文章向大家介绍java原生手写jwt,主要包括java原生手写jwt使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

最近和朋友一起开发的前后端分离项目,我负责后台,他写前端。由于我没有什么实战经验,写的又比较慢(甩锅课很多)他就用node也写了一个后台,实现一个前端两套后台,无缝对接。项目中有用到jwt,本来想的是jwt的工具包,但有一个问题:虽然我们约定了相同的密钥和算法,但工具包签发的jwt只有自己认可,node不认可,而node签发的java不认可。最后发现工具包对密钥进行了加密处理导致密钥不一致而无法无缝对接,只有java后台的话,可以放心使用。
不能用工具包的话,我们就约定手写jwt,按照jwt规范来,废话有点多哈。

一:什么是jwt

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
服务器认证之后生成jwt,返回给用户。用户短期内就可以通过服务器签发的jwt来证实自己的合法性,此方案在一定程度上取代了传统的Cookie/session_id认证。特别是在前后端分离多端适配的情况下。

二:jwt原理

数字1部分是我们日常看到的jwt,由Header(头部)、 Payload(负载)、Signature(签名)三个部分组成通过 “.” 连接。
header对应图片右边的数字2部分,描述jwt的元数据,alg属性表示算法的签名,默认是HS256。typ表面了当前的令牌(token)类型为jwt。
将数字2部分的json数据通过base64URL算法(下面将详细说明)可以得到header部分

2.2、Payload

Payload对应图片右边的数字3部分,用来存放实际需要传递的数据,JWT 规定了7个官方字段,供选用。

    iss (issuer):签发人
    exp (expiration time):过期时间
    sub (subject):主题
    aud (audience):受众
    nbf (Not Before):生效时间
    iat (Issued At):签发时间
    jti (JWT ID):编号

当然也可以自定义自己的数据。同样将数字3部分的json数据通过base64URL算法(下面将详细说明)可以得到Payload部分
是的没错,jwt默认是没有对数据进行加密的,只是通过base64URL算法把数据转化为了字符串,方便传输。为了安全不要把敏感数据放在里面,当然你也可以在对这个部分进行加密。

2.3、Signature

什么?你说都是明文跑的?那还安全个锤子?别急,这个时候就来看看Signature部分
Signature部分是对前两个部分结合密钥(secret),这个密钥只有服务器知道,不能泄露。通过Header里面指定的算法进行加密,默认是HMACSHA256算法。


这样可以防止数据被篡改,虽然算法是公开的,但密钥只有服务器知道。

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

三、手写jwt

知道jwt的实现原理了,我们就可以愉快的手写jwt了,这里总结一下,这就用到了两种算法。一是base64RUL,二是HMACSHA256,刚好jdk8中有这两种算法的原生实现。

3.1、加密实现过程

首先通过base64URL算法对Header和Payload分别加密(这里也不能说是加密),然后通过HMACSHA256算法按一下规则得到签名(Signature )部分,这样还可以防止数据被篡改。
    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)
然后把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

3.2、解密实现过程

解密就非常简单了,通过jwt的header和Payload部分结合密钥用HMACSHA256算法按照规则进行计算,如果得到的结果和第三部分相同则说明当前jwt合法。在通过base64URL解密payload部分拿到数据判断是否过期即可。

四、核心算法

    /**
     * 将加密后的字节数组转换成字符串
     *
     * @param b 字节数组
     * @return 字符串
     */
    public  static String byteArrayToHexString(byte[] b) {
        StringBuilder hs = new StringBuilder();
        String stmp;
        for (int n = 0; b!=null && n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0XFF);
            if (stmp.length() == 1)
                hs.append('0');
            hs.append(stmp);
        }
        return hs.toString().toLowerCase();
    }
    /**
     * sha256_HMAC加密
     * @param message 消息
     * @return 加密后字符串
     */
    public static String sha256_HMAC(String message) {
        String hash = "";
        try {
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
            hash = byteArrayToHexString(bytes);
        } catch (Exception e) {
            System.out.println("Error HmacSHA256 ===========" + e.getMessage());
        }
        return hash;

    }

    /**
     * Base64Url编码
     * 主要jdk8之前没有实现此算法
     * @param data
     * @return
     */
    public static String Base64UrlEncode(String data) throws UnsupportedEncodingException {
        byte[] input = data.getBytes("UTF-8");
        Base64.Encoder encoder = Base64.getUrlEncoder();
        String encode = encoder.encodeToString(input);
        return encode;
    }

    /**
     * Base64Url解码
     * 主要jdk8之前没有实现此算法
     * @param data
     * @return
     */
    public static String Base64UrlDecoder(String data) throws UnsupportedEncodingException {
        Base64.Decoder decoder = Base64.getUrlDecoder();
        byte[] out = decoder.decode(data);
        String str = new String(out,"UTF-8");
        return str;
    }

五、Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。 

原文地址:https://www.cnblogs.com/shaoyu/p/11881438.html