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、JWS、JWE三者之間的關係,其實JWT(JSON Web Token)指的是一種規範,這種規範容許咱們使用JWT在兩個組織之間傳遞安全可靠的信息。而JWS(JSON Web Signature)和JWE(JSON Web Encryption)是JWT規範的兩種不一樣實現,咱們平時最常使用的實現就是JWS。算法
接下來咱們將介紹下
nimbus-jose-jwt
庫的使用,主要使用對稱加密(HMAC)和非對稱加密(RSA)兩種算法來生成和解析JWT令牌。spring
對稱加密指的是使用
相同
的祕鑰來進行加密和解密,若是你的祕鑰不想暴露給解密方,考慮使用非對稱加密。安全
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();
}
}
複製代碼
非對稱加密指的是使用公鑰和私鑰來進行加密解密操做。對於
加密
操做,公鑰負責加密,私鑰負責解密,對於簽名
操做,私鑰負責簽名,公鑰負責驗證。非對稱加密在JWT中的使用顯然屬於簽名
操做。bash
keytool
工具來生成jks
證書文件,該工具在JDK的bin
目錄下;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();
}
}
複製代碼
官方文檔:connect2id.com/products/ni…app
mall項目全套學習教程連載中,關注公衆號第一時間獲取。ide