JWT Token 入门篇

JWT Token 入门篇

这里记录下自己学习jwt的过程。

什么是JWT

JSON Web Token(缩写 JWT),是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的自定义格式。用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
官网:https://jwt.io/
标准:https://tools.ietf.org/html/rfc7519

jwt令牌的优点:
1.jwt基于json,非常方便解析。
2.可以在令牌中自定义丰富的内容,易扩展。
3.通过非对称加密算法及数字签名技术,jwt防止篡改,安全性高。
4.资源服务使用jwt可不依赖认证服务即可完成授权。
jwt令牌的缺点:
jwt令牌较长,占存储空间比较大。

JWT组成部分

一个jwt实际就是一个字符串,它由三部分组成:头部、载荷和签名。

头部(Header)
头部用于描述关于该jwt的最基本信息,例如其类型(即JWT)以及签名所用的算法(如:HMAC SHA256或RSA)等。这也可以被表示成一个JSON对象。

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg:签名的算法,这里使用的算法是HS256算法。
  • typ:是类型。

用Base64对这个JSON编码就能得到JWT的第一部分,这里下面代码会写。

载荷(Payload)
第二部分是载荷,就是存放有效信息的地方。这些有效信息包含三个部分:

  • 标准中注册的声明(建议但不强制使用)
iss:jwt签发者
sub:jwt所面向的用户
aud:接收jwt的一方
exp:jwt的过期时间,这个过期时间必须要大于签发时间
nbf:定义在什么时间之间,该jwt都是不可用的
iat:jwt的签发时间
jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
  • 公共的声明
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分可以在客户端解密。
  • 私有的声明
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
    这个指的就是自定义的claim。比如下面举例中的name属于自定义的claim。这些claim跟JWT标准规定的claim区别在于:jwt规定的claim,jwt的接收方在拿到jwt之后,都知道怎么对这些标准的claim进行验证;而private claim不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

其中sub和iat是标准的声明,name是自定义的声明(私有的或者公共的),也可以用Base64对这个JSON编码来得到JWT的第二部分

