-
Notifications
You must be signed in to change notification settings - Fork 2
[Feat] 계정 잠금 및 비밀번호 설정 규칙 #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,16 @@ | |
| import TtattaBackend.ttatta.repository.UserRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.authentication.BadCredentialsException; | ||
| import org.springframework.security.authentication.LockedException; | ||
| import org.springframework.security.core.userdetails.User; | ||
| import org.springframework.security.core.userdetails.UserDetails; | ||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||
| import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.time.Duration; | ||
| import java.time.LocalDateTime; | ||
| import java.util.Collections; | ||
|
|
||
| @Service | ||
|
|
@@ -20,13 +23,54 @@ public class CustomUserDetailsService implements CustomDetailsService { | |
| private final PasswordEncoder passwordEncoder; | ||
| private final UserRepository userRepository; | ||
|
|
||
| private static final int MAX_ATTEMPTS = 5; | ||
| private static final Duration LOCK_DURATION = Duration.ofHours(12); | ||
|
|
||
| @Override | ||
| public UserDetails loadUserByUsername(String username, String password) throws UsernameNotFoundException { | ||
| Users user = userRepository.findByUsername(username) | ||
| .orElseThrow(() -> new UsernameNotFoundException("해당 아이디를 가진 유저가 존재하지 않습니다: " + username)); | ||
|
|
||
| if (user.isLockedNow()) { | ||
| Duration remain = Duration.between(LocalDateTime.now(), user.getLockUntil()); | ||
|
|
||
| long remainHours = remain.toHours(); | ||
| long remainMinutes = remain.toMinutes(); | ||
|
|
||
| String message; | ||
| if (remainHours >= 1) { | ||
| message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + (remainHours + 1) + "시간 후에 다시 시도해주세요."; | ||
| } else { | ||
| long remainMin = Math.max(1, remainMinutes); | ||
| message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + remainMin + "분 후에 다시 시도해주세요."; | ||
| } | ||
|
|
||
| throw new LockedException(message); | ||
| } else if (user.getLockUntil() != null) { | ||
| user.resetLock(); | ||
| userRepository.save(user); | ||
| } | ||
|
|
||
| if (!passwordEncoder.matches(password, user.getPassword())) { | ||
| throw new BadCredentialsException("Password가 일치하지 않습니다."); | ||
| Users refreshed = userRepository.findByUsername(username).orElse(user); | ||
| int attempts = refreshed.getFailedAttempts(); | ||
| int currentAttempts = attempts + 1; | ||
|
|
||
| if (currentAttempts >= MAX_ATTEMPTS) { | ||
| refreshed.lockFor(LOCK_DURATION); | ||
| userRepository.save(refreshed); | ||
| throw new LockedException("비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다."); | ||
| } | ||
|
|
||
| user.updateFailedAttempts(currentAttempts); | ||
| userRepository.save(user); | ||
|
|
||
| throw new BadCredentialsException("아이디 또는 비밀번호가 올바르지 않습니다. (남은 시도: " + (MAX_ATTEMPTS - currentAttempts) + "회)"); | ||
| } | ||
|
Comment on lines
54
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로그인 실패 시 사용자 정보를 업데이트하는 로직에 동시성 문제가 발생할 수 있으며, 코드가 복잡합니다. if (!passwordEncoder.matches(password, user.getPassword())) {
// 동시 로그인 시도에 대한 경쟁 상태를 방지하기 위해 사용자 정보를 다시 가져옵니다.
Users userToUpdate = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("해당 아이디를 가진 유저가 존재하지 않습니다: " + username));
int newAttempts = userToUpdate.getFailedAttempts() + 1;
userToUpdate.updateFailedAttempts(newAttempts);
if (newAttempts >= MAX_ATTEMPTS) {
userToUpdate.lockFor(LOCK_DURATION);
}
userRepository.save(userToUpdate);
if (newAttempts >= MAX_ATTEMPTS) {
throw new LockedException("비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다.");
} else {
throw new BadCredentialsException("아이디 또는 비밀번호가 올바르지 않습니다. (남은 시도: " + (MAX_ATTEMPTS - newAttempts) + "회)");
}
} |
||
|
|
||
| if (user.getFailedAttempts() != 0 || user.getLockUntil() != null) { | ||
| user.resetLock(); | ||
| userRepository.save(user); | ||
| } | ||
|
|
||
| User securityUser = new User( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
계정 잠금 시 남은 시간을 안내하는 메시지가 사용자에게 혼란을 줄 수 있습니다. 현재 로직은 남은 시간을 시간 단위로 올림하여 보여주기 때문에, 예를 들어 1시간 1분이 남았을 때와 1시간 59분이 남았을 때 모두 "약 2시간"으로 표시됩니다. 사용자에게 더 정확한 정보를 제공하기 위해 시간과 분을 함께 표시하는 것을 고려해보세요.