1. 安全概述

1.1 Web安全威胁

OpenResty作为Web应用网关,面临多种安全威胁: - SQL注入:恶意SQL代码注入 - XSS攻击:跨站脚本攻击 - CSRF攻击:跨站请求伪造 - DDoS攻击:分布式拒绝服务 - 暴力破解:密码暴力破解 - 数据泄露:敏感信息泄露 - 会话劫持:会话令牌被盗用

1.2 OpenResty安全优势

  • 高性能防护:基于Nginx的高并发处理
  • 灵活规则:Lua脚本自定义安全策略
  • 实时响应:毫秒级安全决策
  • 可扩展性:模块化安全组件
  • 集成能力:与现有安全系统集成

1.3 安全架构设计

┌─────────────────┐
│   客户端请求     │
└─────────┬───────┘
          │
┌─────────▼───────┐
│   WAF防护层     │  ← 恶意请求过滤
├─────────────────┤
│   认证授权层     │  ← 身份验证
├─────────────────┤
│   限流控制层     │  ← 流量控制
├─────────────────┤
│   业务逻辑层     │  ← 应用处理
└─────────────────┘

2. 身份认证

2.1 基础认证(Basic Auth)

-- 基础认证模块
local basic_auth = {}
local resty_md5 = require "resty.md5"
local str = require "resty.string"

-- 用户数据库(实际应用中应从数据库或Redis获取)
local users = {
    ["admin"] = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",  -- password: hello
    ["user1"] = "ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f"   -- password: secret
}

-- SHA256哈希函数
local function sha256(str)
    local resty_sha256 = require "resty.sha256"
    local sha256 = resty_sha256:new()
    sha256:update(str)
    local digest = sha256:final()
    return require("resty.string").to_hex(digest)
end

-- 解析Authorization头
local function parse_auth_header(auth_header)
    if not auth_header then
        return nil, nil
    end
    
    local auth_type, credentials = string.match(auth_header, "^(%S+)%s+(.+)$")
    if auth_type ~= "Basic" then
        return nil, nil
    end
    
    -- Base64解码
    local decoded = ngx.decode_base64(credentials)
    if not decoded then
        return nil, nil
    end
    
    local username, password = string.match(decoded, "^([^:]+):(.*)$")
    return username, password
end

-- 验证用户凭据
function basic_auth.authenticate()
    local auth_header = ngx.var.http_authorization
    local username, password = parse_auth_header(auth_header)
    
    if not username or not password then
        ngx.header["WWW-Authenticate"] = 'Basic realm="Protected Area"'
        ngx.status = 401
        ngx.say("Authentication required")
        ngx.exit(401)
    end
    
    -- 验证用户名和密码
    local stored_hash = users[username]
    if not stored_hash then
        ngx.log(ngx.WARN, "Unknown user: ", username)
        ngx.header["WWW-Authenticate"] = 'Basic realm="Protected Area"'
        ngx.status = 401
        ngx.say("Invalid credentials")
        ngx.exit(401)
    end
    
    local password_hash = sha256(password)
    if password_hash ~= stored_hash then
        ngx.log(ngx.WARN, "Invalid password for user: ", username)
        ngx.header["WWW-Authenticate"] = 'Basic realm="Protected Area"'
        ngx.status = 401
        ngx.say("Invalid credentials")
        ngx.exit(401)
    end
    
    -- 认证成功,设置用户信息
    ngx.var.authenticated_user = username
    ngx.log(ngx.INFO, "User authenticated: ", username)
end

-- 检查用户权限
function basic_auth.check_permission(required_role)
    local username = ngx.var.authenticated_user
    if not username then
        ngx.status = 401
        ngx.say("Not authenticated")
        ngx.exit(401)
    end
    
    -- 简单的角色检查(实际应用中应从数据库获取)
    local user_roles = {
        ["admin"] = {"admin", "user"},
        ["user1"] = {"user"}
    }
    
    local roles = user_roles[username] or {}
    for _, role in ipairs(roles) do
        if role == required_role then
            return true
        end
    end
    
    ngx.status = 403
    ngx.say("Insufficient permissions")
    ngx.exit(403)
end

return basic_auth

2.2 JWT认证

-- JWT认证模块
local jwt_auth = {}
local jwt = require "resty.jwt"
local cjson = require "cjson"

-- JWT配置
local jwt_config = {
    secret = "your-secret-key-here",  -- 应该从环境变量或配置文件读取
    algorithm = "HS256",
    exp = 3600,  -- 1小时过期
    issuer = "openresty-app"
}

-- 生成JWT令牌
function jwt_auth.generate_token(user_data)
    local now = ngx.time()
    
    local payload = {
        iss = jwt_config.issuer,
        sub = user_data.username,
        iat = now,
        exp = now + jwt_config.exp,
        user_id = user_data.user_id,
        username = user_data.username,
        roles = user_data.roles or {}
    }
    
    local token = jwt:sign(jwt_config.secret, {
        header = {
            typ = "JWT",
            alg = jwt_config.algorithm
        },
        payload = payload
    })
    
    return token
end

-- 验证JWT令牌
function jwt_auth.verify_token(token)
    if not token then
        return nil, "Token not provided"
    end
    
    local jwt_obj = jwt:verify(jwt_config.secret, token)
    
    if not jwt_obj.valid then
        return nil, "Invalid token: " .. (jwt_obj.reason or "unknown error")
    end
    
    local payload = jwt_obj.payload
    
    -- 检查过期时间
    if payload.exp and payload.exp < ngx.time() then
        return nil, "Token expired"
    end
    
    -- 检查发行者
    if payload.iss ~= jwt_config.issuer then
        return nil, "Invalid issuer"
    end
    
    return payload
end

