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