diff --git a/src/backend/src/main/java/com/zioinfo/kintex/auth/AuthServiceImpl.java b/src/backend/src/main/java/com/zioinfo/kintex/auth/AuthServiceImpl.java index 5acc3e2..84fbe77 100644 --- a/src/backend/src/main/java/com/zioinfo/kintex/auth/AuthServiceImpl.java +++ b/src/backend/src/main/java/com/zioinfo/kintex/auth/AuthServiceImpl.java @@ -7,7 +7,8 @@ import com.zioinfo.kintex.auth.dto.WorkspaceDto; import com.zioinfo.kintex.auth.mapper.UserMapper; import com.zioinfo.kintex.common.error.ApiException; import com.zioinfo.kintex.common.error.ErrorCode; -import org.springframework.security.crypto.password.PasswordEncoder; +import com.zioinfo.kintex.security.TwoFactorService; +import com.zioinfo.kintex.security.dto.SecureLoginResponse; import org.springframework.stereotype.Service; import java.util.LinkedHashMap; @@ -17,36 +18,34 @@ import java.util.Map; /** * 인증 서비스 — UserMapper(인증행·행사 역할) 배선 완료. 로그인·워크스페이스·초대 수락을 처리한다. * - *

보안(계약 §0-3): 비밀번호는 BCrypt 해시와 대조({@link PasswordEncoder})하며, 해시·내부 식별자는 응답에 미노출. + *

보안(계약 §0-3): 비밀번호 대조는 위임된 secureLogin(BCrypt)이 수행하며, 해시·내부 식별자는 응답에 미노출. * 실패 메시지는 이메일/비밀번호를 구분하지 않는 일반화 문구만 반환한다. * - *

2차 인증(OTP/TOTP)·로그인 실패 잠금은 common-dev(B-1) 레인이 별도로 얹으며 본 서비스는 건드리지 않는다. + *

레거시 /api/auth/login 도 {@link TwoFactorService#secureLogin}에 위임한다 — 위임하지 않으면 + * 이 경로가 로그인 실패 잠금·OTP 강제를 모두 우회하는 구멍이 된다. OTP 대상 계정은 토큰 없이 + * OTP_REQUIRED 로 거절되며(챌린지 토큰 미반환), 보안 로그인(/login/secure) 플로우만 허용된다. */ @Service public class AuthServiceImpl implements AuthService { private final JwtService jwtService; private final UserMapper userMapper; - private final PasswordEncoder passwordEncoder; + private final TwoFactorService twoFactorService; - public AuthServiceImpl(JwtService jwtService, UserMapper userMapper, PasswordEncoder passwordEncoder) { + public AuthServiceImpl(JwtService jwtService, UserMapper userMapper, TwoFactorService twoFactorService) { this.jwtService = jwtService; this.userMapper = userMapper; - this.passwordEncoder = passwordEncoder; + this.twoFactorService = twoFactorService; } @Override public LoginResponse login(LoginRequest request) { - Map row = userMapper.findAuthByEmail(request.email()); - String hash = row == null ? null : str(row.get("passwordHash")); - if (row == null || hash == null || !passwordEncoder.matches(request.password(), hash)) { - // 이메일/비밀번호 구분 없이 일반화(계정 열거 방지). - throw new ApiException(ErrorCode.UNAUTHORIZED, "이메일 또는 비밀번호가 올바르지 않습니다."); + SecureLoginResponse secured = twoFactorService.secureLogin(request.email(), request.password()); + if (secured.login() == null) { + throw new ApiException(ErrorCode.OTP_REQUIRED, + "2차 인증이 필요한 계정입니다. 보안 로그인 절차를 이용해 주세요."); } - String userId = str(row.get("userId")); - String displayName = str(row.get("displayName")); - boolean hallManager = Boolean.TRUE.equals(bool(row.get("hallManager"))); - return buildLoginResponse(userId, displayName, hallManager); + return secured.login(); } @Override