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. */@Servicepublic 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)@Builderpublic 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. */@Servicepublic 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. */@Servicepublic 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. */@Servicepublic 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我的项目全套学习教程连载中,关注公众号第一工夫获取。