【Spring-Security】Re12 JsonWebToken

一、认证机制种类:

1、HTTP-Basic-Auth

每次请求接口必须提供账号信息【username + password】

但是信息有暴露风险,配合RestFul风格使用,逐渐淘汰

2、Cookie-Auth

首次请求在客户端和服务端分别创建Cookie + Session 对象

通过两者的对象匹配实现状态管理,浏览器关闭会让Cookie对象销毁

可以设置Cookie过期时间

3、Open-Authorization [ Oauth ]

开放授权,授权给第三方应用来访问服务资源

4、Token-Auth

基于令牌的验证,所有权限控制的判断全部以令牌为凭证通行访问

二、Json Web Token [ JWT ]

标准描述:

https://tools.ietf.org/html/rfc7519

官网地址:

https://jwt.io/

以前后端数据交互标准的JSON作为传输载体实现Token-Auth

https://www.bilibili.com/video/BV12D4y1U7D8?p=39

三、Java - JWT

创建一个SpringBoot项目。

需要Web组件和JJWT组件两个坐标:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

1、创建JWT

JWT令牌生成测试:

package cn.zeal4j;

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

import java.util.Date;

@SpringBootTest
class JJwtApplicationTests {

    @Test
    void contextLoads() {
        creatTokenTest();
    }
    
    void creatTokenTest() {
        JwtBuilder jwtBuilder = Jwts.builder();
        jwtBuilder.
                setId("8848").  // ID标识
                setSubject("userL8").   // 用户主体
                setIssuedAt(new Date()).    // 签发时间
                signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐
        
        // 生成的JWT令牌
        String jwtToken = jwtBuilder.compact();
        System.out.println(jwtToken);
    }
}

打印的令牌字符串:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg1OTk5fQ.BM0CHf0kawFz6uTBjcF8aeFDYdX0M4CN0PswEPm9W0U

令牌使用点号分割令牌的各个信息

eyJhbGciOiJIUzI1NiJ9 # 头部信息
.
eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg1OTk5fQ    # 荷载信息
.
BM0CHf0kawFz6uTBjcF8aeFDYdX0M4CN0PswEPm9W0U    # 签发信息

2、解析JWT

解密处理:

package cn.zeal4j;

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 sun.misc.BASE64Decoder;

import java.util.Date;

@SpringBootTest
class JJwtApplicationTests {

    @Test
    void contextLoads() {
        creatTokenTest();
    }

    void creatTokenTest() {
        JwtBuilder jwtBuilder = Jwts.builder();
        jwtBuilder.
                setId("8848").  // ID标识
                setSubject("userL8").   // 用户主体
                setIssuedAt(new Date()).    // 签发时间
                signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐

        // 生成的JWT令牌
        String jwtToken = jwtBuilder.compact();
        System.out.println(jwtToken);

        System.out.println("- - - - - JWT-Token-Decoder!!! - - - -");
        String[] tokenParts = jwtToken.split("\\.");

        String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]);

        String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]);

        String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]);

        System.out.println("tokenHead -> " + tokenHead);
        System.out.println("tokenCarrier -> " + tokenCarrier);
        System.out.println("tokenSignature -> " + tokenSignature);
    }
}

输出结果:

