Lua是一种轻量级的脚本语言,设计之初是为了嵌入应用程序中。它具有简单易懂的语法和较高的执行效率,因此常用于游戏开发、配置脚本、数据处理等领域。Lua脚本通常被集成到其他程序中,实现特定功能或逻辑。
Lua 是一个小巧的脚本语言,是巴西里约热内卢天主教大学里的一个研究小组于1993年开发的。
Lua 使用标准 C 语言编写并以源代码形式开放,几乎在所有操作系统和平台上都能编译运行。Lua 脚本可以调用 C/C++ 的函数,也可以被 C/C++ 代码调用,所以 Lua 在应用程序中可以被广泛应用。
Lua 并没有提供强大的库,这是由它的定位决定的。所以 Lua 不适合作为开发独立应用程序的语言。其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。
Lua 体积小、启动速度快,一个完整的 Lua 解释器不过200k,在所有脚本引擎中,Lua 的速可以于说是最快的。所以 Lua 是作为嵌入式脚本的最佳选择。这也就是我们为什么要学习 Lua 这门语言。
那 Lua 语言能干吗呢?其实它主要是用作脚本语言,用来开发脚本,例如编写游戏辅助脚本,在 Redis 中使用 Lua 脚本等。
Lua 官网地址:www.lua.org/
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
-
视频教程:https://doc.iocoder.cn/video/
轻量级:
Lua 使用标准 C 语言编写,Lua 语言的官方版本只包括一个精简的核心和最基本的库,体积小、启动速度快,一个完整的 Lua 解释器不过200k,适合嵌入在别的程序里。
可扩展:
Lua 提供了非常易于使用的扩展接口和机制,由宿主语言(通常是 C 或 C++ )提供这些功能,Lua 可以使用它们,就像是本来就内置的功能一样。
其它特性:
-
支持面向过程( procedure-oriented )编程和函数式编程( functional programming )。
-
-
只提供了一种通用类型的表(table),但可以用它实现数组,哈希表,集合,对象。
-
闭包( closure ),通过闭包和表可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
-
提供多线程(协同进程,并非操作系统所支持的线程)支持。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/yudao-cloud
-
视频教程:https://doc.iocoder.cn/video/
-
-
-
数据库插件,例如 MySQL Proxy 和 MySQL WorkBench。
-
Lua 的语法简单直观,以下是一些常用的语法:
变量:
使用local关键字声明局部变量,例如:
local a = 10
条件语句:
使用if、then、elseif、else、end实现条件判断,例如:
if x > 0 then
print("x is positive")
elseif x == 0 then
print("x is zero")
else
print("x is negative")
end
循环:
支持for和while循环,例如:
for i = 1, 10 do
print(i)
end
函数:
使用function定义函数,例如:
function add(a, b)
return a + b
end
Redis 是一个高性能的键值数据库,支持 Lua 脚本以实现原子操作。以下是通过 Redis 和 Lua 脚本限制同一 IP 多次输入错误密码的实现。
假设我们要限制同一 IP 在短时间内(例如 10 分钟)输入错误密码不超过 5 次,否则将锁定该 IP 一段时间。
-- 限制IP多次输入错误密码的Lua脚本
local ip = KEYS[1]
local current_time = tonumber(ARGV[1])
local expire_time = 600 -- 10分钟
local max_attempts = 5
local lock_duration = 3600 -- 锁定时间1小时
-- 获取当前错误计数和上次错误时间
local attempts = tonumber(redis.call('get', ip) or '0')
local lock_time = tonumber(redis.call('get', ip .. ':lock') or '0')
-- 检查是否已被锁定
if current_time < lock_time then
return -1 -- -1表示IP已被锁定
end
-- 更新错误计数
attempts = attempts + 1
if attempts >= max_attempts then
-- 超过最大尝试次数,进行锁定
redis.call('set', ip .. ':lock', current_time + lock_duration)
return -1
else
-- 未达到最大次数,更新错误计数
redis.call('set', ip, attempts)
redis.call('expire', ip, expire_time)
return attempts
end
当然,以下是上述 Lua 脚本中每个参数和变量的解释:
这是传递给 Lua 脚本的第一个键参数,表示需要限制的 IP 地址。通过这个参数,Redis 可以针对特定 IP 进行操作。
这是传递给 Lua 脚本的第一个参数,表示当前的 Unix 时间戳(以秒为单位)。这个时间戳用于检查 IP 是否已经被锁定以及更新锁定时间。
表示错误尝试计数的有效期,这里设定为 600 秒(10 分钟)。在此时间内,如果错误尝试次数没有达到最大限制,计数会自动失效。
最大允许的错误尝试次数。这里设置为 5 次,意味着在 10 分钟内,如果某个 IP 输入错误密码的次数达到 5 次,则会触发锁定机制。
锁定时长,单位为秒。这里设置为 3600 秒(1 小时)。如果 IP 被锁定,它将在 1 小时内无法进行任何新的尝试。
当前错误尝试计数,从 Redis 中获取。使用
redis.call('get', ip)
来获取指定 IP 的错误次数,如果不存在则默认为 0。
IP 的锁定截止时间,从 Redis 中获取。使用
redis.call('get', ip .. ':lock')
获取当前 IP 的锁定时间戳,如果不存在则默认为 0。
-
首先,从 Redis 中获取当前 IP 的错误尝试次数和锁定时间。
-
检查当前时间是否小于锁定时间,若是,说明 IP 已被锁定,返回 -1。
-
-
检查尝试次数是否达到或超过最大次数 (max_attempts)。
-
如果达到或超过,则锁定 IP,设置锁定时间,并返回 -1。
-
如果未达到,则更新尝试次数,并设置尝试次数的过期时间为 expire_time。
可以使用 Redis 提供的 EVAL 命令执行上述 Lua 脚本。以下是一个示例:
redis-cli --eval limit_login_attempts.lua 192.168.1.1 , 1620000000
在这里,
192.168.1.1
是 IP 地址,
1620000000
是当前的 Unix 时间戳。
通过这种方式,可以有效限制一个 IP 在短时间内多次输入错误密码,防止暴力破解攻击。这种机制可以集成到登录系统中,以增强安全性。
为了更具体地结合项目应用场景,我们可以考虑一个示例场景:
在一个用户登录系统中,需要限制某个 IP 地址在短时间内多次输入错误密码以防止暴力攻击。
以下是一个完整的 Java 应用示例,演示如何使用 Redis 和 Lua 脚本来实现这一功能。
在用户登录系统中,我们希望限制某个 IP 地址在 10 分钟内最多输入错误密码 5 次。如果超过这个次数,则在接下来的 1 小时内禁止该 IP 的登录尝试。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class LoginAttemptLimiter {
private static final String LUA_SCRIPT =
"local ip = KEYS[1] " +
"local current_time = tonumber(ARGV[1]) " +
"local expire_time = 600 " + // 10 minutes in seconds
"local max_attempts = 5 " +
"local lock_duration = 3600 " + // 1 hour in seconds
"local attempts = tonumber(redis.call('get', ip) or '0') " +
"local lock_time = tonumber(redis.call('get', ip .. ':lock') or '0') " +
"if current_time < lock_time then " +
" return -1 " + // IP is locked
"end " +
"attempts = attempts + 1 " +
"if attempts >= max_attempts then " +
" redis.call('set', ip .. ':lock', current_time + lock_duration) " +
" return -1 " + // Lock the IP
"else " +
" redis.call('set', ip, attempts) " +
" redis.call('expire', ip, expire_time) " +
" return attempts " + // Return current attempt count
"end";
private final JedisPool jedisPool;
public LoginAttemptLimiter(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public int checkLoginAttempts(String ip) {
try (Jedis jedis = jedisPool.getResource()) {
long currentTime = System.currentTimeMillis() / 1000