JWT 详解及源码分析

1. 什么是JWT

介绍
JSON Web Token,没错就是用来身份认证的,使用了行业流行的RFC 7519方法标准,用官方点的话说是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范,他使用Json及签名和响应算法进行认证
在这里插入图片描述
jwt支持很多种算法:
在这里插入图片描述
同样也支持多种语言库
在这里插入图片描述

特点

  1. 简洁:可以通过URL或者http请求头的方式发送,发送的数据量少,传输速度快
  2. 安全:使用了签名认证及响应算法
  3. 易用
  4. 支持json
  5. 流行
    原则
    通过一些字段值再服务器身份验证之后,会生成一个JSON对象,然后会将这个对象发送给用户,然后在之后用户与服务器通讯时,用户请求时将这个JSON对象发送给服务器,服务器通过这个对象来标识用户,生成对象的时候可以添加签名也就是令牌,所以服务器不会保存任何的数据,所有更容易扩展

2.构成

包含三个部分:

  1. Header 头部
  2. Payload 负载
  3. Signature 签名
    他们三之间用 . 隔开
    盗用一下官网的举例:
    在这里插入图片描述
2.1 Header

用来描述JWT元数据的JSON对象,看一下源码:
在这里插入图片描述
alg:设置算法
cty:内容的类型
typ:类型
kid:密钥id
通常只使用alg和typ,alg默认的是HS256加密
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存,就是完整的Header

2.2 Payload

有效负载部分,是JWT主体内容部分,也是一个json对象,包含传输的数据,再看一下源码:
在这里插入图片描述
iss:发布人
sub:主题
exp:到期时间
nbf:之前不可用
iat:发布时间
jti:jwt的id,用来标识此JWT
aud:用户
当然这些字段是可以自定义的,后面的例子会有具体说明
负载部分也使用Base64 URL算法转换为字符串保存

2.3 Signature

签名部分,是对上两个部分数据进行标识,确保数据没有被改变,在这里需要一个key值来完成加密,也就是我们设置的密钥,再来看一波源码:
在这里插入图片描述
其中content就是header和payload使用.进行合并后的值,在这里可以看到header和payloac是用Base64加密的
其中algorithm就是我们自己设置的密钥
其实吧,这个方法就是我们生成token最后调用的方法,返回的就是我们需要的token了,在这里只截取了部分源码,在后面会通过例子来讲解他是怎么一步一步生成token的

一些问题

摘取了网上一些的评论:

  1. JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。并且不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。
  2. 默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密,当未加密时,一些私密数据无法通过JWT传输

使用

  1. jar包,我用的是3.3.0
		<!--jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.3.0</version>
        </dependency>

TokenUtil完整代码:在这里使用a、b、c、d、e、f、g
、h、i 来区分位置进行说明

package com.example.demo.test;

import java.util.Date;
import java.util.UUID;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

/**
 * @author Shuoshi.Yan
 * @package:com.example.demo.test
 * @className:Token生成util
 * @description:
 * @date 2020-04-22 11:24
 * @version:V1.0
 * @NOTICE:本内容仅限于xxx有限公司内部传阅,禁止外泄以及用于其他的商业项目
 * @ Copyright  xxx. All rights reserved.
 **/

public class TokenUtil {

