IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 【编码实战】2022年还在用jjwt操作jwt?,推荐你使用nimbus-jose-jwt,爽到飞起~ -> 正文阅读

[开发测试]【编码实战】2022年还在用jjwt操作jwt?,推荐你使用nimbus-jose-jwt,爽到飞起~

什么是nimbus-jose-jwt?

nimbus-jose-jwt是基于Apache2.0开源协议的JWT开源库,支持所有的签名(JWS)和加密(JWE)算法。

对于JWT、JWS、JWE介绍

  • JWT是一种规范,它强调了两个组织之间传递安全的信息
  • JWS是JWT的一种实现,包含三部分header(头部)、payload(载荷)、signature(签名)
  • JWE也是JWT的一种实现,包含五部分内容。

在这里插入图片描述

接下来我们将使用对称加密(HMAC)和非对称加密(RSA)两种算法生成和解析JWT令牌。

1.对称加密(HMAC)

对称加密使用相同的密钥进行加密和解密。

  • 首先在pom.xml添加nimbus-jose-jwt依赖库
    <dependencies>
        <!-- web组件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.0.RELEASE<version>
        </dependency>
        <!--jwt工具-->
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>8.16<version>
        </dependency>
    </dependencies>
  • 创建PayloadDto实体类,用于封装JWT中存储的用户信息;
@Data
@ApiModel("信息实体类")
@Builder
@EqualsAndHashCode(callSuper = false)
public class PayloadDto {

    @ApiModelProperty("主题")
    private String sub;

    @ApiModelProperty("签发时间")
    private Long iat;

    @ApiModelProperty("过期时间")
    private Long exp;

    @ApiModelProperty("JWT ID")
    private String jti;

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("用户权限")
    private List<String> authorities;
}
  • 创建JwtTokenService接口以及JwtTokenServiceImpl逻辑实现类,在其中添加根据HMAC算法生成和验证令牌方法。
/**
 * Created by zsh on 2022/3/9
 */
public interface JwtTokenService {
    /**
     * 使用HMAC对称加密算法生成token
     */
    String generateTokenByHMAC(String payloadStr, String secret) throws KeyLengthException;

    /**
     * 模拟生成用户数据
     */
    PayloadDto getDefaultPayloadDto();

    /**
     * 验证令牌
     */
    PayloadDto verifyTokenByHMAC(String token, String secret);
}
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.zsh.mall.domain.PayloadDto;
import com.zsh.mall.exception.JwtExpireException;
import com.zsh.mall.exception.JwtInvalidException;
import com.zsh.mall.service.JwtTokenService;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.util.Date;
import java.util.UUID;

/**
 * Created by zsh on 2022/3/9
 */
@Service
public class JwtTokenServiceImpl implements JwtTokenService {

