mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-13 23:11:06 +00:00
fix : 修复登陆失败,事务失效问题
This commit is contained in:
parent
89bc58d29e
commit
54f635031f
4 changed files with 88 additions and 6 deletions
|
|
@ -0,0 +1,56 @@
|
|||
package com.iflytek.skillhub.auth.local;
|
||||
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Handles failed login attempts for local credentials.
|
||||
* @author zhaieryuan
|
||||
*/
|
||||
@Service
|
||||
public class LocalAuthFailedService {
|
||||
|
||||
private static final int MAX_FAILED_ATTEMPTS = 5;
|
||||
private static final Duration LOCK_DURATION = Duration.ofMinutes(15);
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
private final LocalCredentialRepository credentialRepository;
|
||||
|
||||
public LocalAuthFailedService(Clock clock,
|
||||
LocalCredentialRepository credentialRepository
|
||||
){
|
||||
this.clock = clock;
|
||||
this.credentialRepository = credentialRepository;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
|
||||
public void handleFailedLogin(@Nonnull Long credentialId) {
|
||||
|
||||
LocalCredential credential = credentialRepository.findById(credentialId)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Invalid credential id"));
|
||||
|
||||
int failedAttempts = credential.getFailedAttempts() + 1;
|
||||
Instant lockedUntil = credential.getLockedUntil();
|
||||
|
||||
if (failedAttempts >= MAX_FAILED_ATTEMPTS && lockedUntil == null) {
|
||||
lockedUntil = currentTime().plus(LOCK_DURATION);
|
||||
}
|
||||
|
||||
credentialRepository.updateFailedAttemptsAndLockedUntil(credentialId, failedAttempts, lockedUntil);
|
||||
}
|
||||
|
||||
private Instant currentTime() {
|
||||
return Instant.now(clock);
|
||||
}
|
||||
}
|
||||
|
|
@ -45,13 +45,16 @@ public class LocalAuthService {
|
|||
private final PasswordEncoder passwordEncoder;
|
||||
private final Clock clock;
|
||||
|
||||
private final LocalAuthFailedService localAuthFailedService;
|
||||
|
||||
public LocalAuthService(LocalCredentialRepository credentialRepository,
|
||||
UserAccountRepository userAccountRepository,
|
||||
UserRoleBindingRepository userRoleBindingRepository,
|
||||
GlobalNamespaceMembershipService globalNamespaceMembershipService,
|
||||
PasswordPolicyValidator passwordPolicyValidator,
|
||||
PasswordEncoder passwordEncoder,
|
||||
Clock clock) {
|
||||
Clock clock,
|
||||
LocalAuthFailedService localAuthFailedService) {
|
||||
this.credentialRepository = credentialRepository;
|
||||
this.userAccountRepository = userAccountRepository;
|
||||
this.userRoleBindingRepository = userRoleBindingRepository;
|
||||
|
|
@ -59,6 +62,7 @@ public class LocalAuthService {
|
|||
this.passwordPolicyValidator = passwordPolicyValidator;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.clock = clock;
|
||||
this.localAuthFailedService = localAuthFailedService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -126,7 +130,7 @@ public class LocalAuthService {
|
|||
ensureNotLocked(credential);
|
||||
|
||||
if (!passwordEncoder.matches(password, credential.getPasswordHash())) {
|
||||
handleFailedLogin(credential);
|
||||
localAuthFailedService.handleFailedLogin(credential.getId());
|
||||
throw invalidCredentials();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.iflytek.skillhub.auth.local;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
|
|
@ -15,4 +18,20 @@ public interface LocalCredentialRepository extends JpaRepository<LocalCredential
|
|||
Optional<LocalCredential> findByUserId(String userId);
|
||||
|
||||
boolean existsByUsernameIgnoreCase(String username);
|
||||
|
||||
/**
|
||||
* 更新本地凭证的失败尝试次数和锁定截止时间。
|
||||
*
|
||||
* @param id 凭证ID
|
||||
* @param failedAttempts 失败尝试次数
|
||||
* @param lockedUntil 锁定截止时间,如果为null表示不锁定
|
||||
*/
|
||||
@Modifying
|
||||
@Query("UPDATE LocalCredential c SET c.failedAttempts = :failedAttempts, c.lockedUntil = :lockedUntil WHERE c.id = :id")
|
||||
void updateFailedAttemptsAndLockedUntil(
|
||||
Long id,
|
||||
int failedAttempts,
|
||||
Instant lockedUntil
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ class LocalAuthServiceTest {
|
|||
@Mock
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Mock
|
||||
private LocalAuthFailedService localAuthFailedService;
|
||||
|
||||
private LocalAuthService service;
|
||||
|
||||
@BeforeEach
|
||||
|
|
@ -62,7 +65,8 @@ class LocalAuthServiceTest {
|
|||
globalNamespaceMembershipService,
|
||||
new PasswordPolicyValidator(),
|
||||
passwordEncoder,
|
||||
CLOCK
|
||||
CLOCK,
|
||||
localAuthFailedService
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -122,8 +126,7 @@ class LocalAuthServiceTest {
|
|||
.extracting("status")
|
||||
.isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
|
||||
assertThat(credential.getFailedAttempts()).isEqualTo(1);
|
||||
verify(credentialRepository).save(credential);
|
||||
verify(localAuthFailedService).handleFailedLogin(credential.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -141,7 +144,7 @@ class LocalAuthServiceTest {
|
|||
.extracting("status")
|
||||
.isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
|
||||
assertThat(credential.getLockedUntil()).isEqualTo(Instant.now(CLOCK).plusSeconds(15 * 60));
|
||||
verify(localAuthFailedService).handleFailedLogin(credential.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue