據說你的JWT庫用起來特別扭,推薦一款賊好用的!

SpringBoot實戰電商項目mall(35k+star)地址:github.com/macrozheng/…java

摘要

之前一直使用的是jjwt這個JWT庫,雖然小巧夠用,但對JWT的一些細節封裝的不是很好。最近發現了一個更好用的JWT庫nimbus-jose-jwt,簡單易用,API很是易於理解,對稱加密和非對稱加密算法都支持,推薦給你們!git

簡介

nimbus-jose-jwt是最受歡迎的JWT開源庫,基於Apache 2.0開源協議,支持全部標準的簽名(JWS)和加密(JWE)算法。github

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令牌。spring

對稱加密(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中的使用顯然屬於簽名操做。bash

  • 若是咱們須要使用固定的公鑰和私鑰來進行簽名和驗證的話,咱們須要生成一個證書文件,這裏將使用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令牌的接口進行測試。

參考資料

官方文檔:connect2id.com/products/ni…app

項目源碼地址

github.com/macrozheng/…dom

公衆號

mall項目全套學習教程連載中,關注公衆號第一時間獲取。ide

公衆號圖片
相關文章
相關標籤/搜索