-- 从请求中提取令牌
local function extract_token()
    -- 从Authorization头提取
    local auth_header = ngx.var.http_authorization
    if auth_header then
        local token = string.match(auth_header, "^Bearer%s+(.+)$")
        if token then
            return token
        end
    end
    
    -- 从查询参数提取
    local args = ngx.req.get_uri_args()
    if args.token then
        return args.token
    end
    
    -- 从Cookie提取
    local cookie_token = ngx.var.cookie_access_token
    if cookie_token then
        return cookie_token
    end
    
    return nil
end

-- JWT认证中间件
function jwt_auth.authenticate()
    local token = extract_token()
    
    if not token then
        ngx.status = 401
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "unauthorized",
            message = "Access token required"
        }))
        ngx.exit(401)
    end
    
    local payload, err = jwt_auth.verify_token(token)
    if not payload then
        ngx.log(ngx.WARN, "JWT verification failed: ", err)
        ngx.status = 401
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "unauthorized",
            message = err
        }))
        ngx.exit(401)
    end
    
    -- 设置用户上下文
    ngx.ctx.user = {
        user_id = payload.user_id,
        username = payload.username,
        roles = payload.roles
    }
    
    ngx.log(ngx.INFO, "JWT authentication successful for user: ", payload.username)
end

-- 检查用户角色
function jwt_auth.require_role(required_role)
    local user = ngx.ctx.user
    if not user then
        ngx.status = 401
        ngx.say("Not authenticated")
        ngx.exit(401)
    end
    
    local roles = user.roles or {}
    for _, role in ipairs(roles) do
        if role == required_role then
            return true
        end
    end
    
    ngx.status = 403
    ngx.header.content_type = "application/json"
    ngx.say(cjson.encode({
        error = "forbidden",
        message = "Insufficient permissions"
    }))
    ngx.exit(403)
end

-- 刷新令牌
function jwt_auth.refresh_token(old_token)
    local payload, err = jwt_auth.verify_token(old_token)
    if not payload then
        return nil, err
    end
    
    -- 检查令牌是否即将过期(剩余时间少于30分钟)
    local remaining_time = payload.exp - ngx.time()
    if remaining_time > 1800 then  -- 30分钟
        return old_token  -- 不需要刷新
    end
    
    -- 生成新令牌
    local user_data = {
        user_id = payload.user_id,
        username = payload.username,
        roles = payload.roles
    }
    
    return jwt_auth.generate_token(user_data)
end

return jwt_auth

2.3 OAuth 2.0集成

-- OAuth 2.0客户端模块
local oauth2 = {}
local http = require "resty.http"
local cjson = require "cjson"
local jwt_auth = require "jwt_auth"

-- OAuth配置
local oauth_config = {
    google = {
        client_id = "your-google-client-id",
        client_secret = "your-google-client-secret",
        redirect_uri = "https://your-domain.com/auth/google/callback",
        auth_url = "https://accounts.google.com/o/oauth2/v2/auth",
        token_url = "https://oauth2.googleapis.com/token",
        user_info_url = "https://www.googleapis.com/oauth2/v2/userinfo",
        scope = "openid email profile"
    },
    github = {
        client_id = "your-github-client-id",
        client_secret = "your-github-client-secret",
        redirect_uri = "https://your-domain.com/auth/github/callback",
        auth_url = "https://github.com/login/oauth/authorize",
        token_url = "https://github.com/login/oauth/access_token",
        user_info_url = "https://api.github.com/user",
        scope = "user:email"
    }
}

-- 生成授权URL
function oauth2.get_auth_url(provider, state)
    local config = oauth_config[provider]
    if not config then
        return nil, "Unsupported provider: " .. provider
    end
    
    local params = {
        client_id = config.client_id,
        redirect_uri = config.redirect_uri,
        scope = config.scope,
        response_type = "code",
        state = state or ngx.time()
    }
    
    local query_string = ngx.encode_args(params)
    return config.auth_url .. "?" .. query_string
end

-- 交换授权码获取访问令牌
function oauth2.exchange_code(provider, code)
    local config = oauth_config[provider]
    if not config then
        return nil, "Unsupported provider: " .. provider
    end
    
    local httpc = http.new()
    httpc:set_timeout(10000)  -- 10秒超时
    
    local params = {
        client_id = config.client_id,
        client_secret = config.client_secret,
        code = code,
        grant_type = "authorization_code",
        redirect_uri = config.redirect_uri
    }
    
    local res, err = httpc:request_uri(config.token_url, {
        method = "POST",
        headers = {
            ["Content-Type"] = "application/x-www-form-urlencoded",
            ["Accept"] = "application/json"
        },
        body = ngx.encode_args(params)
    })
    
    if not res then
        return nil, "HTTP request failed: " .. err
    end
    
    if res.status ~= 200 then
        return nil, "OAuth token exchange failed: " .. res.status
    end
    
    local token_data = cjson.decode(res.body)
    return token_data
end

-- 获取用户信息
function oauth2.get_user_info(provider, access_token)
    local config = oauth_config[provider]
    if not config then
        return nil, "Unsupported provider: " .. provider
    end
    
    local httpc = http.new()
    httpc:set_timeout(10000)
    
    local res, err = httpc:request_uri(config.user_info_url, {
        method = "GET",
        headers = {
            ["Authorization"] = "Bearer " .. access_token,
            ["Accept"] = "application/json"
        }
    })
    
    if not res then
        return nil, "HTTP request failed: " .. err
    end
    
    if res.status ~= 200 then
        return nil, "Failed to get user info: " .. res.status
    end
    
    local user_info = cjson.decode(res.body)
    return user_info
end

