别再硬抗了!Redis + Lua 轻松搞定分布式令牌黑名单机制,拒绝恶意访问!
在互联网应用中,为了防止恶意访问,保障系统安全,我们经常需要实现一个黑名单机制。 而在分布式环境下,如何高效、可靠地实现黑名单机制就成了一个值得探讨的问题。 本文将结合 Redis 和 Lua 脚本,详细讲解如何设计并实现一个高效的分布式令牌黑名单机制。
1. 为什么选择 Redis + Lua?
Redis 的优势:
- 高性能: Redis 是一个基于内存的 NoSQL 数据库,读写速度极快,非常适合处理高并发场景。
- 原子性: Redis 的命令是原子性的,保证了在并发情况下的数据一致性,这对于黑名单机制至关重要。
- 丰富的数据结构: Redis 提供了丰富的数据结构,如字符串、列表、集合等,方便我们存储和管理黑名单数据。
Lua 脚本的优势:
- 原子性: Lua 脚本在 Redis 中执行是原子性的,可以保证一系列操作的完整性和一致性。
- 灵活性: Lua 脚本允许我们在 Redis 中执行复杂的逻辑操作,例如判断令牌是否存在、添加令牌到黑名单等。
- 减少网络开销: 使用 Lua 脚本可以将多个操作打包成一个脚本在服务器端执行,减少了客户端和服务器之间的网络交互。
2. 核心设计思路
我们的目标是设计一个基于令牌的黑名单机制,当一个令牌被判定为恶意时,将其加入黑名单,后续请求将被拒绝。设计思路如下:
- 令牌生成: 用户登录或者其他需要身份验证的场景,生成一个唯一的令牌(例如 JWT)。
- 令牌存储: 将令牌存储在 Redis 中,可以使用 SET 结构,并设置一个过期时间,避免令牌无限期地存在。
- 黑名单存储: 使用 Redis 的 SET 结构来存储黑名单中的令牌。 当某个令牌被判定为非法时,将其添加到这个 SET 中。
- 请求验证: 每次请求时,先验证令牌是否有效(是否存在于 Redis 中),再检查令牌是否在黑名单中。
3. Lua 脚本实现
我们使用 Lua 脚本来封装黑名单的检查和添加操作,保证原子性。
检查令牌是否在黑名单中的 Lua 脚本 (is_token_in_blacklist.lua):
-- KEYS[1]: 黑名单的 key -- ARGV[1]: 令牌 local blacklist_key = KEYS[1] local token = ARGV[1] -- 判断令牌是否存在于黑名单中 if redis.call('SISMEMBER', blacklist_key, token) == 1 then return 1 -- 在黑名单中 else return 0 -- 不在黑名单中 end 将令牌添加到黑名单的 Lua 脚本 (add_token_to_blacklist.lua):
-- KEYS[1]: 黑名单的 key -- ARGV[1]: 令牌 local blacklist_key = KEYS[1] local token = ARGV[1] -- 将令牌添加到黑名单 redis.call('SADD', blacklist_key, token) return 1
4. 代码示例 (Python)
import redis import json # Redis 连接配置 redis_host = 'localhost' redis_port = 6379 redis_db = 0 # 连接 Redis r = redis.Redis(host=redis_host, port=redis_port, db=redis_db) # 加载 Lua 脚本 def load_lua_script(script_path): with open(script_path, 'r') as f: script_content = f.read() return r.register_script(script_content) # 加载黑名单检查脚本 is_token_in_blacklist = load_lua_script('is_token_in_blacklist.lua') # 加载添加到黑名单的脚本 add_token_to_blacklist = load_lua_script('add_token_to_blacklist.lua') # 检查令牌是否在黑名单中 def check_token_in_blacklist(token): result = is_token_in_blacklist(keys=['blacklist'], args=[token]) return result # 将令牌添加到黑名单 def add_token_to_blacklist_func(token): add_token_to_blacklist(keys=['blacklist'], args=[token]) print(f'Token {token} added to blacklist.') # 模拟请求验证 def validate_request(token): if check_token_in_blacklist(token): print('Token in blacklist. Request denied.') return False else: print('Token valid. Request accepted.') return True # 示例用法 token1 = 'user1_token' token2 = 'user2_token' # 模拟用户请求 validate_request(token1) # Token valid. Request accepted. validate_request(token2) # Token valid. Request accepted. # 将 token1 加入黑名单 add_token_to_blacklist_func(token1) # 再次验证 validate_request(token1) # Token in blacklist. Request denied. validate_request(token2) # Token valid. Request accepted.
- 代码说明:
- 我们首先连接到 Redis 服务器。
- 然后加载了两个 Lua 脚本:
is_token_in_blacklist.lua
和add_token_to_blacklist.lua
。 check_token_in_blacklist()
函数执行了黑名单检查脚本。add_token_to_blacklist_func()
函数执行了添加令牌到黑名单的脚本。validate_request()
函数模拟了请求验证的过程。
5. 进阶优化
过期时间: 黑名单中的令牌可以设置一个过期时间,例如 1 天或者更短,避免永久封禁合法用户。 你可以使用 Redis 的
EXPIRE
命令来设置过期时间。令牌类型: 可以根据不同的业务场景,使用不同的令牌类型,例如用户令牌、设备令牌等。 针对不同的令牌类型,可以设置不同的黑名单策略。
黑名单范围: 可以支持更细粒度的黑名单,比如 IP 黑名单、用户 ID 黑名单等。
异步处理: 将添加令牌到黑名单的操作异步化,避免阻塞主线程。 例如,可以使用消息队列(如 Kafka)来实现。
监控和报警: 监控黑名单的变化情况,如果黑名单中的令牌数量异常增加,及时报警,排查问题。
6. 总结
通过 Redis 和 Lua 脚本,我们构建了一个高效、可靠的分布式令牌黑名单机制。 这种方案兼顾了性能、原子性和灵活性,能够有效地保护系统安全。 当然,在实际应用中,还需要根据具体的业务场景进行调整和优化,例如设置合理的过期时间、采用更细粒度的黑名单、以及添加监控和报警机制等。
希望这篇文章对您有所帮助! 如果您在实际应用中遇到了问题,欢迎留言讨论! 让我们一起在技术的道路上不断前行! 嘿,对了,您还可以思考下,除了黑名单,还有什么其他方式来限制恶意访问呢? 欢迎留言,咱们一起聊聊!