longz
2026-05-28 aa67b3cb0fe26fab576f7710c978ff3594426cca
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
                            + " 位");
        }
    }
}