目录
前言
一、创建token
二、验证token
总结
JWT在创建的token由header、payload和signature三部分组成,每一部分使用"."分隔,其中header中存储加密的模型,payload中存储需要传输的信息和过期时间等,signature中存储加密后的字符串,使用base64编码提高安全性。在校验的时候也是根据上述规则进行分割,分别进行校验。
在项目中,我们通过调用JWT.create.sign()方法,创建token,并返给前端写在header中,用作身份证明。
public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException {
if (algorithm == null) {
throw new IllegalArgumentException("The Algorithm cannot be null.");
}
headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
headerClaims.put(PublicClaims.TYPE, "JWT");
String signingKeyId = algorithm.getSigningKeyId();
if (signingKeyId != null) {
withKeyId(signingKeyId);
}
return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();
}
在sign方法中,返回的token由三部分组成,中间用"."隔开,分别是header、payload和signature,其中header中存储algorithm,headerClaims中存储用户的校验规则以及一些自定义字段,signature中存储加密后的字符串,在校验时使用。
private String sign() throws SignatureGenerationException {
String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
String content = String.format("%s.%s", header, payload);
byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
String signature = Base64.encodeBase64URLSafeString((signatureBytes));
return String.format("%s.%s", content, signature);
}
项目中通过verify方法校验token的有效性。
public DecodedJWT verify(String token) throws JWTVerificationException {
DecodedJWT jwt = JWT.decode(token);
verifyAlgorithm(jwt, algorithm);
algorithm.verify(jwt);
verifyClaims(jwt, claims);
return jwt;
}
在verify方法中,首先创建了一个DecodedJWT,基本就是将传进来的token字符串以"."作为分隔符进行分割,然后将前两个部分分别作为headerJson和payloadJson。如果token的格式不正确,即token不是以"."分割的,会抛出JWTDecodeException异常。
JWTDecoder(String jwt) throws JWTDecodeException {
parts = TokenUtils.splitToken(jwt);
final JWTParser converter = new JWTParser();
String headerJson;
String payloadJson;
try {
headerJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[0]));
payloadJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[1]));
} catch (NullPointerException e) {
throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e);
}
header = converter.parseHeader(headerJson);
payload = converter.parsePayload(payloadJson);
}
static String[] splitToken(String token) throws JWTDecodeException {
String[] parts = token.split("\\.");
if (parts.length == 2 && token.endsWith(".")) {
//Tokens with alg='none' have empty String as Signature.
parts = new String[]{parts[0], parts[1], ""};
}
if (parts.length != 3) {
throw new JWTDecodeException(String.format("The token was expected to have 3 parts, but got %s.", parts.length));
}
return parts;
}
在verify方法中,将上个方法中获取的header和payload组合成content,然后使用给定方法进行加密,将结果与传入的signature对比,如果相同,校验通过。
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
try {
boolean valid = crypto.verifySignatureFor(getDescription(), secret, contentBytes, signatureBytes);
if (!valid) {
throw new SignatureVerificationException(this);
}
} catch (IllegalStateException | InvalidKeyException | NoSuchAlgorithmException e) {
throw new SignatureVerificationException(this, e);
}
}
boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, contentBytes), signatureBytes);
}
在verifyClaims方法中,默认情况下,JWTVerifier就会将过期时间相关的校验添加进去,基本就是对用户规定的过期时间,字段等进行校验,在前面已经将这些字段从token字符串中解析出来,这里只需要做一下判断。
private void verifyClaims(DecodedJWT jwt, Map claims) throws TokenExpiredException, InvalidClaimException {
for (Map.Entry entry : claims.entrySet()) {
switch (entry.getKey()) {
case PublicClaims.AUDIENCE:
//noinspection unchecked
assertValidAudienceClaim(jwt.getAudience(), (List) entry.getValue());
break;
case PublicClaims.EXPIRES_AT:
assertValidDateClaim(jwt.getExpiresAt(), (Long) entry.getValue(), true);
break;
case PublicClaims.ISSUED_AT:
assertValidDateClaim(jwt.getIssuedAt(), (Long) entry.getValue(), false);
break;
case PublicClaims.NOT_BEFORE:
assertValidDateClaim(jwt.getNotBefore(), (Long) entry.getValue(), false);
break;
case PublicClaims.ISSUER:
assertValidStringClaim(entry.getKey(), jwt.getIssuer(), (String) entry.getValue());
break;
case PublicClaims.JWT_ID:
assertValidStringClaim(entry.getKey(), jwt.getId(), (String) entry.getValue());
break;
case PublicClaims.SUBJECT:
assertValidStringClaim(entry.getKey(), jwt.getSubject(), (String) entry.getValue());
break;
default:
assertValidClaim(jwt.getClaim(entry.getKey()), entry.getKey(), entry.getValue());
break;
}
}
}
本文主要对JWT的token生成和校验过程进行分析,如有不足之处,欢迎大家指出。