From 41a732f8e97c8fd65d7f461f7065e073aa42499d Mon Sep 17 00:00:00 2001 From: liulu Date: Tue, 17 Mar 2026 11:31:35 +0800 Subject: [PATCH] =?UTF-8?q?ukey=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cisd/tms/common/util/IpWhitelistUtil.java | 98 +++++++++ .../tms/common/util/Sm2SignatureUtil.java | 72 +++++++ .../mk/config/UKeyLoginProperties.java | 52 +++++ .../mk/controller/LoginController.java | 32 +++ .../com/cisd/tms/modules/mk/dto/AuthInfo.java | 24 +++ .../com/cisd/tms/modules/mk/dto/LoginDTO.java | 142 +++++++++++++ .../cisd/tms/modules/mk/dto/LoginSignDTO.java | 40 ++++ .../cisd/tms/modules/mk/dto/SecretKey.java | 18 ++ .../com/cisd/tms/modules/mk/dto/UserInfo.java | 70 +++++++ .../modules/mk/service/UKeyLoginService.java | 195 ++++++++++++++++++ 10 files changed, 743 insertions(+) create mode 100644 src/main/java/com/cisd/tms/common/util/IpWhitelistUtil.java create mode 100644 src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/config/UKeyLoginProperties.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/controller/LoginController.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/dto/AuthInfo.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/dto/LoginDTO.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/dto/LoginSignDTO.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/dto/SecretKey.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/dto/UserInfo.java create mode 100644 src/main/java/com/cisd/tms/modules/mk/service/UKeyLoginService.java diff --git a/src/main/java/com/cisd/tms/common/util/IpWhitelistUtil.java b/src/main/java/com/cisd/tms/common/util/IpWhitelistUtil.java new file mode 100644 index 0000000..56d6059 --- /dev/null +++ b/src/main/java/com/cisd/tms/common/util/IpWhitelistUtil.java @@ -0,0 +1,98 @@ +package com.cisd.tms.common.util; + +import jakarta.servlet.http.HttpServletRequest; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +public final class IpWhitelistUtil { + + private IpWhitelistUtil() { + } + + public static String getClientIp(HttpServletRequest request) { + String ipAddress = request.getHeader("X-Forwarded-For"); + if (isBlankOrUnknown(ipAddress)) { + ipAddress = request.getHeader("Proxy-Client-IP"); + } + if (isBlankOrUnknown(ipAddress)) { + ipAddress = request.getHeader("WL-Proxy-Client-IP"); + } + if (isBlankOrUnknown(ipAddress)) { + ipAddress = request.getRemoteAddr(); + } + return ipAddress; + } + + public static List readWhitelist(String path) { + List whitelist = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.isBlank()) { + whitelist.add(line.trim()); + } + } + } catch (IOException e) { + // ignore when whitelist file missing + } + return whitelist; + } + + public static boolean isIpInWhitelist(String ipAddress, List whitelist) { + for (String entry : whitelist) { + if (entry.contains("/")) { + if (isIpInCidr(ipAddress, entry)) { + return true; + } + String cidrAddress = entry.split("/")[0]; + if (cidrAddress.equals(ipAddress)) { + return true; + } + } else if (entry.equals(ipAddress)) { + return true; + } + } + return false; + } + + private static boolean isIpInCidr(String ipAddress, String cidr) { + try { + String[] parts = cidr.split("/"); + if (parts.length != 2) { + return false; + } + InetAddress inetAddr = InetAddress.getByName(parts[0]); + int prefixLength = Integer.parseInt(parts[1]); + byte[] network = inetAddr.getAddress(); + byte[] ip = InetAddress.getByName(ipAddress).getAddress(); + if (network.length != ip.length) { + return false; + } + int fullBytes = prefixLength / 8; + int remainingBits = prefixLength % 8; + for (int i = 0; i < fullBytes; i++) { + if (network[i] != ip[i]) { + return false; + } + } + if (remainingBits > 0) { + int mask = (-1) << (8 - remainingBits); + if ((network[fullBytes] & mask) != (ip[fullBytes] & mask)) { + return false; + } + } + return true; + } catch (Exception e) { + return false; + } + } + + private static boolean isBlankOrUnknown(String value) { + return value == null || value.isEmpty() || "unknown".equalsIgnoreCase(value); + } +} diff --git a/src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java b/src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java new file mode 100644 index 0000000..814ee20 --- /dev/null +++ b/src/main/java/com/cisd/tms/common/util/Sm2SignatureUtil.java @@ -0,0 +1,72 @@ +package com.cisd.tms.common.util; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.Security; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public final class Sm2SignatureUtil { + + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALGO = "SM3withSM2"; + + static { + if (Security.getProvider(PROVIDER) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + private Sm2SignatureUtil() { + } + + public static boolean verifyBase64Signature(String publicKey, String data, String signatureBase64) { + if (publicKey == null || publicKey.isBlank()) { + throw new IllegalArgumentException("public key is required"); + } + if (data == null) { + throw new IllegalArgumentException("data is required"); + } + if (signatureBase64 == null || signatureBase64.isBlank()) { + throw new IllegalArgumentException("signature is required"); + } + try { + PublicKey key = parsePublicKey(publicKey); + Signature signature = Signature.getInstance(SIGNATURE_ALGO, PROVIDER); + signature.initVerify(key); + signature.update(data.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64.trim()); + return signature.verify(signatureBytes); + } catch (Exception e) { + throw new IllegalArgumentException("sm2 verify failed", e); + } + } + + private static PublicKey parsePublicKey(String publicKey) throws Exception { + String normalized = stripPem(publicKey); + byte[] keyBytes = Base64.getDecoder().decode(normalized.getBytes(StandardCharsets.UTF_8)); + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("EC", PROVIDER); + return keyFactory.generatePublic(spec); + } + + private static String stripPem(String key) { + String trimmed = key.trim(); + if (!trimmed.contains("BEGIN")) { + return trimmed; + } + StringBuilder builder = new StringBuilder(); + String[] lines = trimmed.split("\\R"); + for (String line : lines) { + if (line.startsWith("-----")) { + continue; + } + builder.append(line.trim()); + } + return builder.toString(); + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/config/UKeyLoginProperties.java b/src/main/java/com/cisd/tms/modules/mk/config/UKeyLoginProperties.java new file mode 100644 index 0000000..15d03c8 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/config/UKeyLoginProperties.java @@ -0,0 +1,52 @@ +package com.cisd.tms.modules.mk.config; + +import com.cisd.tms.modules.mk.dto.UserInfo; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@ConfigurationProperties(prefix = "tms.ukey") +public class UKeyLoginProperties { + + /** + * 认证信息白名单。 + */ + private List authenticationInfoList = new ArrayList<>(); + + /** + * 角色密码映射。 + */ + private Map rolePasswords = new HashMap<>(); + + /** + * IP 白名单文件路径。 + */ + private String whitelistPath; + + public List getAuthenticationInfoList() { + return authenticationInfoList; + } + + public void setAuthenticationInfoList(List authenticationInfoList) { + this.authenticationInfoList = authenticationInfoList; + } + + public Map getRolePasswords() { + return rolePasswords; + } + + public void setRolePasswords(Map rolePasswords) { + this.rolePasswords = rolePasswords; + } + + public String getWhitelistPath() { + return whitelistPath; + } + + public void setWhitelistPath(String whitelistPath) { + this.whitelistPath = whitelistPath; + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/controller/LoginController.java b/src/main/java/com/cisd/tms/modules/mk/controller/LoginController.java new file mode 100644 index 0000000..cbffe52 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/controller/LoginController.java @@ -0,0 +1,32 @@ +package com.cisd.tms.modules.mk.controller; + +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.modules.mk.dto.AuthInfo; +import com.cisd.tms.modules.mk.dto.LoginDTO; +import com.cisd.tms.modules.mk.service.UKeyLoginService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * UKey 登录接口迁移自 manager.service。 + */ +@RestController +@RequestMapping("/api/v1") +@Tag(name = "UKey 登录", description = "UKey 登录校验与认证信息生成") +public class LoginController { + + @Resource + private UKeyLoginService uKeyLoginService; + + @PostMapping("/UKeyLogin2") + @Operation(summary = "UKey 登录", description = "校验 UKey 签名、随机数与角色密码,并返回认证信息。") + public ApiResponse uKeyLogin(@RequestBody LoginDTO loginDTO, HttpServletRequest request) { + return ApiResponse.success(uKeyLoginService.uKeyLogin(loginDTO, request)); + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/AuthInfo.java b/src/main/java/com/cisd/tms/modules/mk/dto/AuthInfo.java new file mode 100644 index 0000000..920d487 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/dto/AuthInfo.java @@ -0,0 +1,24 @@ +package com.cisd.tms.modules.mk.dto; + +import lombok.Data; + +import java.security.SecureRandom; + +@Data +public class AuthInfo extends SecretKey { + private String token; + + public static AuthInfo getInstance(String token) { + AuthInfo authInfo = new AuthInfo(); + authInfo.token = token; + authInfo.setKey(generateKey()); + authInfo.setIv(generateKey()); + return authInfo; + } + + private static byte[] generateKey() { + byte[] key = new byte[16]; + new SecureRandom().nextBytes(key); + return key; + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/LoginDTO.java b/src/main/java/com/cisd/tms/modules/mk/dto/LoginDTO.java new file mode 100644 index 0000000..f2b35c7 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/dto/LoginDTO.java @@ -0,0 +1,142 @@ +package com.cisd.tms.modules.mk.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * 登录时传输的实体 + */ +@Data +public class LoginDTO { + /** + * 选择的角色 + */ + private String roleSelected; + /** + * 登录的角色 + */ + private String role; + /** + * 认证信息 + */ + private List authInfo; + /** + * 密码 + */ + private String password; + + public Set getRids() { + Set roles = new HashSet<>(); + if (authInfo == null) { + return roles; + } + for (LoginAuthInfo auth : authInfo) { + roles.add(auth.rid); + } + return roles; + } + + public List getRandom() { + List randomList = new ArrayList<>(); + if (authInfo == null) { + return randomList; + } + for (LoginAuthInfo loginAuthInfo : authInfo) { + randomList.add(loginAuthInfo.getRb()); + } + return randomList; + } + + public UserInfo getUserInfo() { + UserInfo userInfo = new UserInfo(); + userInfo.setRole(this.role); + List userList = new ArrayList<>(); + if (authInfo != null) { + for (LoginAuthInfo loginAuthInfo : authInfo) { + UserInfo.User user = new UserInfo.User(); + user.setUid(loginAuthInfo.getUid()); + user.setRid(loginAuthInfo.getRid()); + userList.add(user); + } + } + userInfo.setUser(userList); + return userInfo; + } + + public List getUKeySignVerifyDTOList() { + List uKeySignVerifyDTOList = new ArrayList<>(); + if (authInfo == null) { + return uKeySignVerifyDTOList; + } + for (LoginAuthInfo loginAuthInfo : authInfo) { + UKeySignVerifyDTO uKeySignVerifyDTO = new UKeySignVerifyDTO(); + UKeySignDTO uKeySignDTO = new UKeySignDTO(); + uKeySignDTO.setPubKey(loginAuthInfo.pubKey); + uKeySignDTO.setRole(this.role); + uKeySignDTO.setUid(loginAuthInfo.uid); + uKeySignDTO.setRid(loginAuthInfo.rid); + + uKeySignVerifyDTO.setuKeySignDTO(uKeySignDTO); + uKeySignVerifyDTO.setSign(loginAuthInfo.issueSign); + + uKeySignVerifyDTOList.add(uKeySignVerifyDTO); + } + return uKeySignVerifyDTOList; + } + + public List getLoginSignDTOList() { + List loginSignDTOList = new ArrayList<>(); + if (authInfo == null) { + return loginSignDTOList; + } + for (LoginAuthInfo loginAuthInfo : authInfo) { + LoginSignDTO loginSignDTO = new LoginSignDTO(); + loginSignDTO.setPubKey(loginAuthInfo.pubKey); + loginSignDTO.setSignValue(loginAuthInfo.loginSign); + loginSignDTO.setLoginSignData(loginAuthInfo.loginSignData); + + loginSignDTOList.add(loginSignDTO); + } + return loginSignDTOList; + } + + @Data + public static class LoginAuthInfo { + /** + * U盾公钥 + */ + private String pubKey; + /** + * U盾 uid + */ + private String uid; + /** + * U盾 rid + */ + private String rid; + /** + * 前端生成随机数 + */ + private String ra; + /** + * 后端生成随机数 + */ + private String rb; + /** + * 发行签名 + */ + private String issueSign; + /** + * 登录签名的原始值 + */ + private String loginSignData; + /** + * 登录签名 + */ + private String loginSign; + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/LoginSignDTO.java b/src/main/java/com/cisd/tms/modules/mk/dto/LoginSignDTO.java new file mode 100644 index 0000000..a141be5 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/dto/LoginSignDTO.java @@ -0,0 +1,40 @@ +package com.cisd.tms.modules.mk.dto; + +public class LoginSignDTO { + /** + * U盾公钥 + */ + private String pubKey; + /** + * 签名原始数据 + */ + private String loginSignData; + /** + * 签名值 + */ + private String signValue; + + public String getPubKey() { + return pubKey; + } + + public void setPubKey(String pubKey) { + this.pubKey = pubKey; + } + + public String getLoginSignData() { + return loginSignData; + } + + public void setLoginSignData(String loginSignData) { + this.loginSignData = loginSignData; + } + + public String getSignValue() { + return signValue; + } + + public void setSignValue(String signValue) { + this.signValue = signValue; + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/SecretKey.java b/src/main/java/com/cisd/tms/modules/mk/dto/SecretKey.java new file mode 100644 index 0000000..57319f3 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/dto/SecretKey.java @@ -0,0 +1,18 @@ +package com.cisd.tms.modules.mk.dto; + +import lombok.Data; + +/** + * 加密密钥及偏移量 + */ +@Data +public class SecretKey { + /** + * 密钥 + */ + private byte[] key; + /** + * 偏移量 + */ + private byte[] iv; +} diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/UserInfo.java b/src/main/java/com/cisd/tms/modules/mk/dto/UserInfo.java new file mode 100644 index 0000000..3b1ff1f --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/dto/UserInfo.java @@ -0,0 +1,70 @@ +package com.cisd.tms.modules.mk.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * 角色信息 + */ +@Data +public class UserInfo { + /** + * 角色名称 + */ + private String role; + + private Integer defpassflag; + + private List user; + + private List permissions; + + @Data + public static class User { + /** + * uid + */ + private String uid; + /** + * rid + */ + private String rid; + + public boolean isSameUser(User user) { + return this.uid.equals(user.uid) && this.rid.equals(user.rid); + } + } + + /** + * 认证信息校验,校验配置文件中的认证信息和传入的认证信息是否相同 + */ + public boolean roleUserAuth(List authenticationInfoList) { + List userConfig = new ArrayList<>(getUserFromAuthList(authenticationInfoList)); + for (User current : this.user) { + Iterator iterable = userConfig.iterator(); + while (iterable.hasNext()) { + if (iterable.next().isSameUser(current)) { + iterable.remove(); + break; + } + } + } + return userConfig.isEmpty(); + } + + /** + * 获取配置信息当中相应角色应包含的认证信息 + */ + public List getUserFromAuthList(List authenticationInfoList) { + for (UserInfo authenticationInfo : authenticationInfoList) { + if (authenticationInfo.getRole().equals(this.role)) { + this.permissions = authenticationInfo.getPermissions(); + return authenticationInfo.getUser(); + } + } + throw new IllegalArgumentException("role info not found: " + role); + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/service/UKeyLoginService.java b/src/main/java/com/cisd/tms/modules/mk/service/UKeyLoginService.java new file mode 100644 index 0000000..98f5808 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/mk/service/UKeyLoginService.java @@ -0,0 +1,195 @@ +package com.cisd.tms.modules.mk.service; + +import com.cisd.tms.common.util.IpWhitelistUtil; +import com.cisd.tms.common.util.Sm2SignatureUtil; +import com.cisd.tms.modules.mk.config.UKeyLoginProperties; +import com.cisd.tms.modules.mk.dto.AuthInfo; +import com.cisd.tms.modules.mk.dto.LoginDTO; +import com.cisd.tms.modules.mk.dto.LoginSignDTO; +import com.cisd.tms.modules.mk.dto.UKeySignEntity; +import com.cisd.tms.modules.mk.dto.UKeySignVerifyDTO; +import com.cisd.tms.modules.mk.dto.UserInfo; +import com.cisd.tms.modules.mk.enums.MasterKeyStatus; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Service +public class UKeyLoginService { + + private static final Logger log = LoggerFactory.getLogger(UKeyLoginService.class); + + private final LmkService lmkService; + private final ObjectMapper objectMapper; + private final UKeyLoginProperties loginProperties; + + private volatile List randomList; + + public UKeyLoginService(LmkService lmkService, ObjectMapper objectMapper, UKeyLoginProperties loginProperties) { + this.lmkService = lmkService; + this.objectMapper = objectMapper; + this.loginProperties = loginProperties; + } + + public AuthInfo uKeyLogin(LoginDTO loginDTO, HttpServletRequest request) { + try { + log.info("UKey login payload: {}", objectMapper.writeValueAsString(loginDTO)); + + currentRoleCheck(loginDTO.getRole(), loginDTO.getRids()); + uKeySignCheck(loginDTO.getUKeySignVerifyDTOList()); + + UserInfo userInfo = loginDTO.getUserInfo(); + userInfoCheck(userInfo); + throwIfLmkNotGenerated(); + + randomCheck(loginDTO.getRandom()); + loginSignCheck(loginDTO.getLoginSignDTOList()); + whitelistCheck(request); + checkRolePassword(loginDTO.getRole(), loginDTO.getPassword()); + + AuthInfo authInfo = AuthInfo.getInstance(UUID.randomUUID().toString()); + log.info("UKey login success, role={}", loginDTO.getRole()); + return authInfo; + } catch (JsonProcessingException e) { + throw new IllegalStateException("serialize login payload failed", e); + } catch (RuntimeException e) { + log.error("UKey login failed: {}", e.getMessage(), e); + throw e; + } + } + + private void currentRoleCheck(String role, java.util.Set rids) { + boolean matched; + switch (role) { + case "superadmin" -> matched = rids.size() == 3 + && rids.contains("1") + && rids.contains("2") + && rids.contains("3"); + case "keyadmin" -> matched = rids.size() == 2 + && rids.contains("4") + && rids.contains("5"); + case "auditadmin" -> matched = rids.size() == 1 + && rids.contains("6"); + case "configadmin" -> matched = rids.size() == 1 + && rids.contains("7"); + case "systemadmin" -> matched = rids.size() == 1 + && rids.contains("8"); + default -> matched = false; + } + if (!matched) { + throw new IllegalArgumentException("selected role not matched with UKey rids"); + } + } + + private void uKeySignCheck(List uKeySignVerifyDTOList) { + String iPubKey = lmkService.exportIkPublicKeyHex(); + boolean result = true; + for (UKeySignVerifyDTO uKeySignVerifyDTO : uKeySignVerifyDTOList) { + String payload; + try { + payload = objectMapper.writeValueAsString(UKeySignEntity.getInstance( + uKeySignVerifyDTO.getuKeySignDTO(), iPubKey)); + } catch (JsonProcessingException e) { + throw new IllegalStateException("serialize ukey sign payload failed", e); + } + boolean verified = Sm2SignatureUtil.verifyBase64Signature( + uKeySignVerifyDTO.getuKeySignDTO().getPubKey(), + payload, + uKeySignVerifyDTO.getSign()); + result = result && verified; + } + if (!result) { + throw new IllegalArgumentException("ukey issue sign verify failed"); + } + } + + private void userInfoCheck(UserInfo userInfo) { + List config = loginProperties.getAuthenticationInfoList(); + if (config == null || config.isEmpty()) { + throw new IllegalStateException("ukey authentication config missing"); + } + boolean result = userInfo.roleUserAuth(config); + if (!result) { + throw new IllegalArgumentException("auth info incorrect"); + } + } + + private void throwIfLmkNotGenerated() { + int masterKeyCode = lmkService.getMasterKeyStatus().getCode(); + if (MasterKeyStatus.ABNORMAL.getCode() == masterKeyCode) { + throw new IllegalStateException("master key not generated"); + } + } + + private void randomCheck(List incomingRandoms) { + if (incomingRandoms == null || incomingRandoms.isEmpty()) { + throw new IllegalArgumentException("random number input error"); + } + List currentRandomList = this.randomList; + if (currentRandomList == null || currentRandomList.isEmpty()) { + throw new IllegalStateException("random list not initialized"); + } + for (String r : incomingRandoms) { + if (!currentRandomList.contains(r)) { + throw new IllegalArgumentException("random number input error"); + } + } + } + + private void loginSignCheck(List loginSignDTOList) { + boolean result = true; + for (LoginSignDTO loginSignDTO : loginSignDTOList) { + boolean verified = Sm2SignatureUtil.verifyBase64Signature( + loginSignDTO.getPubKey(), + loginSignDTO.getLoginSignData(), + loginSignDTO.getSignValue()); + result = result && verified; + } + if (!result) { + throw new IllegalArgumentException("login sign verify failed"); + } + } + + private void whitelistCheck(HttpServletRequest request) { + String clientIp = IpWhitelistUtil.getClientIp(request); + List whitelist = IpWhitelistUtil.readWhitelist(resolveWhitelistPath()); + if (!whitelist.isEmpty()) { + if (!IpWhitelistUtil.isIpInWhitelist(clientIp, whitelist)) { + throw new IllegalArgumentException("access denied"); + } + } + } + + private String resolveWhitelistPath() { + String configured = loginProperties.getWhitelistPath(); + if (configured != null && !configured.isBlank()) { + return configured; + } + return System.getProperty("user.dir") + "/config/whitelist.txt"; + } + + private void checkRolePassword(String role, String password) { + Map rolePasswords = loginProperties.getRolePasswords(); + if (rolePasswords == null || rolePasswords.isEmpty()) { + throw new IllegalStateException("role password config missing"); + } + String expected = rolePasswords.get(role); + if (expected == null) { + throw new IllegalArgumentException("role not found: " + role); + } + if (!expected.equals(password)) { + throw new IllegalArgumentException("password check failed"); + } + } + + public void setRandomList(List randomList) { + this.randomList = randomList; + } +}