聽說你的JWT庫用起來特別扭,推薦這款賊好用的

以前一直使用的是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令牌的接口進行測試;

聽說你的JWT庫用起來特別扭,推薦這款賊好用的

 

  • 調用使用HMAC算法解析JWT令牌的接口進行測試。

聽說你的JWT庫用起來特別扭,推薦這款賊好用的

 

非對稱加密(RSA)

非對稱加密指的是使用公鑰和私鑰來進行加密解密操作。對於加密操作,公鑰負責加密,私鑰負責解密,對於簽名操作,私鑰負責簽名,公鑰負責驗證。非對稱加密在JWT中的使用顯然屬於簽名操作。

  • 如果我們需要使用固定的公鑰和私鑰來進行簽名和驗證的話,我們需要生成一個證書文件,這裏將使用Java自帶的keytool工具來生成jks證書文件,該工具在JDK的bin目錄下;

聽說你的JWT庫用起來特別扭,推薦這款賊好用的

 

  • 打開CMD命令界面,使用如下命令生成證書文件,設置別名爲jwt,文件名爲jwt.jks;
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
  • 輸入密碼爲123456,然後輸入各種信息之後就可以生成證書jwt.jks文件了;

聽說你的JWT庫用起來特別扭,推薦這款賊好用的

 

  • 將證書文件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();
    }
}
  • 調用該接口,查看公鑰信息,公鑰是可以公開訪問的;

聽說你的JWT庫用起來特別扭,推薦這款賊好用的

 

  • 在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令牌的接口進行測試;

聽說你的JWT庫用起來特別扭,推薦這款賊好用的

 

  • 調用使用RSA算法解析JWT令牌的接口進行測試。

聽說你的JWT庫用起來特別扭,推薦這款賊好用的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章