    //创建token
    public String createToken(String userUuid) {
        try {
            //设置密钥及算法 a
            Algorithm algorithm = Algorithm.HMAC256("Yss321SSy");
            //生成token
            String token = JWT.create().//b
                    withExpiresAt(DateUtils.addDays(new Date(), 7)).//c
                    withClaim("uid", userUuid).//d
                    withClaim("createTime", new Date()).
                    sign(algorithm);//e
            return token;
        } catch (Exception exception) {
            throw new RuntimeException("token生成失败");
        }
    }
    //使用签名验证
    public String checkToken(String token) {
        if (StringUtils.isBlank(token)) {
            return "token is blank";
        }
        DecodedJWT jwt = null;
        try {
            //设置密钥及算法
            Algorithm algorithm = Algorithm.HMAC256("Yss321SSy");
            JWTVerifier verifier = JWT.require(algorithm).build();//f
            jwt = verifier.verify(token);//g
            String uid = jwt.getClaims().get("uid").asString();//h
            Date date = jwt.getClaims().get("createTime").asDate();
            System.out.println("date:" + date);
            return uid;
        } catch (Exception e) {
            return "checkToken error";
        }
    }
    //不使用签名验证
    public DecodedJWT decode(String token) {
        if (StringUtils.isBlank(token)) {
            return null;
        }
        try {
            DecodedJWT decodedJWT = JWT.decode(token);//i
            return decodedJWT;
        } catch (Exception e) {
            return null;
        }
    }
    public static void main(String[] args) {
        TokenUtil tokenUtil = new TokenUtil();
        String uid = UUID.randomUUID().toString();
        System.out.println("uid:" + uid);
        String result = tokenUtil.createToken(uid);
        System.out.println("生成的token:" + result);
        String uidOut = tokenUtil.checkToken(result);
        System.out.println("解密token后的uid:" + uidOut);
        DecodedJWT decodedJWT = tokenUtil.decode(result);
        System.out.println("Header:" + decodedJWT.getHeader());
        System.out.println("Payload:" + decodedJWT.getPayload());
        System.out.println("Signature:" + decodedJWT.getSignature());
        System.out.println("Token:" + decodedJWT.getToken());
    }
}