    @Override
    public String generateTokenByHMAC(String payloadStr, String secret) {
        try {
            //准备JWS-header
            JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256)
                    .type(JOSEObjectType.JWT).build();
            //将负载信息装载到payload
            Payload payload = new Payload(payloadStr);
            //封装header和payload到JWS对象
            JWSObject jwsObject = new JWSObject(jwsHeader, payload);
            //创建HMAC签名器
            JWSSigner jwsSigner = new MACSigner(secret);
            //签名
            jwsObject.sign(jwsSigner);
            return jwsObject.serialize();
        } catch (KeyLengthException e) {
            e.printStackTrace();
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public PayloadDto getDefaultPayloadDto() {
        Date now = new Date();
        Date exp = DateUtil.offsetSecond(now, 60 * 60);
        return PayloadDto.builder()
                .sub("zsh")
                .iat(now.getTime())
                .exp(exp.getTime())
                .jti(UUID.randomUUID().toString())
                .username("zsh")
                .authorities(CollUtil.toList("ADMIN"))
                .build();
    }

    @Override
    public PayloadDto verifyTokenByHMAC(String token, String secret) {
        try {
            JWSObject jwsObject = JWSObject.parse(token);
            //创建HMAC验证器
            JWSVerifier jwsVerifier = new MACVerifier(secret);
            if (!jwsObject.verify(jwsVerifier)) {
                throw new JwtInvalidException(401, "token签名不合法!");
            }
            String payload = jwsObject.getPayload().toString();
            PayloadDto payloadDto = JSONUtil.toBean(payload, PayloadDto.class);
            if (payloadDto.getExp() < new Date().getTime()) {
                throw new JwtExpireException(401, "token已过期!");
            }
            return payloadDto;
        } catch (ParseException | JOSEException e) {
            e.printStackTrace();
        } catch (JwtInvalidException e) {
            e.printStackTrace();
        } catch (JwtExpireException e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • 创建JwtTokenController类,编写依据HMAC算法生成和解析令牌的接口;注意HMAC算法要求密钥的长度至少为32个字节,所以这里我使用了MD5进行了加密充当密钥。
/**
 * Created by zsh on 2022/3/9
 */
@Api(tags = "JwtTokenController", value = "JWT令牌管理")
@RestController
public class JwtTokenController {

    @Resource
    private JwtTokenService jwtTokenService;

    @ApiOperation("使用HMAC对称加密生成token")
    @GetMapping(value = "/hmac/generate")
    public CommonResult generateTokenByHMAC() throws KeyLengthException {
        PayloadDto payloadDto = jwtTokenService.getDefaultPayloadDto();
        String token = jwtTokenService.generateTokenByHMAC(JSONUtil.toJsonStr(payloadDto), SecureUtil.md5("test"));
        return CommonResult.success(token);
    }

    @ApiOperation("验签")
    @GetMapping(value = "/hmac/verify")
    public CommonResult verifyTokenByHMAC(String token) {
        PayloadDto payloadDto = jwtTokenService.verifyTokenByHMAC(token, SecureUtil.md5("test"));
        return CommonResult.success(payloadDto);
    }
}

其他swagger相关的配置和异常类的编写不再展示。

  • 启动访问localhost:8080/swagger-ui.html

在这里插入图片描述
点击测试,发现生成令牌接口正常。
在这里插入图片描述
根据生成的令牌进行解析

在这里插入图片描述
在这里插入图片描述
成功,HMAC对称加密算法生成令牌和解析令牌已经结束!

2.非对称加密(RSA)

非对称加密采用公钥和私钥进行加密和解密。对于加密操作,公钥负责加密,私钥负责解密。对于签名操作,私钥负责签名,公钥负责验签。

对于加密和签名的理解
例如两个端A和B进行通信,A向B发送了一条经过签名和加密的信息;涉及了四个密钥:A公钥、A私钥、B公钥、B私钥。

  • 签名:是为了让B确认这个信息就是A发出的,不是别人
  • 加密:对传输的内容进行保护,即使信息被恶意截取,也无法进行解析。只有B可以查看。

A向B发送信息进行签名和加密的具体流程。

A使用自己的私钥对信息进行签名
A使用B的公钥对信息进行加密

B接收到A发送的信息进行如下处理:

B用自己的私钥对信息进行解密
B使用A的公钥对进行验签操作

所以整个流程保证了端到端的唯一性!

  • 使用JDK提供的keytool工具或者使用git生成jwt.jks,放到resources目录下

在这里插入图片描述

  • 在JwtTokenService接口中定义方法,并在JwtTokenServiceImpl类中实现其业务
    /**
     * 从类路径下加载jwt.jk
     */
    RSAKey loadJKSByClassPath();

    /**
     * 使用RSA非对称算法生成token
     */
    String generateTokenByRSA(String payloadStr, RSAKey rsaKey) throws JOSEException;

    /**
     * 根据RSA非对称算法验证token
     */
    PayloadDto verifyTokenByRSA(String token, RSAKey rsaKey) throws ParseException, JOSEException, JwtInvalidException;
	@Override
    public RSAKey loadJKSByClassPath() {
        //从类路径下加载证书
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
        //获取公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //获取私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey).privateKey(privateKey).build();
    }

    @Override
    public String generateTokenByRSA(String payloadStr, RSAKey rsaKey) throws JOSEException {
        //构建JWS头
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build();
        //构建载荷
        Payload payload = new Payload(payloadStr);
        //将JWS-header和payload封装成JWS对象中
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        //创建签名器
        JWSSigner signer = new RSASSASigner(rsaKey, true);
        jwsObject.sign(signer);
        return jwsObject.serialize();
    }

    @Override
    public PayloadDto verifyTokenByRSA(String token, RSAKey rsaKey) throws ParseException, JOSEException, JwtInvalidException {
        JWSObject jwsObject = JWSObject.parse(token);
        RSAKey verifyKey = rsaKey.toPublicJWK();
        JWSVerifier verifier = new RSASSAVerifier(verifyKey);
        if (!jwsObject.verify(verifier)) {
            throw new JwtInvalidException(401, "签名不合法!");
        }
        String payload = jwsObject.getPayload().toString();
        String substring = payload.substring(11, payload.length() - 1);
        String[] strings = substring.split(",");
        PayloadDto payloadDto = PayloadDto.builder()
                .sub(strings[0])
                .iat(100L)
                .exp(100L)
                .jti(strings[3])
                .username(strings[4])
                .authorities(CollUtil.toList(strings[5]))
                .build();
        return payloadDto;
    }
  • 在JwtTokenController中加入以下接口
    @ApiOperation("获取公钥")
    @GetMapping("/rsa/publicKey")
    public CommonResult getRsaPublicKey() {
        RSAKey key = jwtTokenService.loadJKSByClassPath();
        return CommonResult.success(new JWKSet(key).toJSONObject());
    }

    @ApiOperation("使用RSA非对称加密算法生成token")
    @GetMapping("/rsa/generate")
    public CommonResult generateTokenByRSA() throws JOSEException {
        PayloadDto payloadDto = jwtTokenService.getDefaultPayloadDto();
        RSAKey rsaKey = jwtTokenService.loadJKSByClassPath();
        String token = jwtTokenService.generateTokenByRSA(payloadDto.toString(), rsaKey);
        return CommonResult.success(token);
    }

    @ApiOperation("RSA验签")
    @GetMapping("/rsa/verify")
    public CommonResult verifyTokenByRSA(String token) throws ParseException, JOSEException, JwtInvalidException {
        PayloadDto payloadDto = jwtTokenService.verifyTokenByRSA(token, jwtTokenService.loadJKSByClassPath());
        return CommonResult.success(payloadDto);
    }
  • 测试
    在这里插入图片描述
    在这里插入图片描述
  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2022-03-15 22:56:38  更:2022-03-15 22:57:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 0:46:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码