共计 7966 个字符,预计需要花费 20 分钟才能阅读完成。
SpringBoot 实战电商我的项目 mall(35k+star)地址:https://github.com/macrozheng/mall
摘要
以前始终应用的是 jjwt
这个 JWT 库,尽管玲珑够用, 但对 JWT 的一些细节封装的不是很好。最近发现了一个更好用的 JWT 库nimbus-jose-jwt
,简略易用,API 十分易于了解,对称加密和非对称加密算法都反对,举荐给大家!
简介
nimbus-jose-jwt
是最受欢迎的 JWT 开源库,基于 Apache 2.0 开源协定,反对所有规范的签名 (JWS) 和加密 (JWE) 算法。
JWT 概念关系
这里咱们须要理解下 JWT、JWS、JWE 三者之间的关系,其实 JWT(JSON Web Token)指的是一种标准,这种标准容许咱们应用 JWT 在两个组织之间传递安全可靠的信息。而 JWS(JSON Web Signature)和 JWE(JSON Web Encryption)是 JWT 标准的两种不同实现,咱们平时最常应用的实现就是 JWS。
应用
接下来咱们将介绍下
nimbus-jose-jwt
库的应用,次要应用对称加密(HMAC)和非对称加密(RSA)两种算法来生成和解析 JWT 令牌。
对称加密(HMAC)
对称加密指的是应用
雷同
的秘钥来进行加密和解密,如果你的秘钥不想裸露给解密方,思考应用非对称加密。
- 要应用
nimbus-jose-jwt
库,首先在pom.xml
增加相干依赖;
<!--JWT 解析库 -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>8.16</version>
</dependency>
- 创立
JwtTokenServiceImpl
作为 JWT 解决的业务类,增加依据HMAC
算法生成和解析 JWT 令牌的办法,能够发现nimbus-jose-jwt
库操作 JWT 的 API 十分易于了解;
/**
* Created by macro on 2020/6/22.
*/
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
@Override
public String generateTokenByHMAC(String payloadStr, String secret) throws JOSEException {
// 创立 JWS 头,设置签名算法和类型
JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).
type(JOSEObjectType.JWT)
.build();
// 将负载信息封装到 Payload 中
Payload payload = new Payload(payloadStr);
// 创立 JWS 对象
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 创立 HMAC 签名器
JWSSigner jwsSigner = new MACSigner(secret);
// 签名
jwsObject.sign(jwsSigner);
return jwsObject.serialize();}
@Override
public PayloadDto verifyTokenByHMAC(String token, String secret) throws ParseException, JOSEException {
// 从 token 中解析 JWS 对象
JWSObject jwsObject = JWSObject.parse(token);
// 创立 HMAC 验证器
JWSVerifier jwsVerifier = new MACVerifier(secret);
if (!jwsObject.verify(jwsVerifier)) {throw new JwtInvalidException("token 签名不非法!");
}
String payload = jwsObject.getPayload().toString();
PayloadDto payloadDto = JSONUtil.toBean(payload, PayloadDto.class);
if (payloadDto.getExp() < new Date().getTime()) {throw new JwtExpiredException("token 已过期!");
}
return payloadDto;
}
}
- 创立
PayloadDto
实体类,用于封装 JWT 中存储的信息;
/**
* Created by macro on 2020/6/22.
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
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;
}
- 在
JwtTokenServiceImpl
类中增加获取默认的 PayloadDto 的办法,JWT 过期工夫设置为60s
;
/**
* Created by macro on 2020/6/22.
*/
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
@Override
public PayloadDto getDefaultPayloadDto() {Date now = new Date();
Date exp = DateUtil.offsetSecond(now, 60*60);
return PayloadDto.builder()
.sub("macro")
.iat(now.getTime())
.exp(exp.getTime())
.jti(UUID.randomUUID().toString())
.username("macro")
.authorities(CollUtil.toList("ADMIN"))
.build();}
}
- 创立
JwtTokenController
类,增加依据 HMAC 算法生成和解析 JWT 令牌的接口,因为 HMAC 算法须要长度至多为32 个字节
的秘钥,所以咱们应用 MD5 加密下;
/**
* JWT 令牌治理 Controller
* Created by macro on 2020/6/22.
*/
@Api(tags = "JwtTokenController", description = "JWT 令牌治理")
@Controller
@RequestMapping("/token")
public class JwtTokenController {
@Autowired
private JwtTokenService jwtTokenService;
@ApiOperation("应用对称加密(HMAC)算法生成 token")
@RequestMapping(value = "/hmac/generate", method = RequestMethod.GET)
@ResponseBody
public CommonResult generateTokenByHMAC() {
try {PayloadDto payloadDto = jwtTokenService.getDefaultPayloadDto();
String token = jwtTokenService.generateTokenByHMAC(JSONUtil.toJsonStr(payloadDto), SecureUtil.md5("test"));
return CommonResult.success(token);
} catch (JOSEException e) {e.printStackTrace();
}
return CommonResult.failed();}
@ApiOperation("应用对称加密(HMAC)算法验证 token")
@RequestMapping(value = "/hmac/verify", method = RequestMethod.GET)
@ResponseBody
public CommonResult verifyTokenByHMAC(String token) {
try {PayloadDto payloadDto = jwtTokenService.verifyTokenByHMAC(token, SecureUtil.md5("test"));
return CommonResult.success(payloadDto);
} catch (ParseException | JOSEException e) {e.printStackTrace();
}
return CommonResult.failed();}
}
- 调用应用 HMAC 算法生成 JWT 令牌的接口进行测试;
- 调用应用 HMAC 算法解析 JWT 令牌的接口进行测试。
非对称加密(RSA)
非对称加密指的是应用公钥和私钥来进行加密解密操作。对于
加密
操作,公钥负责加密,私钥负责解密,对于签名
操作,私钥负责签名,公钥负责验证。非对称加密在 JWT 中的应用显然属于签名
操作。
- 如果咱们须要应用固定的公钥和私钥来进行签名和验证的话,咱们须要生成一个证书文件,这里将应用 Java 自带的
keytool
工具来生成jks
证书文件,该工具在 JDK 的bin
目录下;
- 关上 CMD 命令界面,应用如下命令生成证书文件,设置别名为
jwt
,文件名为jwt.jks
;
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
- 输出明码为
123456
,而后输出各种信息之后就能够生成证书jwt.jks
文件了;
- 将证书文件
jwt.jks
复制到我的项目的resource
目录下,而后须要从证书文件中读取RSAKey
,这里咱们须要在pom.xml
中增加一个 Spring Security 的 RSA 依赖;
<!--Spring Security RSA 工具类 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
<version>1.0.7.RELEASE</version>
</dependency>
- 而后在
JwtTokenServiceImpl
类中增加办法,从类门路下读取证书文件并转换为RSAKey
对象;
/**
* Created by macro on 2020/6/22.
*/
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
@Override
public RSAKey getDefaultRSAKey() {
// 从 classpath 下获取 RSA 秘钥对
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
KeyPair keyPair = keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
// 获取 RSA 公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 获取 RSA 私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey).privateKey(privateKey).build();}
}
- 咱们能够在
JwtTokenController
中增加一个接口,用于获取证书中的公钥;
/**
* JWT 令牌治理 Controller
* Created by macro on 2020/6/22.
*/
@Api(tags = "JwtTokenController", description = "JWT 令牌治理")
@Controller
@RequestMapping("/token")
public class JwtTokenController {
@Autowired
private JwtTokenService jwtTokenService;
@ApiOperation("获取非对称加密(RSA)算法公钥")
@RequestMapping(value = "/rsa/publicKey", method = RequestMethod.GET)
@ResponseBody
public Object getRSAPublicKey() {RSAKey key = jwtTokenService.getDefaultRSAKey();
return new JWKSet(key).toJSONObject();}
}
- 调用该接口,查看公钥信息,公钥是能够公开拜访的;
- 在
JwtTokenServiceImpl
中增加依据RSA
算法生成和解析 JWT 令牌的办法,能够发现和下面的HMAC
算法操作基本一致;
/**
* Created by macro on 2020/6/22.
*/
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
@Override
public String generateTokenByRSA(String payloadStr, RSAKey rsaKey) throws JOSEException {
// 创立 JWS 头,设置签名算法和类型
JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256)
.type(JOSEObjectType.JWT)
.build();
// 将负载信息封装到 Payload 中
Payload payload = new Payload(payloadStr);
// 创立 JWS 对象
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 创立 RSA 签名器
JWSSigner jwsSigner = new RSASSASigner(rsaKey, true);
// 签名
jwsObject.sign(jwsSigner);
return jwsObject.serialize();}
@Override
public PayloadDto verifyTokenByRSA(String token, RSAKey rsaKey) throws ParseException, JOSEException {
// 从 token 中解析 JWS 对象
JWSObject jwsObject = JWSObject.parse(token);
RSAKey publicRsaKey = rsaKey.toPublicJWK();
// 应用 RSA 公钥创立 RSA 验证器
JWSVerifier jwsVerifier = new RSASSAVerifier(publicRsaKey);
if (!jwsObject.verify(jwsVerifier)) {throw new JwtInvalidException("token 签名不非法!");
}
String payload = jwsObject.getPayload().toString();
PayloadDto payloadDto = JSONUtil.toBean(payload, PayloadDto.class);
if (payloadDto.getExp() < new Date().getTime()) {throw new JwtExpiredException("token 已过期!");
}
return payloadDto;
}
}
- 在
JwtTokenController
类,增加依据 RSA 算法生成和解析 JWT 令牌的接口,应用默认的 RSA 钥匙对;
/**
* JWT 令牌治理 Controller
* Created by macro on 2020/6/22.
*/
@Api(tags = "JwtTokenController", description = "JWT 令牌治理")
@Controller
@RequestMapping("/token")
public class JwtTokenController {
@Autowired
private JwtTokenService jwtTokenService;
@ApiOperation("应用非对称加密(RSA)算法生成 token")
@RequestMapping(value = "/rsa/generate", method = RequestMethod.GET)
@ResponseBody
public CommonResult generateTokenByRSA() {
try {PayloadDto payloadDto = jwtTokenService.getDefaultPayloadDto();
String token = jwtTokenService.generateTokenByRSA(JSONUtil.toJsonStr(payloadDto),jwtTokenService.getDefaultRSAKey());
return CommonResult.success(token);
} catch (JOSEException e) {e.printStackTrace();
}
return CommonResult.failed();}
@ApiOperation("应用非对称加密(RSA)算法验证 token")
@RequestMapping(value = "/rsa/verify", method = RequestMethod.GET)
@ResponseBody
public CommonResult verifyTokenByRSA(String token) {
try {PayloadDto payloadDto = jwtTokenService.verifyTokenByRSA(token, jwtTokenService.getDefaultRSAKey());
return CommonResult.success(payloadDto);
} catch (ParseException | JOSEException e) {e.printStackTrace();
}
return CommonResult.failed();}
}
- 调用应用 RSA 算法生成 JWT 令牌的接口进行测试;
- 调用应用 RSA 算法解析 JWT 令牌的接口进行测试。
参考资料
官网文档:https://connect2id.com/produc…
我的项目源码地址
https://github.com/macrozheng…
公众号
mall 我的项目全套学习教程连载中,关注公众号 第一工夫获取。