结果:
在这里插入图片描述
根据代码逻辑顺序和其中设计的源码来说明他是怎么走的

  1. 看一下a的位置:Algorithm对象是干嘛的
    在这里插入图片描述
    这俩参数不用管,用不到,看一下这个英文注释,大体意思是这个类是设置签名及其验证过程中使用的算法的,再来看一下咱们在这里使用的Algorithm.HMAC256()方法,看一下:
    在这里插入图片描述
    意思是:使用HS256加密方法及签名返回一个Algorithm对象,在这里上下滑动就可以看到在前面我截的图的所有算法的实现方法,这里的HMACAlgorithm继承自Algorithm
    然后点进这个对象看一下:
    在这里插入图片描述
    其中getSSecretBytes方法是对输入的签名进行为空判断并使其转换成UTF-8格式的一个byte[]
    在这里插入图片描述
    再具体点:这个getBytes方法使用的是java.lang.String提供的一个转换方式,有兴趣可以研究一下:
    在这里插入图片描述
  2. 继续看一下b:
    重点来了,首先看一下JWT对象:
    在这里插入图片描述
    在这里有三个方法:
    DecodedJWT():不使用签名就可以得到token生成时候使用的参数,后面会有说明
    require():从它的参数Algorithm,就可以看出来,他是使用签名来返回一个构建器,和DecodedJWT相反
    create():这个就是我们创建token首先调用的方法,意思是返回用于创建和签名令牌的Json Web令牌构建器
    首先看一下create()方法:上边说到这是返回用于创建和签名令牌的Json Web令牌构建器,这个有点难懂,没事,去看一下它的源码:
    在这里插入图片描述
    我理解的这个类的作用就是为了设置header和payload的
    看一下JWTCreator.Builder,的Builder类,这是一个内部类,作用就是来定义设置的header和payload的数据):
    在这里插入图片描述
    返回到JWT的源码页面,看一下create()方法,里面有一个初始化Builder的方法:
    在这里插入图片描述
    上边咱们说到这个Builder是用来设置header和payload的那么在后面就应该对这俩进行设置,然后返回来才是初始化Builder
  3. 继续看一下c位置:是不是"Exp"有点熟悉,没错就是上边刚开始咱们说到的Payload中的Exp
    在这里就是来设置header和payload中提供的参数的值的,可以自己设置,在这里我就设置了一个Exp(过期时间)的值,设置了7天过期
    在这里插入图片描述
    点进去看一下,上下都是设置Header和Payload的值的方法,这里没什么好看的
  4. 看一下d位置:
    withClaim()方法,这个是用来自定义自己的值的,这里定义了两个值,看一下:
    在这里插入图片描述
    这里的assertNonNull是对name进行为空判断的,这里是不是好奇为什么没有对value进行为空判断,在下面这个方法addClaim()里:
    在这里插入图片描述
    判断了一下然后符合条件就放到payloadClains里面了,这个payloadClains正式Builder中的payloadClains
  5. 继续看一下e位置:
    在这里插入图片描述
    这个sign()的作用是根据给定的签名和设置的算法生成一个新的JWT,这里面对Algorithm进行了判断并设置了Header中的alg和typ的值
    看一下getSigningKeeyId方法这个是判断一下自定义的Algorithm对象有没有设置kid,然后对kid进行设置
    在这里插入图片描述
    然后看一下返回的构造器方法:
    在这里插入图片描述
    看一下构造器:
    在这里插入图片描述
    其中mapper和module是用来将map序列化成json格式的String的,然后将处理完的headerJson和payloadJson保存一下
  6. 在这里header和payload设置完了,那么接下来对signature进行设设置:
    看一下sign()方法的返回中的sign():
    在这里插入图片描述
    没错,在这里就是设置signature的地方,也就是刚开始介绍它的时候截的图的地方:
    在这里插入图片描述
    在这里感觉上边设置的header和payload来设置signature,然后返回最后我们需要的token。这就到了最初的方法:
    在这里插入图片描述
  7. 接下来看一线第二个方法checkToken,看一下f位置
    首先看一下这个方法:
    在这里插入图片描述
    在这里插入图片描述
    在上边说过这个方法是返回带有用于验证令牌签名的算法的构建器。
    看一下这个初始化的方法:
    在这里插入图片描述
    这个BaseVerification又是一个内部类:
    在这里插入图片描述
    作用是用来初始化这三个参数的,这里稍微留意一下defaultLeeway字段
    然后再看一下build()方法:
    在这里插入图片描述
    这个方法主要是设置payload的三个参数的(exp,nbf,iat):
    在这里插入图片描述
    在这里插入图片描述
    这个ClockImpl是设置时间的:
    在这里插入图片描述
    在这里插入图片描述
    在这的addLeewayToDateClaims()方法就是设置三个参数的(exp,nbf,iat),上边留意的defaultLeeway就是在这里用的
    在这里插入图片描述
    然后初始化一下JWTVerifier
    在这里插入图片描述
  8. 现在到g位置,这里是对token进行验证
    在这里插入图片描述
    这个JWT.decode()方法的作用是获取token的数据,就是上面说的那个不加签名进行操作的JWT方法,这个方法在下面进行说明,
    verifyAlgorithm()方法是对加密算法进行验证的
    algorithm.verify(jwt)是对给的签名进行验证的
    verifyClaims是对Payload的字段进行验证的:
    在这里插入图片描述
    最后验证全部通过并且也通过JWT.decode()方法拿到了数据,所以我们就可以拿数据了也就是走到了g位置
    再来看一下verifier.verify()方法的返回对象:DecodedJWT,它是专门用于接触token的返回对象封装
    在这里插入图片描述
    除了自己的方法还继承了Payload和Header的方法,在这里我们使用的是getClaims(),这个方法是
    来自Payload
    在这里插入图片描述
    到这里checkToken方法就讲解完了
  9. 最后说一下JWT.decode()方法也就是最后一个方法decode()
    在这里插入图片描述
    进来之后可以看到它返回的对象是DecodedJWT对象:
    在这里插入图片描述
    来看一下JWTDecoder类,它是继承自DecodedJWT
    在这里插入图片描述
    这个方法的作用就是反编译,得到原来生成token之前使用的数据,其中的JWTParser类是将json解析成Header和Payload对象的,这里面也是使用的是刚上来说的那个序列化对象(ObjectMpper)

到此为止,不正确的地方,望指出!

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值