Implementing Login Attempt Limiting with Spring Boot, Redis, and Lua Scripts
This article demonstrates how to implement a login attempt limiting mechanism using Spring Boot, Redis, and Lua scripts, explaining the problem of repeated password failures, the design of IP-based rate limiting, and providing complete front‑end and back‑end code examples with configuration details.
Background: Users often encounter password lockout after multiple failed attempts, and resetting passwords cannot reuse the old password.
The author asks three questions about lockout behavior and decides to solve them using SpringBoot, Redis, and Lua scripts.
Answers: lockout is based on IP, Redis Lua script handles key expiration, and the technology stack is SpringBoot+Redis+Lua.
Front‑end implementation
A simple HTML login page with a form is provided.
<!DOCTYPE html>
<html>
<head>
<title>登录页面</title>
<style>
body {background-color:#F5F5F5;}
form {width:300px;margin:0 auto;margin-top:100px;padding:20px;background-color:white;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,0.2);}
label {display:block;margin-bottom:10px;}
input[type="text"], input[type="password"] {border:none;padding:10px;margin-bottom:20px;border-radius:5px;box-shadow:0 0 5px rgba(0,0,0,0.1);width:100%;box-sizing:border-box;font-size:16px;}
input[type="submit"] {background-color:#30B0F0;color:white;border:none;padding:10px;border-radius:5px;box-shadow:0 0 5px rgba(0,0,0,0.1);width:100%;font-size:16px;cursor:pointer;}
input[type="submit"]:hover {background-color:#1C90D6;}
</style>
</head>
<body>
<form action="http://localhost:8080/login" method="get">
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
<label for="password">密码</label>
<input type="password" id="password" name="password" placeholder="请输入密码" required>
<input type="submit" value="登录">
</form>
</body>
</html>Back‑end analysis
A flow diagram shows that request counting occurs before login logic, is independent of success, and uses key expiration for reset.
Redis is chosen for distributed atomic increment, and Lua scripts reduce network overhead and ensure atomicity.
Lua rate‑limit script
local c
c = redis.call('get',KEYS[1])
if c and tonumber(c) > tonumber(ARGV[1]) then
return c;
end
c = redis.call('incr',KEYS[1])
if tonumber(c) == 1 then
redis.call('expire',KEYS[1],ARGV[2])
end
return c;Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitCount {
String name() default "";
String key() default "";
String prefix() default "";
int period() default 60;
int count() default 3;
}Aspect implementation
public Object around(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(
RequestContextHolder.getRequestAttributes())).getRequest();
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
LimitCount annotation = method.getAnnotation(LimitCount.class);
String name = annotation.name();
String key = annotation.key();
String ip = IPUtil.getIpAddr(request);
int limitPeriod = annotation.period();
int limitCount = annotation.count();
ImmutableList
keys = ImmutableList.of(StringUtils.join(annotation.prefix(), "_", key, ip));
String luaScript = buildLuaScript();
RedisScript
redisScript = new DefaultRedisScript<>(luaScript, Number.class);
Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
log.info("IP:{} 第 {} 次访问key为 {},描述为 [{}] 的接口", ip, count, keys, name);
if (count != null && count.intValue() <= limitCount) {
return point.proceed();
} else {
return "接口访问超出频率限制";
}
}Utility class for IP extraction
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}Redis configuration
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}") private String host;
@Value("${spring.redis.port}") private int port;
@Value("${spring.redis.password}") private String password;
@Value("${spring.redis.timeout}") private int timeout;
@Value("${spring.redis.jedis.pool.max-idle}") private int maxIdle;
@Value("${spring.redis.jedis.pool.max-wait}") private long maxWaitMillis;
@Value("${spring.redis.database:0}") private int database;
@Bean public JedisPool redisPoolFactory() { ... }
@Bean public JedisConnectionFactory jedisConnectionFactory() { ... }
@Bean(name = "redisTemplate") @ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
redisTemplate(RedisConnectionFactory cf) { ... }
@Bean public CacheManager cacheManager(RedisConnectionFactory cf) { ... }
@Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory cf) { ... }
@Bean public KeyGenerator wiselyKeyGenerator() { ... }
@Bean public RedisTemplate
limitRedisTemplate(RedisConnectionFactory cf) { ... }
}Controller
@RestController
public class LoginController {
@GetMapping("/login")
@LimitCount(key = "login", name = "登录接口", prefix = "limit")
public String login(@RequestParam String username,
@RequestParam String password,
HttpServletRequest request) throws Exception {
if (StringUtils.equals("张三", username) && StringUtils.equals("123456", password)) {
return "登录成功";
}
return "账户名或密码错误";
}
}The application entry point is a standard SpringBoot main class.
@SpringBootApplication
public class LoginLimitApplication {
public static void main(String[] args) {
SpringApplication.run(LoginLimitApplication.class, args);
}
}A demonstration shows the login page, successful attempts, and the lockout message after exceeding the allowed number of tries.
While many modern systems use captchas after repeated failures, the presented IP‑based rate limiting can be adapted to suit various project requirements.
Scan the QR code to receive a free book on program management systems.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.