-- OAuth认证流程
function oauth2.authenticate(provider)
    local args = ngx.req.get_uri_args()
    local code = args.code
    local state = args.state
    
    if not code then
        -- 重定向到OAuth提供商
        local auth_url = oauth2.get_auth_url(provider, ngx.time())
        ngx.redirect(auth_url)
        return
    end
    
    -- 交换授权码
    local token_data, err = oauth2.exchange_code(provider, code)
    if not token_data then
        ngx.log(ngx.ERR, "OAuth code exchange failed: ", err)
        ngx.status = 400
        ngx.say("Authentication failed")
        ngx.exit(400)
    end
    
    -- 获取用户信息
    local user_info, err = oauth2.get_user_info(provider, token_data.access_token)
    if not user_info then
        ngx.log(ngx.ERR, "Failed to get user info: ", err)
        ngx.status = 400
        ngx.say("Authentication failed")
        ngx.exit(400)
    end
    
    -- 创建本地用户会话
    local user_data = {
        user_id = user_info.id or user_info.login,
        username = user_info.name or user_info.login,
        email = user_info.email,
        provider = provider,
        roles = {"user"}  -- 默认角色
    }
    
    -- 生成JWT令牌
    local jwt_token = jwt_auth.generate_token(user_data)
    
    -- 设置Cookie
    ngx.header["Set-Cookie"] = "access_token=" .. jwt_token .. "; Path=/; HttpOnly; Secure; SameSite=Strict"
    
    -- 重定向到应用首页
    ngx.redirect("/dashboard")
end

return oauth2

3. 访问控制

3.1 基于角色的访问控制(RBAC)

-- RBAC访问控制模块
local rbac = {}
local cjson = require "cjson"

-- 权限定义
local permissions = {
    ["user.read"] = "读取用户信息",
    ["user.write"] = "修改用户信息",
    ["user.delete"] = "删除用户",
    ["admin.read"] = "读取管理信息",
    ["admin.write"] = "修改系统配置",
    ["api.read"] = "调用只读API",
    ["api.write"] = "调用写入API"
}

-- 角色权限映射
local role_permissions = {
    ["guest"] = {"api.read"},
    ["user"] = {"user.read", "api.read"},
    ["moderator"] = {"user.read", "user.write", "api.read", "api.write"},
    ["admin"] = {"user.read", "user.write", "user.delete", "admin.read", "admin.write", "api.read", "api.write"}
}

-- 资源权限映射
local resource_permissions = {
    ["/api/users"] = {
        ["GET"] = "user.read",
        ["POST"] = "user.write",
        ["PUT"] = "user.write",
        ["DELETE"] = "user.delete"
    },
    ["/api/admin"] = {
        ["GET"] = "admin.read",
        ["POST"] = "admin.write",
        ["PUT"] = "admin.write",
        ["DELETE"] = "admin.write"
    },
    ["/api/public"] = {
        ["GET"] = "api.read"
    }
}

-- 获取用户权限
function rbac.get_user_permissions(user_roles)
    local user_permissions = {}
    
    for _, role in ipairs(user_roles) do
        local role_perms = role_permissions[role] or {}
        for _, perm in ipairs(role_perms) do
            user_permissions[perm] = true
        end
    end
    
    return user_permissions
end

-- 检查用户是否有特定权限
function rbac.has_permission(user_roles, required_permission)
    local user_permissions = rbac.get_user_permissions(user_roles)
    return user_permissions[required_permission] == true
end

-- 检查资源访问权限
function rbac.check_resource_access(user_roles, resource_path, method)
    -- 查找匹配的资源模式
    local required_permission = nil
    
    for pattern, methods in pairs(resource_permissions) do
        if string.match(resource_path, pattern) then
            required_permission = methods[method]
            break
        end
    end
    
    if not required_permission then
        -- 如果没有定义权限,默认拒绝访问
        return false, "No permission defined for resource"
    end
    
    if rbac.has_permission(user_roles, required_permission) then
        return true
    else
        return false, "Insufficient permissions: " .. required_permission
    end
end

-- RBAC中间件
function rbac.middleware()
    local user = ngx.ctx.user
    if not user then
        ngx.status = 401
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "unauthorized",
            message = "Authentication required"
        }))
        ngx.exit(401)
    end
    
    local resource_path = ngx.var.uri
    local method = ngx.var.request_method
    local user_roles = user.roles or {"guest"}
    
    local has_access, err = rbac.check_resource_access(user_roles, resource_path, method)
    
    if not has_access then
        ngx.log(ngx.WARN, "Access denied for user ", user.username, " to ", resource_path, ": ", err)
        ngx.status = 403
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "forbidden",
            message = err
        }))
        ngx.exit(403)
    end
    
    ngx.log(ngx.INFO, "Access granted for user ", user.username, " to ", resource_path)
end

-- 动态权限检查
function rbac.check_dynamic_permission(permission_name)
    local user = ngx.ctx.user
    if not user then
        return false
    end
    
    local user_roles = user.roles or {"guest"}
    return rbac.has_permission(user_roles, permission_name)
end

return rbac

3.2 IP白名单和黑名单

-- IP访问控制模块
local ip_control = {}
local iputils = require "resty.iputils"

-- IP白名单(CIDR格式)
local whitelist = {
    "127.0.0.1/32",      -- 本地回环
    "10.0.0.0/8",        -- 私有网络A类
    "172.16.0.0/12",     -- 私有网络B类
    "192.168.0.0/16",    -- 私有网络C类
    "203.0.113.0/24"     -- 示例公网段
}

-- IP黑名单
local blacklist = {
    "192.0.2.0/24",      -- 示例恶意IP段
    "198.51.100.0/24"    -- 示例恶意IP段
}

-- 解析IP列表
local function parse_ip_list(ip_list)
    local parsed = {}
    for _, ip_range in ipairs(ip_list) do
        table.insert(parsed, iputils.parse_cidr(ip_range))
    end
    return parsed