- - - - - JWT-Token-Decoder!!! - - - -
tokenHead -> {"alg":"HS256"}
tokenCarrier -> {"jti":"8848","sub":"userL8","iat":1601386514
tokenSignature -> *�2ɥr�ԻjNz�4�����RzЂ97�+f

签名是被盐加密过了的,所以就算用算法解密了,但是有盐打乱,还是不能知道原文是什么

另外每次生成的Token信息的也会有不同的区别:

eyJhbGciOiJIUzI1NiJ9.
eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NTE0fQ.
KuQyyaVys9S7_ak56pTSWkJbtotxSetCCOTeeBitmmw
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
eyJhbGciOiJIUzI1NiJ9.
eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ.
LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0

头密文是一直的,但是荷载密文是在结尾有区别,原因是我们加了签发日期

而下面的签发密文是盐加密的,每次都会算出不一样的密文结果

package cn.zeal4j;

import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.Base64Codec;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import sun.misc.BASE64Decoder;

import java.util.Date;

@SpringBootTest
class JJwtApplicationTests {

    @Test
    void contextLoads() {
        // creatTokenTest();

        parseJwtTokenTest();
    }

    void creatTokenTest() {
        JwtBuilder jwtBuilder = Jwts.builder();
        jwtBuilder.
                setId("8848").  // ID标识
                setSubject("userL8").   // 用户主体
                setIssuedAt(new Date()).    // 签发时间
                signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐

        // 生成的JWT令牌
        String jwtToken = jwtBuilder.compact();
        System.out.println(jwtToken);

        System.out.println("- - - - - JWT-Token-Decoder!!! - - - -");
        String[] tokenParts = jwtToken.split("\\.");

        String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]);

        String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]);

        String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]);

        System.out.println("tokenHead -> " + tokenHead);
        System.out.println("tokenCarrier -> " + tokenCarrier);
        System.out.println("tokenSignature -> " + tokenSignature);
    }

    void parseJwtTokenTest() {
        // 模拟客户端发送的JWT令牌
        final String simulationClientJwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ.LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0";

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(simulationClientJwtToken);


        JwsHeader header = claimsJws.getHeader();
        String keyId = header.getKeyId(); // 获取ID标识

        // 负载对象
        Claims body = claimsJws.getBody();
        String subject = body.getSubject(); // 获取签发主体
        Date issuedAt = body.getIssuedAt(); // 获取签发时间
        String id = body.getId(); // 这也能获取ID?

        String signature = claimsJws.getSignature(); // 签名

        System.out.println("from JwsHeader KeyId -> " + keyId);
        System.out.println("from ClaimsBody id -> " + id);

        System.out.println("ClaimsBody subject -> " + subject);
        System.out.println("ClaimsBody issuedAt -> " + issuedAt);
        System.out.println("signature -> " + signature);
    }
}

打印结果:

from JwsHeader KeyId -> null
from ClaimsBody id -> 8848
ClaimsBody subject -> userL8
ClaimsBody issuedAt -> Tue Sep 29 21:39:16 CST 2020
signature -> LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0

3、Token过期时间设置

package cn.zeal4j;

import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.Base64Codec;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import sun.misc.BASE64Decoder;

import java.text.SimpleDateFormat;
import java.util.Date;

@SpringBootTest
class JJwtApplicationTests {

    @Test
    void contextLoads() {
        // 1、creatTokenTest();

        // 2、parseJwtTokenTest();

        // 3、parseExpiredJwtTokenTest(creatTokenTest());

        parseExpiredJwtTokenTest(creatTokenTest());
    }

    String creatTokenTest() {
        long currentTimeMillis = System.currentTimeMillis();
        long theExpireTimeMills = currentTimeMillis + (60 * 1000); // 1毫秒 * 1000(1秒) * 60 = 1 分钟
        JwtBuilder jwtBuilder = Jwts.builder();
        jwtBuilder.
                setExpiration(new Date(theExpireTimeMills)).
                setId("8848").  // ID标识
                setSubject("userL8").   // 用户主体
                setIssuedAt(new Date()).    // 签发时间
                signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐

        // 生成的JWT令牌
        String jwtToken = jwtBuilder.compact();
        System.out.println(jwtToken);

        System.out.println("- - - - - JWT-Token-Decoder!!! - - - -");
        String[] tokenParts = jwtToken.split("\\.");

        String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]);

        String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]);

        String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]);

        System.out.println("tokenHead -> " + tokenHead);
        System.out.println("tokenCarrier -> " + tokenCarrier);
        System.out.println("tokenSignature -> " + tokenSignature);

        return jwtToken;
    }

    void parseJwtTokenTest() {
        // 模拟客户端发送的JWT令牌
        final String simulationClientJwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ.LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0";

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(simulationClientJwtToken);

        JwsHeader header = claimsJws.getHeader();
        String keyId = header.getKeyId(); // 获取ID标识

        // 负载对象
        Claims body = claimsJws.getBody();
        String subject = body.getSubject(); // 获取签发主体
        Date issuedAt = body.getIssuedAt(); // 获取签发时间
        String id = body.getId(); // 这也能获取ID?

        String signature = claimsJws.getSignature(); // 签名

        System.out.println("from JwsHeader KeyId -> " + keyId);
        System.out.println("from ClaimsBody id -> " + id);

        System.out.println("ClaimsBody subject -> " + subject);
        System.out.println("ClaimsBody issuedAt -> " + issuedAt);
        System.out.println("signature -> " + signature);
    }

    void parseExpiredJwtTokenTest(String jwtToken) {
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(jwtToken);
        // eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDEzODgzODYsImp0aSI6Ijg4NDgiLCJzdWIiOiJ1c2VyTDgiLCJpYXQiOjE2MDEzODgzMjZ9.Ve0n_TylzsCFHnk4vrjWKM_fZPGteupsx2aLbJU2E0k
        
        JwsHeader header = claimsJws.getHeader();
        String keyId = header.getKeyId(); // 获取ID标识

        // 负载对象
        Claims body = claimsJws.getBody();
        String subject = body.getSubject(); // 获取签发主体
        Date issuedAt = body.getIssuedAt(); // 获取签发时间
        String id = body.getId(); // 这也能获取ID?

        String signature = claimsJws.getSignature(); // 签名

//        System.out.println("from JwsHeader KeyId -> " + keyId);
//        System.out.println("from ClaimsBody id -> " + id);
//
//        System.out.println("ClaimsBody subject -> " + subject);
//        System.out.println("ClaimsBody issuedAt -> " + issuedAt);
//        System.out.println("signature -> " + signature);

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String signTime = simpleDateFormat.format(body.getIssuedAt());
        String expireTime = simpleDateFormat.format(body.getExpiration());
        String thisTime = simpleDateFormat.format(new Date());

        System.out.println("- - - JwtTokenTimeExpireTest - - -");
        System.out.println("签发时间 -> " + signTime);
        System.out.println("过期时间 -> " + expireTime);
        System.out.println("现在时间 -> " + thisTime);
    }
}

结果打印:

- - - JwtTokenTimeExpireTest - - -
签发时间 -> 2020-09-29 22:05:26
过期时间 -> 2020-09-29 22:06:26
现在时间 -> 2020-09-29 22:05:26

超过过期时间解析Token会让程序抛出TokenExpireException异常,详细信息自测,不赘述了

4、自定义申明:

String creatTokenTest() {
    long currentTimeMillis = System.currentTimeMillis();
    long theExpireTimeMills = currentTimeMillis + (60 * 1000); // 1毫秒 * 1000(1秒) * 60 = 1 分钟

    Map<String, Object> map = new HashMap<>();
    map.put("k1", "v1");
    map.put("k2", "v2");
    map.put("k3", "v3");
    // ......

    JwtBuilder jwtBuilder = Jwts.builder();
    jwtBuilder.
            setExpiration(new Date(theExpireTimeMills)).
            setId("8848").  // ID标识
            setSubject("userL8").   // 用户主体
            setIssuedAt(new Date()).    // 签发时间
            claim("customClaimKey01", "customClaimValue01"). // 自定义申明方式一
            claim("customClaimKey02", "customClaimValue02").
            addClaims(map). // 自定义申明方式二
            signWith(SignatureAlgorithm.HS256, "This is a simple salty"); // 签名算法和盐

    // 生成的JWT令牌
    String jwtToken = jwtBuilder.compact();
    System.out.println(jwtToken);

    System.out.println("- - - - - JWT-Token-Decoder!!! - - - -");
    String[] tokenParts = jwtToken.split("\\.");

    String tokenHead = Base64Codec.BASE64.decodeToString(tokenParts[0]);

    String tokenCarrier = Base64Codec.BASE64.decodeToString(tokenParts[1]);

    String tokenSignature = Base64Codec.BASE64.decodeToString(tokenParts[2]);

    System.out.println("tokenHead -> " + tokenHead);
    System.out.println("tokenCarrier -> " + tokenCarrier);
    System.out.println("tokenSignature -> " + tokenSignature);

    return jwtToken;
}

void parseJwtTokenTest() {
    // 模拟客户端发送的JWT令牌
    final String simulationClientJwtToken = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODQ4Iiwic3ViIjoidXNlckw4IiwiaWF0IjoxNjAxMzg2NzU2fQ.LSnm3uzBW6T6fHqGLszv5jsoIoIIiKZRx_rMAwylLV0";

    Jws<Claims> claimsJws = Jwts.parser().setSigningKey("This is a simple salty").parseClaimsJws(simulationClientJwtToken);

    JwsHeader header = claimsJws.getHeader();
    String keyId = header.getKeyId(); // 获取ID标识

    // 负载对象
    Claims body = claimsJws.getBody();
    String subject = body.getSubject(); // 获取签发主体
    Date issuedAt = body.getIssuedAt(); // 获取签发时间
    String id = body.getId(); // 这也能获取ID?

    Object o = body.get("key-name"); // 获取申明

    String signature = claimsJws.getSignature(); // 签名

    System.out.println("from JwsHeader KeyId -> " + keyId);
    System.out.println("from ClaimsBody id -> " + id);

    System.out.println("ClaimsBody subject -> " + subject);
    System.out.println("ClaimsBody issuedAt -> " + issuedAt);
    System.out.println("signature -> " + signature);
}

 

posted @ 2020-09-29 22:19  emdzz  阅读(267)  评论(0编辑  收藏  举报