86 lines
3.5 KiB
Java
86 lines
3.5 KiB
Java
package com.zioinfo.mes.auth;
|
|
|
|
import com.zioinfo.mes.auth.mapper.UserMapper;
|
|
import com.zioinfo.mes.uiws.auth.TwoFactorService;
|
|
import com.zioinfo.mes.uiws.common.UiwsApiException;
|
|
import com.zioinfo.mes.uiws.common.UiwsErrorCode;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* MES 인증 서비스.
|
|
* - 기존 단일 JWT 로그인 보존(2FA off 또는 미설정 시 회귀 0).
|
|
* - UIWS 2FA 레이어(이메일 코드) 추가: 2FA on 이면 1차 통과 후 verify-token + 이메일코드 발급.
|
|
* 실패 누적 max-login-fail 회 시 계정 잠금.
|
|
*/
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class AuthService {
|
|
|
|
private final UserMapper userMapper;
|
|
private final PasswordEncoder passwordEncoder;
|
|
private final JwtUtil jwtUtil;
|
|
private final TwoFactorService twoFactorService;
|
|
|
|
/**
|
|
* 1차 로그인. 2FA 활성 시 verify-token + 이메일코드 흐름으로 분기,
|
|
* 비활성 시 기존처럼 access 토큰 즉시 발급.
|
|
*
|
|
* @return 2FA off: { token, type, twofa:"false" }
|
|
* 2FA on : { verifyToken, step:"EMAIL", maskedEmail, twofa:"true" }
|
|
*/
|
|
public Map<String, String> login(String username, String password) {
|
|
MesUser user = userMapper.findByUsername(username);
|
|
|
|
// 잠금 우선 차단(존재하는 계정에 한해 잠금 메시지 — 존재 여부 누설 최소화)
|
|
if (user != null && twoFactorService.isLocked(user)) {
|
|
throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED);
|
|
}
|
|
if (user == null || !user.isActive()) {
|
|
throw new RuntimeException("ERR-AUTH-001: 존재하지 않거나 비활성 계정");
|
|
}
|
|
if (!passwordEncoder.matches(password, user.getPasswordHash())) {
|
|
// 2FA 활성 시 실패 누적/잠금. 비활성 시 기존 동작(메시지만) 유지.
|
|
if (twoFactorService.isEnabled()) {
|
|
twoFactorService.recordLoginFailure(username);
|
|
MesUser after = userMapper.findByUsername(username);
|
|
if (after != null && Boolean.TRUE.equals(after.getLocked())) {
|
|
throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED);
|
|
}
|
|
}
|
|
throw new RuntimeException("ERR-AUTH-002: 비밀번호 불일치");
|
|
}
|
|
|
|
// 비밀번호 검증 통과
|
|
if (twoFactorService.isEnabled()) {
|
|
Map<String, String> step1 = twoFactorService.beginTwoFactor(user);
|
|
// verifyToken/step/maskedEmail + twofa 플래그
|
|
return Map.of(
|
|
"twofa", "true",
|
|
"verifyToken", step1.get("verifyToken"),
|
|
"step", step1.get("step"),
|
|
"maskedEmail", step1.getOrDefault("maskedEmail", ""));
|
|
}
|
|
|
|
// 2FA 비활성 — 기존 단일 로그인 흐름(회귀 0)
|
|
userMapper.resetLoginFail(username);
|
|
String token = jwtUtil.generate(username, user.getRole());
|
|
return Map.of("twofa", "false", "token", token, "type", "Bearer");
|
|
}
|
|
|
|
public Map<String, Object> me(String token) {
|
|
String username = jwtUtil.getUsername(token);
|
|
String role = jwtUtil.getRole(token);
|
|
MesUser u = userMapper.findByUsername(username);
|
|
Map<String, Object> m = new HashMap<>();
|
|
m.put("username", username);
|
|
m.put("role", role);
|
|
m.put("displayName", u != null ? u.getDisplayName() : username);
|
|
return m;
|
|
}
|
|
}
|