org.dromara.hutool.json.jwt.JWTValidator Maven / Gradle / Ivy
/*
* Copyright (c) 2013-2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.json.jwt;
import org.dromara.hutool.core.date.DateUtil;
import org.dromara.hutool.core.exception.ValidateException;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.json.jwt.signers.JWTSigner;
import org.dromara.hutool.json.jwt.signers.NoneJWTSigner;
import java.util.Date;
/**
* JWT数据校验器,用于校验包括:
*
* - 算法是否一致
* - 算法签名是否正确
* - 字段值是否有效(例如时间未过期等)
*
*
* @author looly
* @since 5.7.2
*/
public class JWTValidator {
private final JWT jwt;
/**
* 创建JWT验证器
*
* @param token JWT Token
* @return JWTValidator
*/
public static JWTValidator of(final String token) {
return new JWTValidator(JWT.of(token));
}
/**
* 创建JWT验证器
*
* @param jwt JWT对象
* @return JWTValidator
*/
public static JWTValidator of(final JWT jwt) {
return new JWTValidator(jwt);
}
/**
* 构造
*
* @param jwt JWT对象
*/
public JWTValidator(final JWT jwt) {
this.jwt = jwt;
}
/**
* 验证算法,使用JWT对象自带的{@link JWTSigner}
*
* @return this
* @throws ValidateException 验证失败的异常
*/
public JWTValidator validateAlgorithm() throws ValidateException {
return validateAlgorithm(null);
}
/**
* 验证算法,使用自定义的{@link JWTSigner}
*
* @param signer 用于验证算法的签名器
* @return this
* @throws ValidateException 验证失败的异常
*/
public JWTValidator validateAlgorithm(final JWTSigner signer) throws ValidateException {
validateAlgorithm(this.jwt, signer);
return this;
}
/**
* 检查JWT的以下三两个时间:
*
*
* - {@link JWTPayload#NOT_BEFORE}:被检查时间必须晚于生效时间
* - {@link JWTPayload#EXPIRES_AT}:被检查时间必须早于失效时间
* - {@link JWTPayload#ISSUED_AT}:签发时间必须早于失效时间
*
*
* 如果某个时间没有设置,则不检查(表示无限制)
*
* @return this
* @throws ValidateException 验证失败的异常
* @since 5.7.3
*/
public JWTValidator validateDate() throws ValidateException {
return validateDate(DateUtil.beginOfSecond(DateUtil.now()));
}
/**
* 检查JWT的以下三两个时间:
*
*
* - {@link JWTPayload#NOT_BEFORE}:生效时间不能晚于当前时间
* - {@link JWTPayload#EXPIRES_AT}:失效时间不能早于当前时间
* - {@link JWTPayload#ISSUED_AT}: 签发时间不能晚于当前时间
*
*
* 如果某个时间没有设置,则不检查(表示无限制)
*
* @param dateToCheck 被检查的时间,一般为当前时间
* @return this
* @throws ValidateException 验证失败的异常
*/
public JWTValidator validateDate(final Date dateToCheck) throws ValidateException {
validateDate(this.jwt.getPayload(), dateToCheck, 0L);
return this;
}
/**
* 检查JWT的以下三两个时间:
*
*
* - {@link JWTPayload#NOT_BEFORE}:生效时间不能晚于当前时间
* - {@link JWTPayload#EXPIRES_AT}:失效时间不能早于当前时间
* - {@link JWTPayload#ISSUED_AT}: 签发时间不能晚于当前时间
*
*
* 如果某个时间没有设置,则不检查(表示无限制)
*
* @param dateToCheck 被检查的时间,一般为当前时间
* @param leeway 容忍空间,单位:秒。当不能晚于当前时间时,向后容忍;不能早于向前容忍。
* @return this
* @throws ValidateException 验证失败的异常
*/
public JWTValidator validateDate(final Date dateToCheck, final long leeway) throws ValidateException {
validateDate(this.jwt.getPayload(), dateToCheck, leeway);
return this;
}
/**
* 验证算法
*
* @param jwt {@link JWT}对象
* @param signer 用于验证的签名器
* @throws ValidateException 验证异常
*/
private static void validateAlgorithm(final JWT jwt, JWTSigner signer) throws ValidateException {
final String algorithmId = jwt.getAlgorithm();
if (null == signer) {
signer = jwt.getSigner();
}
if (StrUtil.isEmpty(algorithmId)) {
// 可能无签名
if (null == signer || signer instanceof NoneJWTSigner) {
return;
}
throw new ValidateException("No algorithm defined in header!");
}
if (null == signer) {
throw new IllegalArgumentException("No Signer for validate algorithm!");
}
final String algorithmIdInSigner = signer.getAlgorithmId();
if (!StrUtil.equals(algorithmId, algorithmIdInSigner)) {
throw new ValidateException("Algorithm [{}] defined in header doesn't match to [{}]!"
, algorithmId, algorithmIdInSigner);
}
// 通过算法验证签名是否正确
if (!jwt.verify(signer)) {
throw new ValidateException("Signature verification failed!");
}
}
/**
* 检查JWT的以下三两个时间:
*
*
* - {@link JWTPayload#NOT_BEFORE}:生效时间不能晚于当前时间
* - {@link JWTPayload#EXPIRES_AT}:失效时间不能早于当前时间
* - {@link JWTPayload#ISSUED_AT}: 签发时间不能晚于当前时间
*
*
* 如果某个时间没有设置,则不检查(表示无限制)
*
* @param payload {@link JWTPayload}
* @param now 当前时间
* @param leeway 容忍空间,单位:秒。当不能晚于当前时间时,向后容忍;不能早于向前容忍。
* @throws ValidateException 验证异常
*/
private static void validateDate(final JWTPayload payload, Date now, final long leeway) throws ValidateException {
if (null == now) {
// 默认当前时间
now = DateUtil.now();
// truncate millis
now.setTime(now.getTime() / 1000 * 1000);
}
// 检查生效时间(生效时间不能晚于当前时间)
final Date notBefore = payload.getClaimsJson().getDate(JWTPayload.NOT_BEFORE);
validateNotAfter(JWTPayload.NOT_BEFORE, notBefore, now, leeway);
// 检查失效时间(失效时间不能早于当前时间)
final Date expiresAt = payload.getClaimsJson().getDate(JWTPayload.EXPIRES_AT);
validateNotBefore(JWTPayload.EXPIRES_AT, expiresAt, now, leeway);
// 检查签发时间(签发时间不能晚于当前时间)
final Date issueAt = payload.getClaimsJson().getDate(JWTPayload.ISSUED_AT);
validateNotAfter(JWTPayload.ISSUED_AT, issueAt, now, leeway);
}
/**
* 验证指定字段的时间不能晚于当前时间
* 被检查的日期不存在则跳过
*
* @param fieldName 字段名
* @param dateToCheck 被检查的字段日期
* @param now 当前时间
* @param leeway 容忍空间,单位:秒。向后容忍
* @throws ValidateException 验证异常
*/
private static void validateNotAfter(final String fieldName, final Date dateToCheck, Date now, final long leeway) throws ValidateException {
if (null == dateToCheck) {
return;
}
if(leeway > 0){
now = DateUtil.date(now.getTime() + leeway * 1000);
}
if (dateToCheck.after(now)) {
throw new ValidateException("'{}':[{}] is after now:[{}]",
fieldName, DateUtil.date(dateToCheck), DateUtil.date(now));
}
}
/**
* 验证指定字段的时间不能早于当前时间
* 被检查的日期不存在则跳过
*
* @param fieldName 字段名
* @param dateToCheck 被检查的字段日期
* @param now 当前时间
* @param leeway 容忍空间,单位:秒。。向前容忍
* @throws ValidateException 验证异常
*/
@SuppressWarnings("SameParameterValue")
private static void validateNotBefore(final String fieldName, final Date dateToCheck, Date now, final long leeway) throws ValidateException {
if (null == dateToCheck) {
return;
}
if(leeway > 0){
now = DateUtil.date(now.getTime() - leeway * 1000);
}
if (dateToCheck.before(now)) {
throw new ValidateException("'{}':[{}] is before now:[{}]",
fieldName, DateUtil.date(dateToCheck), DateUtil.date(now));
}
}
}