end

-- 预解析IP列表
local parsed_whitelist = parse_ip_list(whitelist)
local parsed_blacklist = parse_ip_list(blacklist)

-- 检查IP是否在列表中
local function ip_in_list(ip, parsed_list)
    for _, cidr in ipairs(parsed_list) do
        if iputils.ip_in_cidrs(ip, {cidr}) then
            return true
        end
    end
    return false
end

-- 获取真实客户端IP
function ip_control.get_real_ip()
    -- 检查X-Forwarded-For头(代理环境)
    local xff = ngx.var.http_x_forwarded_for
    if xff then
        local first_ip = string.match(xff, "([^,]+)")
        if first_ip then
            return string.gsub(first_ip, "%s+", "")
        end
    end
    
    -- 检查X-Real-IP头
    local real_ip = ngx.var.http_x_real_ip
    if real_ip then
        return real_ip
    end
    
    -- 使用直接连接IP
    return ngx.var.remote_addr
end

-- 白名单检查
function ip_control.check_whitelist()
    local client_ip = ip_control.get_real_ip()
    
    if ip_in_list(client_ip, parsed_whitelist) then
        ngx.log(ngx.INFO, "IP ", client_ip, " is in whitelist")
        return true
    else
        ngx.log(ngx.WARN, "IP ", client_ip, " is not in whitelist")
        ngx.status = 403
        ngx.say("Access denied: IP not in whitelist")
        ngx.exit(403)
    end
end

-- 黑名单检查
function ip_control.check_blacklist()
    local client_ip = ip_control.get_real_ip()
    
    if ip_in_list(client_ip, parsed_blacklist) then
        ngx.log(ngx.WARN, "IP ", client_ip, " is in blacklist")
        ngx.status = 403
        ngx.say("Access denied: IP blacklisted")
        ngx.exit(403)
    else
        ngx.log(ngx.INFO, "IP ", client_ip, " is not in blacklist")
        return true
    end
end

-- 地理位置检查
function ip_control.check_geo_location(allowed_countries)
    local client_ip = ip_control.get_real_ip()
    
    -- 这里需要集成GeoIP数据库,如MaxMind GeoLite2
    -- 示例实现(需要安装lua-resty-maxminddb)
    local maxminddb = require "resty.maxminddb"
    
    if not maxminddb.initted() then
        maxminddb.init("/path/to/GeoLite2-Country.mmdb")
    end
    
    local res, err = maxminddb.lookup(client_ip)
    if not res then
        ngx.log(ngx.WARN, "GeoIP lookup failed for ", client_ip, ": ", err)
        return true  -- 默认允许访问
    end
    
    local country_code = res.country and res.country.iso_code
    if not country_code then
        ngx.log(ngx.WARN, "No country code found for IP ", client_ip)
        return true
    end
    
    -- 检查是否在允许的国家列表中
    for _, allowed_country in ipairs(allowed_countries) do
        if country_code == allowed_country then
            ngx.log(ngx.INFO, "IP ", client_ip, " from allowed country: ", country_code)
            return true
        end
    end
    
    ngx.log(ngx.WARN, "IP ", client_ip, " from blocked country: ", country_code)
    ngx.status = 403
    ngx.say("Access denied: Geographic restriction")
    ngx.exit(403)
end

-- 动态IP控制(基于Redis)
function ip_control.dynamic_check()
    local redis = require "resty.redis"
    local client_ip = ip_control.get_real_ip()
    
    local red = redis:new()
    red:set_timeout(1000)
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "Failed to connect to Redis: ", err)
        return true  -- 默认允许访问
    end
    
    -- 检查IP是否在动态黑名单中
    local is_blocked = red:get("blocked_ip:" .. client_ip)
    if is_blocked and is_blocked ~= ngx.null then
        ngx.log(ngx.WARN, "IP ", client_ip, " is dynamically blocked")
        red:set_keepalive(10000, 100)
        ngx.status = 403
        ngx.say("Access denied: IP temporarily blocked")
        ngx.exit(403)
    end
    
    red:set_keepalive(10000, 100)
    return true
end

return ip_control

4. 限流与防护

4.1 请求限流

-- 限流模块
local rate_limiter = {}
local limit_req = require "resty.limit.req"
local limit_conn = require "resty.limit.conn"

-- 限流配置
local rate_limit_config = {
    -- 全局限流:每秒100个请求,突发200个
    global = {
        rate = 100,
        burst = 200,
        delay = 50
    },
    -- API限流:每秒50个请求
    api = {
        rate = 50,
        burst = 100,
        delay = 25
    },
    -- 用户限流:每个用户每秒10个请求
    user = {
        rate = 10,
        burst = 20,
        delay = 5
    }
}

-- 创建限流器实例
local limiters = {}

local function get_limiter(limiter_type)
    if not limiters[limiter_type] then
        local config = rate_limit_config[limiter_type]
        if config then
            limiters[limiter_type] = limit_req.new("rate_limit_" .. limiter_type, config.rate, config.burst)
        end
    end
    return limiters[limiter_type]
end

-- 全局限流
function rate_limiter.global_limit()
    local limiter = get_limiter("global")
    if not limiter then
        return
    end
    
    local key = "global"
    local delay, err = limiter:incoming(key, true)
    
    if not delay then
        if err == "rejected" then
            ngx.log(ngx.WARN, "Global rate limit exceeded")
            ngx.status = 429
            ngx.header["Retry-After"] = "1"
            ngx.say("Too Many Requests")
            ngx.exit(429)
        else
            ngx.log(ngx.ERR, "Rate limiter error: ", err)
        end
        return
    end
    
    if delay > 0 then
        ngx.sleep(delay)
    end
end

-- 基于IP的限流
function rate_limiter.ip_limit()
    local limiter = get_limiter("api")
    if not limiter then
        return
    end
    
    local ip_control = require "ip_control"
    local client_ip = ip_control.get_real_ip()
    
    local delay, err = limiter:incoming(client_ip, true)
    
    if not delay then
        if err == "rejected" then
            ngx.log(ngx.WARN, "IP rate limit exceeded for ", client_ip)
            ngx.status = 429
            ngx.header["Retry-After"] = "1"
            ngx.say("Too Many Requests")
            ngx.exit(429)
        else
            ngx.log(ngx.ERR, "Rate limiter error: ", err)
        end
        return
    end
    
    if delay > 0 then
        ngx.sleep(delay)
    end
end

-- 基于用户的限流
function rate_limiter.user_limit()
    local user = ngx.ctx.user
    if not user then
        return  -- 未认证用户不进行用户级限流
    end
    
    local limiter = get_limiter("user")
    if not limiter then
        return
    end
    
    local user_key = "user:" .. user.user_id
    local delay, err = limiter:incoming(user_key, true)
    
    if not delay then
        if err == "rejected" then
            ngx.log(ngx.WARN, "User rate limit exceeded for ", user.username)
            ngx.status = 429
            ngx.header["Retry-After"] = "1"
            ngx.say("Too Many Requests")
            ngx.exit(429)
        else
            ngx.log(ngx.ERR, "Rate limiter error: ", err)
        end
        return
    end
    
    if delay > 0 then
        ngx.sleep(delay)
    end
end

-- 连接数限制
function rate_limiter.connection_limit(max_connections)
    max_connections = max_connections or 100
    
    local limiter = limit_conn.new("connection_limit", max_connections, 0, 0.5)
    
    local ip_control = require "ip_control"
    local client_ip = ip_control.get_real_ip()
    
    local delay, err = limiter:incoming(client_ip, true)
    
    if not delay then
        if err == "rejected" then
            ngx.log(ngx.WARN, "Connection limit exceeded for ", client_ip)
            ngx.status = 503
            ngx.say("Service Temporarily Unavailable")
            ngx.exit(503)
        else
            ngx.log(ngx.ERR, "Connection limiter error: ", err)
        end
        return
    end
    
    if delay > 0 then
        ngx.sleep(delay)
    end
end

-- 自适应限流
function rate_limiter.adaptive_limit()
    local stats_cache = ngx.shared.stats
    if not stats_cache then
        return
    end
    
    -- 获取系统负载指标
    local cpu_usage = stats_cache:get("cpu_usage") or 0
    local memory_usage = stats_cache:get("memory_usage") or 0
    local response_time = stats_cache:get("avg_response_time") or 0
    
    -- 根据系统负载调整限流阈值
    local base_rate = 100
    local rate_factor = 1.0
    
    if cpu_usage > 80 or memory_usage > 80 then
        rate_factor = 0.5  -- 高负载时减少50%
    elseif response_time > 1000 then  -- 响应时间超过1秒
        rate_factor = 0.7  -- 减少30%
    elseif cpu_usage < 30 and memory_usage < 30 then
        rate_factor = 1.5  -- 低负载时增加50%
    end
    
    local adjusted_rate = math.floor(base_rate * rate_factor)
    
    -- 使用调整后的限流率
    local limiter = limit_req.new("adaptive_limit", adjusted_rate, adjusted_rate * 2)
    
    local delay, err = limiter:incoming("adaptive", true)
    
    if not delay then
        if err == "rejected" then
            ngx.log(ngx.WARN, "Adaptive rate limit exceeded (rate: ", adjusted_rate, ")")
            ngx.status = 429
            ngx.say("Too Many Requests")
            ngx.exit(429)
        end
    end
    
    if delay > 0 then
        ngx.sleep(delay)
    end
end

return rate_limiter

4.2 DDoS防护

-- DDoS防护模块
local ddos_protection = {}
local redis = require "resty.redis"
local cjson = require "cjson"

-- DDoS检测配置
local ddos_config = {
    -- 检测窗口(秒)
    window_size = 60,
    -- 阈值配置
    thresholds = {
        requests_per_ip = 1000,     -- 单IP每分钟请求数
        total_requests = 10000,     -- 总请求数每分钟
        error_rate = 0.5,           -- 错误率阈值
        new_ips_rate = 100          -- 新IP数量每分钟
    },
    -- 封禁时间(秒)
    ban_duration = 300,
    -- 挑战模式持续时间
    challenge_duration = 60
}

-- 获取Redis连接
local function get_redis()
    local red = redis:new()
    red:set_timeout(1000)
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "Failed to connect to Redis: ", err)
        return nil
    end
    
    return red
end

-- 记录请求统计
function ddos_protection.record_request()
    local red = get_redis()
    if not red then
        return
    end
    
    local ip_control = require "ip_control"
    local client_ip = ip_control.get_real_ip()
    local current_time = ngx.time()
    local window_key = math.floor(current_time / ddos_config.window_size)
    
    -- 记录IP请求数
    local ip_key = "ddos:ip:" .. window_key .. ":" .. client_ip
    red:incr(ip_key)
    red:expire(ip_key, ddos_config.window_size * 2)
    
    -- 记录总请求数
    local total_key = "ddos:total:" .. window_key
    red:incr(total_key)
    red:expire(total_key, ddos_config.window_size * 2)
    
    -- 记录新IP
    local new_ip_key = "ddos:new_ip:" .. window_key
    local ip_seen_key = "ddos:ip_seen:" .. client_ip
    local is_new = red:get(ip_seen_key)
    if not is_new or is_new == ngx.null then
        red:incr(new_ip_key)
        red:expire(new_ip_key, ddos_config.window_size * 2)
        red:setex(ip_seen_key, 3600, "1")  -- 标记IP已见过,1小时过期
    end
    
    red:set_keepalive(10000, 100)
