LoginAttemptServiceImpl.java

package org.darkroomlibrary.service.impl;

import org.darkroomlibrary.infrastructure.security.ClientIpResolver;
import org.darkroomlibrary.infrastructure.security.LoginAttemptStore;
import org.darkroomlibrary.service.LoginAttemptService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;
import java.util.Locale;

/**
 * 登录防暴力破解服务实现(内存存储)
 */
@Service
public class LoginAttemptServiceImpl implements LoginAttemptService {

    @Value("${security.login.max-fail-attempts:5}")
    private int maxFailAttempts;

    @Value("${security.login.lock-duration-minutes:30}")
    private int lockDurationMinutes;

    @Resource
    private LoginAttemptStore loginAttemptStore;

    @Resource
    private ClientIpResolver clientIpResolver;

    @Override
    public void loginFailed(String account) {
        loginAttemptStore.loginFailed(subject(account), maxFailAttempts, lockDurationMinutes);
    }

    @Override
    public void loginSucceeded(String account) {
        loginAttemptStore.loginSucceeded(subject(account));
    }

    @Override
    public boolean isBlocked(String account) {
        return loginAttemptStore.isBlocked(subject(account));
    }

    @Override
    public long getRemainingLockSeconds(String account) {
        return loginAttemptStore.getRemainingLockSeconds(subject(account));
    }

    @Override
    @Scheduled(fixedRate = 60000)
    public void clearExpired() {
        loginAttemptStore.clearExpired();
    }

    private String subject(String account) {
        String normalizedAccount = account == null
                ? ""
                : account.trim().toLowerCase(Locale.ROOT);
        return normalizedAccount + "|" + clientIpResolver.resolveCurrentRequest();
    }
}