关于java:听说你的JWT库用起来特别扭推荐一款贼好用的

31次阅读

共计 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 我的项目全套学习教程连载中,关注公众号 第一工夫获取。

正文完
 0