WEBKT

别再硬抗了!Redis + Lua 轻松搞定分布式令牌黑名单机制,拒绝恶意访问!

336 0 0 0

在互联网应用中,为了防止恶意访问,保障系统安全,我们经常需要实现一个黑名单机制。 而在分布式环境下,如何高效、可靠地实现黑名单机制就成了一个值得探讨的问题。 本文将结合 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.luaadd_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 脚本,我们构建了一个高效、可靠的分布式令牌黑名单机制。 这种方案兼顾了性能、原子性和灵活性,能够有效地保护系统安全。 当然,在实际应用中,还需要根据具体的业务场景进行调整和优化,例如设置合理的过期时间、采用更细粒度的黑名单、以及添加监控和报警机制等。

希望这篇文章对您有所帮助! 如果您在实际应用中遇到了问题,欢迎留言讨论! 让我们一起在技术的道路上不断前行! 嘿,对了,您还可以思考下,除了黑名单,还有什么其他方式来限制恶意访问呢? 欢迎留言,咱们一起聊聊!

后端架构师 RedisLua分布式令牌黑名单

评论点评