window.cnblogsConfig = { blogUser: 'MoYu', blogAvatar: 'https://gitee.com/MoYu-zc/picgo/raw/master/img/20210213094450.jpg', blogStartDate: '2020-02-09', webpageTitleOnblur: '(o゚v゚)ノ Hi,Back', webpageTitleOnblurTimeOut: 500, webpageTitleFocus: '(*´∇`*) 欢迎回来!', webpageTitleFocusTimeOut: 1000, webpageIcon: "https://gitee.com/MoYu-zc/picgo/raw/master/img/20210213094450.jpg", enable: true, // 是否开启日/夜间模式切换按钮 auto: { // 自动切换相关配置 enable: false, // 开启自动切换 dayHour: 7, // 日间模式开始时间,整数型,24小时制 nightHour: 20 // 夜间模式开始时间,整数型,24小时制 } switchDayNight: { enable: true, auto: { enable: true } }, progressBar: { id : 'top-progress-bar', // 请勿修改该值 color : '#77b6ff', height : '2px', duration: 0.2, }, loading: { rebound: { tension: 16, friction: 5, }, spinner: { id: 'spinner', radius: 90, sides: 3, depth: 4, colors: { background: '#f0f0f0', stroke: '#272633', base: null, child: '#272633', }, alwaysForward: true, // When false the spring will reverse normally. restAt: 0.5, // A number from 0.1 to 0.9 || null for full rotation renderBase: false, } }, homeTopAnimationRendered: true, homeTopAnimation: { radius: 15, density: 0.2, color: 'rgba(255,255,255, .2)', // 颜色设置,“random” 为随机颜色 clearOffset: 0.3, }, essayTopAnimationRendered: true, essayTopAnimation: { triW : 14, triH : 20, neighbours : ["side", "top", "bottom"], speedTrailAppear : .1, speedTrailDisappear : .1, speedTriOpen : 1, trailMaxLength : 30, trailIntervalCreation : 100, delayBeforeDisappear : 2, colorsRandom: false, // v1.2.4 是否开启随机颜色 colors: [ '#96EDA6', '#5BC6A9', '#38668C', '#374D84', '#BED5CB', '#62ADC6', '#8EE5DE', '#304E7B' ] }, homeTopImg: [ "https://cdn.jsdelivr.net/gh/BNDong/Cnblogs-Theme-SimpleMemory@master/img/webp/home_top_bg.webp", "https://cdn.jsdelivr.net/gh/BNDong/Cnblogs-Theme-SimpleMemory@master/img/webp/home_top_bg.webp" ], homeBannerTextType: "one", essayTopImg: [ "https://cdn.jsdelivr.net/gh/BNDong/Cnblogs-Theme-SimpleMemory@master/img/webp/nothome_top_bg.webp", "https://cdn.jsdelivr.net/gh/BNDong/Cnblogs-Theme-SimpleMemory@master/img/webp/nothome_top_bg.webp", "https://gitee.com/MoYu-zc/picgo/raw/master/img/20210208190902.jpg", "https://gitee.com/MoYu-zc/picgo/raw/master/img/20210208190954.jpg", ], codeMaxHeight: true, codeLineNumber: true, essayCode: { fontFamily: "'Ubuntu Mono',monospace", // 代码框字体 fontSize: "14px" // 代码框字体大小 }, }

JWT

JWT

1.什么是JWT

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

优点:

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

缺点: JWT令牌较长,占存储空间比较大.

2.JWT组成

一个 JWT 实际上就是一个字符串,它由三部分组成,第一部分我们称它为头部(header) , 第二部分我们称其为载荷(payload) ,第三部分是签证(signature)

2.1 头部

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。

JDK中提供了非常方便的BASE64EncoderBASE64Decoder,用它们可以非常方便的完成基于BASE64的编码和解码。

2.2载荷

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个载荷:

{
  "sub": "123456",
  "name": "MoYu",
  "admin": true
}

然后将其进行base64加密,得到Jwt的第二部分:

ewogICJzdWIiOiAiMTIzNDU2IiwKICAibmFtZSI6ICJNb1l1IiwKICAiYWRtaW4iOiB0cnVlCn0=

提示:不要放一些 敏感信息

2.3 签证

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret (一定要保密)

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

作者:Dearmadman
链接:https://www.jianshu.com/p/576dbf44b2ae
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ewogICJzdWIiOiAiMTIzNDU2IiwKICAibmFtZSI6ICJNb1l1IiwKICAiYWRtaW4iOiB0cnVlCn0=.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

1

3.JWT-dome

3.1 生成token

1.创建一个maven项目:

(该项目选用的Mavend的QuickStart版本,注意别选错)

2

2.使其成为SpringBoot项目、导入依赖

pom.xml 中加入

<parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.4.4</version>
    <relativePath/>
</parent>
<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.1</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>

3.创建启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

4.创建测试类

@SpringBootTest
public class JwtTest {
    @Test
    public void testJwt(){
        JwtBuilder jwtBuilder = Jwts.builder()
            //设置id,{“jti”:“888”}
            .setId("888")
            //签发用户,{"sub":"MoYu"}
            .setSubject("MoYu")
            //签发时间,{"iat":"xxxxx"}
            .setIssuedAt(new Date())
            .signWith(SignatureAlgorithm.HS256, "xxxx");
        //生成token
        String token = jwtBuilder.compact();
        System.out.println(token);
        String[] split = token.split("\\.");
        //头部
        System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
        //载荷
        System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
        //签名  乱码
        System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
    }
}

运行测试:

3

将生成的jwt令牌在jwt官网查看:

4

3.2 解析token

@Test
public void parseToken(){
    String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJNb1l1IiwiaWF0IjoxNjE4MTE5MDkwfQ.-GSkCkgflTs6IQHmWQjrgY6YD0hOgRBmFwG64x5mgdk";
    Claims claims = Jwts.parser()
        .setSigningKey("xxxx")
        .parseClaimsJws(token)
        .getBody();
    
    System.out.println("jti:"+claims.getId());
    System.out.println("sub:"+claims.getSubject());
    System.out.println("iat:"+claims.getIssuedAt());

}

5

3.3 token过期校验

创建一个带过期时间exp的token令牌

// 生成token(带过期时间)
@Test
public void testJwtHasExp(){
    long now = System.currentTimeMillis();
    //过期时间
    long exp = now + 60 * 1000;
    JwtBuilder jwtBuilder = Jwts.builder()
        //设置id,{“jti”:“888”}
        .setId("888")
        //签发用户,{"sub":"MoYu"}
        .setSubject("MoYu")
        //签发时间,{"iat":"xxxxx"}
        .setIssuedAt(new Date())
        .signWith(SignatureAlgorithm.HS256, "xxxx")
        //设置过期时间,{“exp”:"xxxx"}
        .setExpiration(new Date(exp));
    //生成token
    String token = jwtBuilder.compact();
    System.out.println(token);
    String[] split = token.split("\\.");
    //头部
    System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
    //载荷
    System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
    //签名  乱码
    System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
}

6

对应的修改token解析方法:

// 解析token(带过期时间)
@Test
public void parseTokenHasExp(){
    String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJNb1l1IiwiaWF0IjoxNjE4MTE5NzY2LCJleHAiOjE2MTgxMTk4MjZ9.T5vAI5KK2QYY7W7qqWZXYn9JHHZ9kW6IdtTFg_hX98Q";
    Claims claims = Jwts.parser()
        .setSigningKey("xxxx")
        .parseClaimsJws(token)
        .getBody();
    System.out.println("jti:"+claims.getId());
    System.out.println("sub:"+claims.getSubject());
    System.out.println("iat:"+claims.getIssuedAt());
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println("过期时间:"+format.format(claims.getExpiration()));
    System.out.println("签发时间:"+format.format(claims.getIssuedAt()));
    System.out.println("当前时间:"+format.format(new Date()));
}

过期时间内:

7

过期时间外:

8

3.4 token自定义申明

对于token的自定义申明有两种方式:

  • .addClaims() ,参数类型为 Map
  • .claim("xxxx","xxxx") ,这个方法可以进行逐条添加

生成带有自定义申明的toke令牌:

// 生成token(自定义申明)
@Test
public void testJwtHasClaims(){
    JwtBuilder jwtBuilder = Jwts.builder()
        //设置id,{“jti”:“888”}
        .setId("888")
        //签发用户,{"sub":"MoYu"}
        .setSubject("MoYu")
        //签发时间,{"iat":"xxxxx"}
        .setIssuedAt(new Date())
        .signWith(SignatureAlgorithm.HS256, "xxxx")
        //自定义申明  参数是map
        // .addClaims();
        .claim("logo","xxx,jpg")
        .claim("一句话","这只是一句话");
    //生成token
    String token = jwtBuilder.compact();
    System.out.println(token);
    String[] split = token.split("\\.");
    //头部
    System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
    //载荷
    System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
    //签名  乱码
    System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
}

9

进行对带有自定义申明的token令牌进行解析:

// 解析token(自定义申明)
@Test
public void parseTokenHasClaims(){
    String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJNb1l1IiwiaWF0IjoxNjE4MTIwNTg0LCJsb2dvIjoieHh4LGpwZyIsIuS4gOWPpeivnSI6Iui_meWPquaYr-S4gOWPpeivnSJ9.m44gqMFO863jI5B2qjb9yXvaRJsIRYcfdEI3KiJgW1M";
    Claims claims = Jwts.parser()
        .setSigningKey("xxxx")
        .parseClaimsJws(token)
        .getBody();
    System.out.println("jti:"+claims.getId());
    System.out.println("sub:"+claims.getSubject());
    System.out.println("iat:"+claims.getIssuedAt());
    System.out.println("logo:"+claims.get("logo"));
    System.out.println("一句话:"+claims.get("一句话"));
}

10

3.5 刷新token令牌

刷新token令牌本质上就是,对之前的token令牌中的签发时间进行更新,从而生成新的token令牌

@Test
public void parseTokenRefresh(){
    String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJNb1l1IiwiaWF0IjoxNjE4MTIwNTg0LCJsb2dvIjoieHh4LGpwZyIsIuS4gOWPpeivnSI6Iui_meWPquaYr-S4gOWPpeivnSJ9.m44gqMFO863jI5B2qjb9yXvaRJsIRYcfdEI3KiJgW1M";
    Claims claims = Jwts.parser()
        .setSigningKey("xxxx")
        .parseClaimsJws(token)
        .getBody();
    //更新签发时间
    JwtBuilder jwtBuilder = Jwts.builder()
        .setId(claims.getId())
        .setSubject(claims.getSubject())
        .setIssuedAt(new Date())
        .signWith(SignatureAlgorithm.HS256, "xxxx");
    String refresh_token = jwtBuilder.compact();

    System.out.println("更新前:"+token);
    System.out.println("更新后:"+refresh_token);
}

11

个人博客为:
MoYu's Github Blog
MoYu's Gitee Blog

posted @ 2021-04-24 22:27  MoYu-zc  阅读(170)  评论(0编辑  收藏  举报