end

-- 记录错误响应
function ddos_protection.record_error()
    local red = get_redis()
    if not red then
        return
    end
    
    local current_time = ngx.time()
    local window_key = math.floor(current_time / ddos_config.window_size)
    
    local error_key = "ddos:errors:" .. window_key
    red:incr(error_key)
    red:expire(error_key, ddos_config.window_size * 2)
    
    red:set_keepalive(10000, 100)
end

-- 检测DDoS攻击
function ddos_protection.detect_attack()
    local red = get_redis()
    if not red then
        return false
    end
    
    local ip_control = require "ip_control"
    local client_ip = ip_control.get_real_ip()
    local current_time = ngx.time()
    local window_key = math.floor(current_time / ddos_config.window_size)
    
    -- 检查IP请求频率
    local ip_key = "ddos:ip:" .. window_key .. ":" .. client_ip
    local ip_requests = red:get(ip_key) or 0
    
    if tonumber(ip_requests) > ddos_config.thresholds.requests_per_ip then
        ngx.log(ngx.WARN, "DDoS detected: High request rate from IP ", client_ip, " (", ip_requests, " requests)")
        ddos_protection.ban_ip(client_ip, "high_request_rate")
        red:set_keepalive(10000, 100)
        return true
    end
    
    -- 检查总请求数
    local total_key = "ddos:total:" .. window_key
    local total_requests = red:get(total_key) or 0
    
    if tonumber(total_requests) > ddos_config.thresholds.total_requests then
        ngx.log(ngx.WARN, "DDoS detected: High total request rate (", total_requests, " requests)")
        ddos_protection.enable_challenge_mode()
        red:set_keepalive(10000, 100)
        return true
    end
    
    -- 检查错误率
    local error_key = "ddos:errors:" .. window_key
    local error_count = red:get(error_key) or 0
    local error_rate = tonumber(total_requests) > 0 and tonumber(error_count) / tonumber(total_requests) or 0
    
    if error_rate > ddos_config.thresholds.error_rate then
        ngx.log(ngx.WARN, "DDoS detected: High error rate (", error_rate * 100, "%)")
        ddos_protection.enable_challenge_mode()
        red:set_keepalive(10000, 100)
        return true
    end
    
    red:set_keepalive(10000, 100)
    return false
end

-- 封禁IP
function ddos_protection.ban_ip(ip, reason)
    local red = get_redis()
    if not red then
        return
    end
    
    local ban_key = "ddos:banned:" .. ip
    local ban_info = {
        reason = reason,
        banned_at = ngx.time(),
        expires_at = ngx.time() + ddos_config.ban_duration
    }
    
    red:setex(ban_key, ddos_config.ban_duration, cjson.encode(ban_info))
    red:set_keepalive(10000, 100)
    
    ngx.log(ngx.WARN, "IP ", ip, " banned for ", ddos_config.ban_duration, " seconds. Reason: ", reason)
end

-- 检查IP是否被封禁
function ddos_protection.is_ip_banned(ip)
    local red = get_redis()
    if not red then
        return false
    end
    
    local ban_key = "ddos:banned:" .. ip
    local ban_info = red:get(ban_key)
    
    red:set_keepalive(10000, 100)
    
    if ban_info and ban_info ~= ngx.null then
        local ban_data = cjson.decode(ban_info)
        if ban_data.expires_at > ngx.time() then
            return true, ban_data
        end
    end
    
    return false
end

-- 启用挑战模式
function ddos_protection.enable_challenge_mode()
    local red = get_redis()
    if not red then
        return
    end
    
    local challenge_key = "ddos:challenge_mode"
    red:setex(challenge_key, ddos_config.challenge_duration, "1")
    red:set_keepalive(10000, 100)
    
    ngx.log(ngx.WARN, "Challenge mode enabled for ", ddos_config.challenge_duration, " seconds")
end

-- 检查是否处于挑战模式
function ddos_protection.is_challenge_mode()
    local red = get_redis()
    if not red then
        return false
    end
    
    local challenge_key = "ddos:challenge_mode"
    local is_challenge = red:get(challenge_key)
    
    red:set_keepalive(10000, 100)
    
    return is_challenge and is_challenge ~= ngx.null
end

-- JavaScript挑战
function ddos_protection.javascript_challenge()
    local challenge_html = [[
<!DOCTYPE html>
<html>
<head>
    <title>Security Check</title>
    <meta charset="utf-8">
</head>
<body>
    <h1>Security Check</h1>
    <p>Please wait while we verify your request...</p>
    <script>
        // 简单的JavaScript挑战
        var challenge = Math.floor(Math.random() * 1000000);
        var answer = challenge * 2 + 1;
        
        setTimeout(function() {
            var form = document.createElement('form');
            form.method = 'POST';
            form.action = window.location.href;
            
            var challengeInput = document.createElement('input');
            challengeInput.type = 'hidden';
            challengeInput.name = 'challenge';
            challengeInput.value = challenge;
            
            var answerInput = document.createElement('input');
            answerInput.type = 'hidden';
            answerInput.name = 'answer';
            answerInput.value = answer;
            
            form.appendChild(challengeInput);
            form.appendChild(answerInput);
            document.body.appendChild(form);
            form.submit();
        }, 2000);
    </script>
</body>
</html>
    ]]
    
    ngx.header.content_type = "text/html"
    ngx.say(challenge_html)
    ngx.exit(200)
end

