什么是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 {@Overridepublic String generateTokenByHMAC(String payloadStr, String secret) {try {//准备JWS-headerJWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();//将负载信息装载到payloadPayload 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;}@Overridepublic 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();}@Overridepublic 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 {@Resourceprivate 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;
@Overridepublic 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();}@Overridepublic 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();}@Overridepublic 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);}
- 测试


