签证、签名(Signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  1. header(base64后的)
  2. payload(base64后的)
  3. secret(盐,一定要保密)

这个部分需要base64加密后的header和base64加密后payload使用.连接组成的字符串,然后通过header中声明的加密方式加盐组合加密,然后就构成了jwt的第三部分。

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务器的私钥,在任何场景都不应该泄露出去。一旦客户端得知这个secret,那就意味着是可以自我签发jwt了。

什么是JJWT

JJWT是一个提供端到端的JWT创建和验证的Java库。永久免费和开源(Apache License,版本2.0),JJWT很容易使用和理解,它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

快速入门

创建springboot工程,引入依赖,依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.hjl</groupId>
    <artifactId>jjwt-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jjwt-demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

官网显示java的有6个不同的依赖,这里为什么要用io.jsonwebtoken这个版本,是因为官网显示的这个支持的算法多,以及人用的多,最新的是0.11.1,我这里用的是0.9.0版本
在这里插入图片描述
在这里插入图片描述
在test下面测试,示例如下:
在这里插入图片描述
代码如下:
生成jwt token代码

package com.hjl.jjwtdemo;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.Base64Codec;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;

@SpringBootTest
class JjwtDemoApplicationTests {

    @Test
    void contextLoads() {
    }

    /**
     * 生成jwt,因为没有设置过期时间,所以是永久有效的
     */
    @Test
    public void testJwt(){
        //jwt三部分构成第一个是头部,第二个是载荷,第三个是签名
        JwtBuilder jwtBuilder= Jwts.builder()
                .setId("888") //唯一Id{"jti":"888"}
                .setSubject("Rose") //接收的用户{"sub":"Rose"}
                .setIssuedAt(new Date()) //签发时间 {"iat":""}
                .signWith(SignatureAlgorithm.HS256,"xxxx"); //签名算法及秘钥,签名算法是{"alg":"HS256"},秘钥是xxxx

        //签发token
        String token=jwtBuilder.compact();
        System.out.println("token="+token);

        //通过BASE64解析jwt生成的token
        String[] a=token.split("\\.");
        //头部值里面只有你是用的什么算法,如:HS256
        System.out.println("jwt头部值:"+ Base64Codec.BASE64.decodeToString(a[0]));
        //载荷值里面就是你设置的唯一Id,接收的用户和签发时间
        System.out.println("jwt载荷值:"+ Base64Codec.BASE64.decodeToString(a[1]));
        //这个会乱码,因为你没有通过秘钥来解析值
        System.out.println("jwt签名值:"+ Base64Codec.BASE64.decodeToString(a[2]));
    }
}

然后我们点击这个,如下图:
在这里插入图片描述
执行结果如下,token就是我们生成的jwt token值,下面的就是通过Base64解码获得的值,这里会发现签名是乱码,这是正常的:
在这里插入图片描述
解析jwt token代码:
在testJwt()方法下面在写上这段代码,来解析jwt

/**
     * 解析token
     */
    @Test
    public void testParseToken(){
        //上一个方法生成的token
        String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA3ODQ3NjY0fQ.zgpT3QV5q-kC-RsEjV73jQtYCnTT9ICHgxsZgYPMjy8";
        //解析token,获取Claims,就是载荷中的值
        Claims claims = (Claims) Jwts.parser()
                .setSigningKey("xxxx") //秘钥 通过这个秘钥来解析
                .parse(token)
                .getBody();
        System.out.println("jti="+claims.getId());
        System.out.println("sub="+claims.getSubject());
        System.out.println("iat="+claims.getIssuedAt());
    }

获取结果如下,发现值是对的,是自己设置的值:
在这里插入图片描述
生成jwt(失效时间),设置过期时间
在testJwt里面的方法里面改一下就好,代码如下:

/**
     * 生成jwt(失效时间),设置过期时间
     */
    @Test
    public void testJwtHasExpire(){
        //当前时间
        long date=System.currentTimeMillis();
        //失效时间 1分钟
        long exp=date+60*1000;
        //jwt三部分构成第一个是头部,第二个是载荷,第三个是签名
        JwtBuilder jwtBuilder= Jwts.builder()
                .setId("888") //唯一Id{"jti":"888"}
                .setSubject("Rose") //接收的用户{"sub":"Rose"}
                .setIssuedAt(new Date()) //签发时间 {"iat":""}
                .signWith(SignatureAlgorithm.HS256,"xxxx") //签名算法及秘钥{"alg":"HS256"}
                .setExpiration(new Date(exp)); //设置失效时间

        //签发token
        String token=jwtBuilder.compact();
        System.out.println("token="+token);

        //解析jwt生成的token
        String[] a=token.split("\\.");
        //头部值里面只有你是用的什么算法,如:HS256
        System.out.println("jwt头部值:"+ Base64Codec.BASE64.decodeToString(a[0]));
        //载荷值里面就是你设置的唯一Id,接收的用户和签发时间
        System.out.println("jwt载荷值:"+ Base64Codec.BASE64.decodeToString(a[1]));
        //这个会乱码
        System.out.println("jwt签名值:"+ Base64Codec.BASE64.decodeToString(a[2]));
    }

执行结果如下,这里就是多了个过期时间,当时间到了后,这个token就过期不可用了,结果和testJwt返回结果一样:
在这里插入图片描述
解析token(失效时间)
这里的代码和testParseToken()代码是一样的,如下:

/**
     * 解析token(失效时间)
     */
    @Test
    public void testParseTokenHasExpire(){
        //上一个方法生成的token
        String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA3ODQ4NTU1LCJleHAiOjE2MDc4NDg2MTV9.Q3DxON-xEKYZt_VX5Sxar-e6nuMdFIynCE-y2OFZPec";
        //解析token,获取Claims,就是载荷中的值
        Claims claims = (Claims) Jwts.parser()
                .setSigningKey("xxxx") //秘钥 通过这个秘钥来解析
                .parse(token)
                .getBody();
        System.out.println("jti="+claims.getId());
        System.out.println("sub="+claims.getSubject());
        System.out.println("iat="+claims.getIssuedAt());
    }

当过期时间到了后,在执行的结果如下:
在这里插入图片描述
会说这个jwt已经过期了,报错如下:
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2020-12-13T16:36:55Z. Current time: 2020-12-13T16:38:46Z, a difference of 111422 milliseconds. Allowed clock skew: 0 milliseconds.

生成jwt(自定义声明载荷值)
代码如下:

    /**
     * 生成jwt(自定义声明载荷值)
     */
    @Test
    public void testJwtHasEnhancer(){
        //jwt三部分构成第一个是头部,第二个是载荷,第三个是签名
        JwtBuilder jwtBuilder= Jwts.builder()
                .setId("888") //唯一Id{"jti":"888"}
                .setSubject("Rose") //接收的用户{"sub":"Rose"}
                .setIssuedAt(new Date()) //签发时间 {"iat":""}
                .signWith(SignatureAlgorithm.HS256,"xxxx") //签名算法及秘钥{"alg":"HS256"}
                //自定义申明
                .claim("name","Jack") //通过claim来自定义载荷值,这里是属性为name,值为jack
                .claim("sex","男");
                //.addClaims(map) //这里是通过map集合来自定义值,将值放到map里面添加

        //签发token
        String token=jwtBuilder.compact();
        System.out.println("token="+token);

        //解析jwt生成的token
        String[] a=token.split("\\.");
        //头部值里面只有你是用的什么算法,如:HS256
        System.out.println("jwt头部值:"+ Base64Codec.BASE64.decodeToString(a[0]));
        //载荷值里面就是你设置的唯一Id,接收的用户和签发时间
        System.out.println("jwt载荷值:"+ Base64Codec.BASE64.decodeToString(a[1]));
        //这个会乱码
        System.out.println("jwt签名值:"+ Base64Codec.BASE64.decodeToString(a[2]));
    }

执行结果如下:
在这里插入图片描述
解析token(自定义申明载荷值)
代码如下:

/**
     * 解析token(自定义申明载荷值)
     */
    @Test
    public void testParseTokenHasEnhancer(){
        //上一个方法生成的token
        String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA3ODQ5MDA4LCJuYW1lIjoiSmFjayIsInNleCI6IueUtyJ9.hcPlDsy0v8MN0CuBS__SC-PbsJgT1gBa1qyRGP_jMpg";
        //解析token,获取Claims,就是载荷中的值
        Claims claims = (Claims) Jwts.parser()
                .setSigningKey("xxxx") //秘钥 通过这个秘钥来解析
                .parse(token)
                .getBody();
        System.out.println("jti="+claims.getId());
        System.out.println("sub="+claims.getSubject());
        System.out.println("iat="+claims.getIssuedAt());
        System.out.println("name="+claims.get("name"));
        System.out.println("sex="+claims.get("sex"));
    }

执行结果如下:
在这里插入图片描述
好了到这里,我的学习笔记就做完了!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值