-- 验证JavaScript挑战
function ddos_protection.verify_challenge()
    ngx.req.read_body()
    local args = ngx.req.get_post_args()
    
    local challenge = tonumber(args.challenge)
    local answer = tonumber(args.answer)
    
    if not challenge or not answer then
        return false
    end
    
    local expected_answer = challenge * 2 + 1
    if answer == expected_answer then
        -- 挑战成功,设置通过标记
        local ip_control = require "ip_control"
        local client_ip = ip_control.get_real_ip()
        
        local red = get_redis()
        if red then
            local pass_key = "ddos:challenge_passed:" .. client_ip
            red:setex(pass_key, 3600, "1")  -- 1小时内免挑战
            red:set_keepalive(10000, 100)
        end
        
        return true
    end
    
    return false
end

-- DDoS防护中间件
function ddos_protection.middleware()
    local ip_control = require "ip_control"
    local client_ip = ip_control.get_real_ip()
    
    -- 检查IP是否被封禁
    local is_banned, ban_info = ddos_protection.is_ip_banned(client_ip)
    if is_banned then
        ngx.log(ngx.WARN, "Blocked request from banned IP: ", client_ip)
        ngx.status = 403
        ngx.say("Access denied: IP temporarily blocked")
        ngx.exit(403)
    end
    
    -- 记录请求
    ddos_protection.record_request()
    
    -- 检测攻击
    if ddos_protection.detect_attack() then
        -- 攻击已被处理(IP封禁或启用挑战模式)
        return
    end
    
    -- 检查是否处于挑战模式
    if ddos_protection.is_challenge_mode() then
        -- 检查是否已通过挑战
        local red = get_redis()
        if red then
            local pass_key = "ddos:challenge_passed:" .. client_ip
            local has_passed = red:get(pass_key)
            red:set_keepalive(10000, 100)
            
            if not has_passed or has_passed == ngx.null then
                -- 需要进行挑战验证
                if ngx.var.request_method == "POST" then
                    if ddos_protection.verify_challenge() then
                        -- 挑战成功,继续处理请求
                        return
                    else
                        -- 挑战失败
                        ddos_protection.javascript_challenge()
                    end
                else
                    -- 显示挑战页面
                    ddos_protection.javascript_challenge()
                end
            end
        end
    end
end

return ddos_protection

5. 安全配置示例

5.1 Nginx配置

# nginx.conf安全配置
http {
    # 隐藏Nginx版本信息
    server_tokens off;
    
    # 安全头设置
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'";
    
    # 限制请求大小
    client_max_body_size 10m;
    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    
    # 超时设置
    client_body_timeout 12;
    client_header_timeout 12;
    keepalive_timeout 15;
    send_timeout 10;
    
    # 共享内存配置
    lua_shared_dict rate_limit_global 10m;
    lua_shared_dict rate_limit_api 10m;
    lua_shared_dict rate_limit_user 10m;
    lua_shared_dict connection_limit 10m;
    lua_shared_dict stats 20m;
    lua_shared_dict locks 10m;
    
    # 初始化脚本
    init_by_lua_block {
        -- 加载安全模块
        require "resty.core"
        
        -- 初始化安全配置
        local security_config = {
            enable_ddos_protection = true,
            enable_rate_limiting = true,
            enable_ip_filtering = true,
            enable_geo_blocking = false
        }
        
        ngx.shared.stats:set("security_config", require("cjson").encode(security_config))
    }
    
    server {
        listen 443 ssl http2;
        server_name example.com;
        
        # SSL配置
        ssl_certificate /path/to/cert.pem;
        ssl_certificate_key /path/to/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
        
        # 安全检查
        access_by_lua_block {
            -- DDoS防护
            local ddos_protection = require "ddos_protection"
            ddos_protection.middleware()
            
            -- IP访问控制
            local ip_control = require "ip_control"
            ip_control.check_blacklist()
            
            -- 限流控制
            local rate_limiter = require "rate_limiter"
            rate_limiter.global_limit()
            rate_limiter.ip_limit()
        }
        
        # API路由
        location /api/ {
            access_by_lua_block {
                -- JWT认证
                local jwt_auth = require "jwt_auth"
                jwt_auth.authenticate()
                
                -- RBAC权限检查
                local rbac = require "rbac"
                rbac.middleware()
                
                -- 用户级限流
                local rate_limiter = require "rate_limiter"
                rate_limiter.user_limit()
            }
            
            # 代理到后端
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
        
        # 管理员路由
        location /admin/ {
            access_by_lua_block {
                -- IP白名单检查
                local ip_control = require "ip_control"
                ip_control.check_whitelist()
                
                -- JWT认证
                local jwt_auth = require "jwt_auth"
                jwt_auth.authenticate()
                
                -- 管理员权限检查
                jwt_auth.require_role("admin")
            }
            
            proxy_pass http://admin_backend;
        }
        
        # 认证路由
        location /auth/ {
            content_by_lua_block {
                local uri = ngx.var.uri
                
                if uri == "/auth/login" then
                    -- 处理登录
                    local login_handler = require "login_handler"
                    login_handler.handle_login()
                    
                elseif string.match(uri, "/auth/oauth/(.+)") then
                    -- OAuth认证
                    local provider = string.match(uri, "/auth/oauth/(.+)")
                    local oauth2 = require "oauth2"
                    oauth2.authenticate(provider)
                    
                else
                    ngx.status = 404
                    ngx.say("Not found")
                end
            }
        }
        
        # 错误处理
        log_by_lua_block {
            -- 记录错误响应
            if ngx.status >= 400 then
                local ddos_protection = require "ddos_protection"
                ddos_protection.record_error()
            end
        }
    }
    
    # HTTP重定向到HTTPS
    server {
        listen 80;
        server_name example.com;
        return 301 https://$server_name$request_uri;
    }
}

5.2 登录处理器

-- 登录处理模块
local login_handler = {}
local cjson = require "cjson"
local jwt_auth = require "jwt_auth"
local mysql = require "resty.mysql"

-- 用户验证
local function verify_user(username, password)
    -- 连接数据库
    local db, err = mysql:new()
    if not db then
        return nil, "Database connection failed: " .. err
    end
    
    db:set_timeout(1000)
    
    local ok, err = db:connect({
        host = "127.0.0.1",
        port = 3306,
        database = "auth_db",
        user = "auth_user",
        password = "auth_password",
        charset = "utf8",
        max_packet_size = 1024 * 1024
    })
    
    if not ok then
        return nil, "Database connection failed: " .. err
    end
    
    -- 查询用户
    local sql = "SELECT id, username, password_hash, salt, roles, status FROM users WHERE username = ? AND status = 'active'"
    local res, err = db:query(sql, username)
    
    if not res then
        db:close()
        return nil, "Database query failed: " .. err
    end
    
    if #res == 0 then
        db:close()
        return nil, "User not found or inactive"
    end
    
    local user = res[1]
    
    -- 验证密码
    local resty_sha256 = require "resty.sha256"
    local sha256 = resty_sha256:new()
    sha256:update(password .. user.salt)
    local password_hash = require("resty.string").to_hex(sha256:final())
    
    if password_hash ~= user.password_hash then
        db:close()
        return nil, "Invalid password"
    end
    
    db:close()
    
    return {
        user_id = user.id,
        username = user.username,
        roles = cjson.decode(user.roles or '[]')
    }
end

-- 处理登录请求
function login_handler.handle_login()
    if ngx.var.request_method ~= "POST" then
        ngx.status = 405
        ngx.say("Method not allowed")
        ngx.exit(405)
    end
    
    ngx.req.read_body()
    local body = ngx.req.get_body_data()
    
    if not body then
        ngx.status = 400
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "bad_request",
            message = "Request body required"
        }))
        ngx.exit(400)
    end
    
    local login_data = cjson.decode(body)
    local username = login_data.username
    local password = login_data.password
    
    if not username or not password then
        ngx.status = 400
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "bad_request",
            message = "Username and password required"
        }))
        ngx.exit(400)
    end
    
    -- 验证用户
    local user, err = verify_user(username, password)
    if not user then
        ngx.log(ngx.WARN, "Login failed for user ", username, ": ", err)
        ngx.status = 401
        ngx.header.content_type = "application/json"
        ngx.say(cjson.encode({
            error = "unauthorized",
            message = "Invalid credentials"
        }))
        ngx.exit(401)
    end
    
    -- 生成JWT令牌
    local token = jwt_auth.generate_token(user)
    
    ngx.log(ngx.INFO, "User ", username, " logged in successfully")
    
    ngx.header.content_type = "application/json"
    ngx.say(cjson.encode({
        success = true,
        token = token,
        user = {
            user_id = user.user_id,
            username = user.username,
            roles = user.roles
        }
    }))
end

return login_handler

6. 监控与日志

6.1 安全事件监控

-- 安全监控模块
local security_monitor = {}
local cjson = require "cjson"
local redis = require "resty.redis"

-- 安全事件类型
local event_types = {
    LOGIN_SUCCESS = "login_success",
    LOGIN_FAILURE = "login_failure",
    ACCESS_DENIED = "access_denied",
    RATE_LIMIT_EXCEEDED = "rate_limit_exceeded",
    IP_BANNED = "ip_banned",
    DDOS_DETECTED = "ddos_detected",
    SUSPICIOUS_ACTIVITY = "suspicious_activity"
}

-- 记录安全事件
function security_monitor.log_event(event_type, details)
    local event = {
        timestamp = ngx.time(),
        event_type = event_type,
        ip = require("ip_control").get_real_ip(),
        user_agent = ngx.var.http_user_agent,
        uri = ngx.var.uri,
        method = ngx.var.request_method,
        details = details or {}
    }
    
    -- 记录到Nginx日志
    ngx.log(ngx.WARN, "SECURITY_EVENT: ", cjson.encode(event))
    
    -- 发送到Redis队列用于实时处理
    local red = redis:new()
    red:set_timeout(1000)
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if ok then
        red:lpush("security_events", cjson.encode(event))
        red:set_keepalive(10000, 100)
    end
end

-- 检测异常行为
function security_monitor.detect_anomaly()
    local red = redis:new()
    red:set_timeout(1000)
    
    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        return
    end
    
    local client_ip = require("ip_control").get_real_ip()
    local current_time = ngx.time()
    local window = 300  -- 5分钟窗口
    
    -- 检查短时间内的失败登录次数
    local login_failures = red:get("login_failures:" .. client_ip) or 0
    if tonumber(login_failures) > 5 then
        security_monitor.log_event(event_types.SUSPICIOUS_ACTIVITY, {
            reason = "multiple_login_failures",
            count = login_failures
        })
    end
    
    red:set_keepalive(10000, 100)
end

return security_monitor

6.2 安全配置最佳实践

  1. 定期更新

    • 及时更新OpenResty和依赖库
    • 定期审查安全配置
    • 监控安全漏洞公告
  2. 密钥管理

    • 使用强随机密钥
    • 定期轮换密钥
    • 安全存储敏感信息
  3. 监控告警

    • 实时监控安全事件
    • 设置异常告警
    • 建立应急响应机制
  4. 访问控制

    • 最小权限原则
    • 定期审查权限
    • 多因素认证

总结

OpenResty提供了强大的安全功能,通过合理配置和使用各种安全模块,可以构建一个安全可靠的Web应用防护体系。关键要点包括:

  • 多层防护:结合多种安全机制
  • 实时监控:及时发现和响应安全威胁
  • 灵活配置:根据业务需求调整安全策略
  • 持续改进:定期评估和优化安全配置