JWT(JSON Web Token)

基于令牌技术可以用来实现登录认证。
这里所提到的令牌就是用户身份的标识,其本质就是一个字符串。
令牌的形式有很多,这里介绍功能强大的JWT令牌。

1.介绍

JWT全称:JSON Web Token (官网:JSON Web Tokens - jwt.io

是目前最流行的认证解决方案,是一种基于Token令牌的认证授权机制。 从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。

它定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

简洁:是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。

自包含:指的是jwt令牌,看似是一个随机的字符串,但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。如:可以直接在jwt令牌中存储用户的相关信息。

无状态:因为JWT自包含的特性,我们的服务器不需要存储 Session 信息。减轻了服务端的存储压力。更符合设计Restful API的无状态原则--也就是每个请求都是独立且相互独立的,服务不会在请求之间保留任何状态。

简单来讲,jwt就是将原始的json数据格式进行了安全的封装,这样就可以直接基于jwt在通信双方安全的进行信息传输了。

2.JWT的组成

JWT令牌由三个部分组成,三个部分之间使用英文的点来分割

第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{"alg":"HS256","type":"JWT"}

typ(Type):令牌类型,也就是 JWT。
alg(Algorithm):签名算法,比如 HS256。


第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{"id":"1","username":"Tom"}


第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。

JWT是如何将原始的JSON格式数据,转变为字符串的呢?

其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码

Base64:是一种基于64个可打印的字符来表示二进制数据的编码方式。

既然能编码,那也就意味着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符号,那就是等号。等号它是一个补位的符号。

需要注意的是Base64是编码方式,而不是加密方式。所以一定不要将隐私信息存放在 Payload 和Header当中!!!

总结:JWT分为三部分。
Header和Payload都是存储信息的,Header存储的是和令牌本身相关的信息【jwt和签名算法等】,Payload存储的是自定义信息,一般是和用户相关的信息【用户id,姓名等】。本来是Json格式,经过base64编码进行转换。

而第三部分的签名则是Token伪造的关键,将 header+payload+指定秘钥 通过指定签名算法计算而来。从而使得只要Token一个字符被修改校验就无法通过。
简而言之,只要不知道密钥就无法对jwt进行伪造。

注意:密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。

3.通过token校验的基本逻辑

4.基于Java代码如何生成和校验JWT令牌

一般使用jjwt依赖,这个依赖中包含工具类Jwts

<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

1.生成令牌使用的是Jwts.builder()

例1:

@Test
public void genJwt(){
    Map<String,Object> claims = new HashMap<>();
    claims.put("id",1);
    claims.put("username","Tom");
    
    String jwt = Jwts.builder()
        .setClaims(claims) //自定义内容(载荷Payload) 
        .setExpiration(new Date(System.currentTimeMillis() + 24*3600*1000))//有效期,也是payload里的信息,可以看到set方法都是往payload里添加信息
        .signWith(SignatureAlgorithm.HS256, "flyingpig") //签名算法+密钥实现签名        
        .compact();
        //.compact()方法的作用是将JWT的header、payload和signature部分连接起来,并返回最终生成的JWT字符串,
        //如果没有这一步就不是返回string类型了,而是返回JwtBuilder类型
    
    System.out.println(jwt);
}

上面的最终生成的jwt的header和payload中存储内容的json:

header的json:
{
  "typ": "JWT",
  "alg": "HS256"
}
Payload的json
{
  "id": "1",
  "name": "Tom"
}

HS256则是一种密钥对称算法,它使用相同的密钥进行加密和解密。这意味着发送方和接收方必须共享相同的密钥,否则无法正确解密数据。使用SHA256散列函数。

例2:

Jwts.builder()
    .setId(uuid)              //唯一的ID
    .setSubject(subject)   // 主题  可以是JSON数据
    .setIssuer("flyingpig")     // 签发者
    .setIssuedAt(now)      // 签发时间
    .setExpiration(expDate)  //过期时间
    .signWith(signatureAlgorithm, secretKey); //使用HS256对称加密算法签名, 第二个参数为秘钥
    .compact();    //合并信息,生成最终的jwt

上面的最终生成的jwt的header和payload中存储内容的json:

header的json:
{
  "typ": "JWT",
  "alg": "HS256"
}
Payload的json
{
  "iss": "flyingpig",
  "sub": "用户id",
  "iat": "签发时间",
  "exp": "过期时间"
}

2.解析令牌使用的是Jwts.parser()

我们可以将生成的令牌复制一下,然后打开JWT的官网,将生成的令牌直接放在Encoded位置,此时就会自动的将令牌解析出来。

第一部分解析出来,看到JSON格式的原始数据,所使用的签名算法为HS256。
第二个部分是我们自定义的数据,之前我们自定义的数据就是id,还有一个exp代表的是我们所设置的过期时间。
由于前两个部分是base64编码,所以是可以直接解码出来。但最后一个部分并不是base64编码,是经过签名算法计算出来的,所以最后一个部分是不会解析的。

而在解析令牌使用的是Jwts.parser()。同时我们需要提供对应的密钥

获取header里的内容和签名算法:

//获取jwt的header
JwsHeader jwsHeader=Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getHeader();

//获取jwt的签名算法
String signature=Jwts.parser()
                  .setSigningKey(secretKey)//提供密钥
                  .parseClaimsJws(jwt)//解析jwt
                  .getSignature();

获取payload里的内容。

//获取payload里的键值对
//claims继承自map,可以通过claims解析出payload中json的键值对
Claims claims=Jwts.parser()
                  .setSigningKey(secretKey)//提供密钥
                  .parseClaimsJws(jwt)//解析jwt
                  .getBody();

String userName=claims.get(name);//获取之前案例一种自定义内容userName
String uuid=claims.getId();//获取之前案例二中setId的内容
String userId=claims.getSubject();//获取之前案例二中setSubject中的内容

当你提供的密钥错误,或检测到jwt过期,就会代表令牌非法,程序会报错。

3.解析的过程

拿到 JWT 之后,会解析出其中包含的 Header、Payload 以及 Signature 。服务端会根据 Header、Payload、密钥再次生成一个 Signature。拿新生成的 Signature 和 JWT 中的 Signature 作对比,如果一样就说明 Header 和 Payload 没有被修改,JWT合法。

然后再通过Payload的时间检查有没有过期。

【1】生成jwt的id一般使用uuid,而密钥则一般不会采用明文,而是经过加密。

    public static final String JWT_KEY = "Zmx5aW5ncGln";//flyingpig的base64编码

    /**
     * 生成加密后的秘钥 secretKey
     *
     * @return
     */
    public static SecretKey generalKey() {
        //将密钥明文base64解码后通过aes算法进行加密
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    public static String getUUID() {
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }

【2】关于Payload中的Claims

Payload也是JSON 格式数据,包含了Claims(声明,包含 JWT 的相关信息)。

Claims 分为三种类型:
(1)Registered Claims(注册声明):预定义的一些声明,建议使用,但不是强制性的。
(2)Public Claims(公有声明):JWT 签发方可以自定义的声明,但是为了避免冲突,应该在 IANA JSON Web Token Registryopen in new window 中定义它们。
(3)Private Claims(私有声明):JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用。

下面是一些常见的注册声明:
iss(issuer):JWT 签发方。
iat(issued at time):JWT 签发时间。
sub(subject):JWT 主题。
aud(audience):JWT 接收方。
exp(expiration time):JWT 的过期时间。
nbf(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。
jti(JWT ID):JWT 唯一标识

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值