package com.sinata.appointment.service;
|
|
import com.sinata.appointment.config.AdminLoginCryptoProperties;
|
import com.sinata.appointment.constants.BizErrorCode;
|
import com.sinata.appointment.exception.BizException;
|
import java.nio.charset.StandardCharsets;
|
import java.time.Instant;
|
import java.util.Base64;
|
import javax.crypto.Cipher;
|
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.SecretKeySpec;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
|
@Service
|
@RequiredArgsConstructor
|
public class AdminLoginCryptoService {
|
|
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
private static final String AES = "AES";
|
private static final String SEPARATOR = "|";
|
|
private final AdminLoginCryptoProperties properties;
|
|
public String decryptLoginField(String encryptedValue, String fieldLabel) {
|
if (!StringUtils.hasText(encryptedValue)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, fieldLabel + "不能为空");
|
}
|
String plain = decryptAes(encryptedValue.trim());
|
int separatorIndex = plain.indexOf(SEPARATOR);
|
if (separatorIndex <= 0 || separatorIndex >= plain.length() - 1) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "登录信息格式错误,请重试");
|
}
|
long timestamp = parseTimestamp(plain.substring(0, separatorIndex));
|
validateTimestamp(timestamp);
|
String value = plain.substring(separatorIndex + 1);
|
if (!StringUtils.hasText(value)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, fieldLabel + "不能为空");
|
}
|
return value.trim();
|
}
|
|
private String decryptAes(String encryptedValue) {
|
validateKeyAndIv();
|
try {
|
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
SecretKeySpec keySpec =
|
new SecretKeySpec(properties.getKey().getBytes(StandardCharsets.UTF_8), AES);
|
IvParameterSpec ivSpec =
|
new IvParameterSpec(properties.getIv().getBytes(StandardCharsets.UTF_8));
|
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
byte[] decrypted =
|
cipher.doFinal(Base64.getDecoder().decode(encryptedValue.getBytes(StandardCharsets.UTF_8)));
|
return new String(decrypted, StandardCharsets.UTF_8);
|
} catch (Exception ex) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "登录信息解密失败,请重试");
|
}
|
}
|
|
private long parseTimestamp(String raw) {
|
try {
|
return Long.parseLong(raw);
|
} catch (NumberFormatException ex) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "登录信息时间戳无效,请重试");
|
}
|
}
|
|
private void validateTimestamp(long timestamp) {
|
long now = Instant.now().toEpochMilli();
|
if (Math.abs(now - timestamp) > properties.getMaxSkewMillis()) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "登录信息已过期,请刷新后重试");
|
}
|
}
|
|
private void validateKeyAndIv() {
|
if (!StringUtils.hasText(properties.getKey())
|
|| !StringUtils.hasText(properties.getIv())
|
|| properties.getKey().length() != 16
|
|| properties.getIv().length() != 16) {
|
int keyLength = properties.getKey() == null ? 0 : properties.getKey().length();
|
int ivLength = properties.getIv() == null ? 0 : properties.getIv().length();
|
throw new IllegalStateException(
|
"管理后台登录AES配置无效,key/iv 必须为16位字符,当前 key="
|
+ keyLength
|
+ " 位, iv="
|
+ ivLength
|
+ " 位");
|
}
|
}
|
}
|