6 changed files with 188 additions and 4 deletions
@ -0,0 +1,159 @@ |
|||
package com.zdxt.auth.project.system.user.controller; |
|||
|
|||
import com.google.gson.Gson; |
|||
import com.google.gson.JsonObject; |
|||
import com.zdxt.auth.framework.shiro.nopaddword.LoginType; |
|||
import com.zdxt.auth.framework.shiro.nopaddword.UserToken; |
|||
import com.zdxt.auth.framework.shiro.nopaddword.UserType; |
|||
import com.zdxt.auth.framework.web.controller.BaseController; |
|||
import com.zdxt.auth.project.system.user.service.IUserService; |
|||
import com.zdxt.domain.auth.User; |
|||
import org.apache.shiro.SecurityUtils; |
|||
import org.apache.shiro.authc.AuthenticationException; |
|||
import org.apache.shiro.subject.Subject; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
|
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.web.server.ResponseStatusException; |
|||
|
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.spec.SecretKeySpec; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.time.LocalDateTime; |
|||
import java.time.format.DateTimeFormatter; |
|||
import java.util.Base64; |
|||
|
|||
/** |
|||
* 通过用户ID登录 |
|||
*/ |
|||
@Controller |
|||
public class LoginByUserIdController extends BaseController { |
|||
|
|||
private static final Logger log = LoggerFactory.getLogger(LoginByUserIdController.class); |
|||
|
|||
/** |
|||
* AES解密密钥(与新系统 EncryptUtils.encryptByAes 加密密钥一致) |
|||
*/ |
|||
private static final String AES_KEY = "zdxt2026PlatForm"; |
|||
|
|||
/** |
|||
* 时间戳允许的最大偏移量(秒) |
|||
*/ |
|||
private static final long ALLOWED_TIME_OFFSET_SECONDS = 20; |
|||
|
|||
/** |
|||
* 允许登录的开始日期时间(格式 yyyy-MM-dd HH:mm) |
|||
*/ |
|||
private static final String ALLOWED_START_TIME = "2026-06-18 08:00"; |
|||
|
|||
/** |
|||
* 允许登录的结束日期时间(格式 yyyy-MM-dd HH:mm) |
|||
*/ |
|||
private static final String ALLOWED_END_TIME = "2026-08-01 22:00"; |
|||
|
|||
@Autowired |
|||
private IUserService userService; |
|||
|
|||
/** |
|||
* 通过v1_system_credential免密登录,解密凭据后验证时间戳偏移并使用userId登录 |
|||
* |
|||
* @param v1SystemCredential AES加密的凭据(包含userid和ts) |
|||
* @return 跳转页面 |
|||
*/ |
|||
@GetMapping("/loginByUserId") |
|||
public String loginByUserId(@RequestParam("v1_system_credential") String v1SystemCredential) { |
|||
// 0. 验证当前时间是否在允许的日期时间范围内
|
|||
if (!isWithinAllowedTime()) { |
|||
log.warn("通过userId登录失败, 当前时间不在允许的时间范围内[{} - {}]", |
|||
ALLOWED_START_TIME, ALLOWED_END_TIME); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 1. AES解密凭据
|
|||
String decryptedJson = decryptByAes(v1SystemCredential); |
|||
if (decryptedJson == null) { |
|||
log.warn("通过userId登录失败, v1_system_credential解密失败"); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 2. 解析JSON获取userid和ts
|
|||
Long userId; |
|||
long ts; |
|||
try { |
|||
Gson gson = new Gson(); |
|||
JsonObject jsonObject = gson.fromJson(decryptedJson, JsonObject.class); |
|||
userId = Long.parseLong(jsonObject.get("userid").getAsString()); |
|||
ts = Long.parseLong(jsonObject.get("ts").getAsString()); |
|||
} catch (Exception e) { |
|||
log.warn("通过userId登录失败, 凭据JSON解析异常: {}", e.getMessage()); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 3. 验证时间戳是否与当前系统时间偏移在±20秒内
|
|||
long currentTimeMillis = System.currentTimeMillis(); |
|||
long diffSeconds = Math.abs(currentTimeMillis - ts) / 1000; |
|||
if (diffSeconds > ALLOWED_TIME_OFFSET_SECONDS) { |
|||
log.warn("通过userId登录失败, 时间戳偏移超过{}秒, ts: {}, 当前时间: {}, 偏移: {}秒", |
|||
ALLOWED_TIME_OFFSET_SECONDS, ts, currentTimeMillis, diffSeconds); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 4. 根据userId查询用户
|
|||
User sysUser = userService.selectUserById(userId); |
|||
if (sysUser == null) { |
|||
log.warn("通过userId登录失败, 用户不存在, userId: {}", userId); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
|
|||
// 5. 使用免密登录方式进行Shiro认证
|
|||
UserToken token = new UserToken(sysUser.getLoginName(), LoginType.NOPASSWD, UserType.SYS.getType()); |
|||
Subject subject = SecurityUtils.getSubject(); |
|||
try { |
|||
subject.login(token); |
|||
log.info("通过v1_system_credential登录成功, userId: {}", userId); |
|||
return "redirect:/code/statisticalPanel/home-analysis"; |
|||
} catch (AuthenticationException e) { |
|||
log.error("通过userId登录认证失败, userId: {}, 原因: {}", userId, e.getMessage()); |
|||
throw new ResponseStatusException(HttpStatus.NOT_FOUND); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断当前时间是否在允许的日期时间范围内 |
|||
* |
|||
* @return true-在允许时间范围内, false-不在允许时间范围内 |
|||
*/ |
|||
private boolean isWithinAllowedTime() { |
|||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); |
|||
LocalDateTime startTime = LocalDateTime.parse(ALLOWED_START_TIME, formatter); |
|||
LocalDateTime endTime = LocalDateTime.parse(ALLOWED_END_TIME, formatter); |
|||
LocalDateTime now = LocalDateTime.now(); |
|||
return !now.isBefore(startTime) && !now.isAfter(endTime); |
|||
} |
|||
|
|||
/** |
|||
* AES/ECB/PKCS5Padding 解密(对应新系统 EncryptUtils.encryptByAes 的加密方式) |
|||
* |
|||
* @param encryptedText Base64编码的AES密文 |
|||
* @return 解密后的明文字符串,解密失败返回null |
|||
*/ |
|||
private String decryptByAes(String encryptedText) { |
|||
try { |
|||
byte[] keyBytes = AES_KEY.getBytes(StandardCharsets.UTF_8); |
|||
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); |
|||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); |
|||
cipher.init(Cipher.DECRYPT_MODE, keySpec); |
|||
byte[] decodeBytes = Base64.getDecoder().decode(encryptedText); |
|||
byte[] decrypted = cipher.doFinal(decodeBytes); |
|||
return new String(decrypted, StandardCharsets.UTF_8); |
|||
} catch (Exception e) { |
|||
log.error("AES解密失败: {}", e.getMessage()); |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue