3.8.0-Kong网关自定义插件-增强版CORS跨域插件

能力增加:
1、origins支持正则、不需要指定协议、不需要指定端口
例如:*.demo.com:*
2、增强安全性,不允许非指定的主机名访问(返回403),kong自带的插件也可以,但必须写一堆清单,支持正则后就方便多了

 

1、新建自定义插件目录

mkdir -p /usr/local/share/lua/5.1/kong/plugins/dynamic-cors/ && chown -R kong.kong /usr/local/share/lua/5.1/kong/plugins/dynamic-cors

 

2、新建Lua文件

init.lua

local DynamicCorsHandler = require("kong.plugins.dynamic-cors.handler")

return {
  name = "dynamic-cors",
  version = "1.0.0",
  handler = DynamicCorsHandler,
  schema = require("kong.plugins.dynamic-cors.schema"),
  priority = 900,
}

 

config.lua

return {
    no_consumer = true,
    name = "dynamic-cors",
    fields = {
        config = {
            type = "record",
            fields = {}
        }
    }
}

 

schema.lua

local typedefs = require "kong.db.schema.typedefs"

return {
    name = "dynamic-cors",
    fields = {
        { consumer = typedefs.no_consumer },
        { protocols = typedefs.protocols_http },
        { config = {
            type = "record",
            fields = {
                { origins = {
                    type = "array",
                    default = { "*" },
                    elements = { type = "string" },
                    description = "允许的域名列表,支持通配符如 *.example.com"
                }},
                { methods = {
                    type = "array",
                    default = { "*" },
                    elements = { type = "string" },
                    description = "允许的 HTTP 方法"
                }},
                { headers = {
                    type = "array",
                    default = { "*" },
                    elements = { type = "string" },
                    description = "允许的请求头"
                }},
                { exposed_headers = {
                    type = "array",
                    default = {},
                    elements = { type = "string" },
                    description = "允许客户端访问的响应头"
                }},
                { credentials = {
                    type = "boolean",
                    default = true,
                    description = "是否允许发送 Cookie/认证信息"
                }},
                { max_age = {
                    type = "integer",
                    default = 3600,
                    description = "预检请求缓存时间(秒)"
                }},
                { allow_wildcard_subdomains = {
                    type = "boolean",
                    default = true,
                    description = "是否启用通配符子域名匹配"
                }},
                { origin_regex_patterns = {
                    type = "array",
                    default = {},
                    elements = { type = "string" },
                    description = "正则表达式模式列表"
                }},
                { debug = {
                    type = "boolean",
                    default = false,
                    description = "是否启用调试日志"
                }}
            }
        }}
    }
}

 

handler.lua

local DynamicCorsHandler = { PRIORITY = 999, VERSION = "1.5" }

local function safe_wildcard_match(origin, pattern)
    -- 1. 完全匹配
    if origin == pattern then
        return true
    end
    
    -- 2. 全局通配符
    if pattern == "*" then
        return true
    end
    
    -- 3. 提取 Origin 的主机名
    local origin_host
    if origin:find("://") then
        local rest = origin:match("://(.+)$")
        if rest then
            origin_host = rest:match("^([^:]+)")
        end
    else
        origin_host = origin:match("^([^:]+)")
    end
    
    if not origin_host then
        return false
    end
    
    -- 4. 处理带端口通配符的模式
    if pattern:find(":*$") then
        local host_pattern = pattern:sub(1, -3)
        
        if host_pattern == "*" then
            return true
        end
        
        -- 安全匹配逻辑:必须以 .domain 结尾
        if host_pattern:sub(1, 2) == "*." then
            local domain = host_pattern:sub(3)
            if origin_host == domain or origin_host:sub(-#domain - 1) == "." .. domain then
                if origin_host ~= domain then
                    return true
                end
            end
        end
        
        if origin_host == host_pattern then
            return true
        end
    end
    
    -- 5. 处理不带端口通配符的模式
    if pattern:sub(1, 2) == "*." then
        local domain = pattern:sub(3)
        if origin_host == domain or origin_host:sub(-#domain - 1) == "." .. domain then
            if origin_host ~= domain then
                return true
            end
        end
    end
    
    -- 6. 直接主机名匹配
    if origin_host == pattern then
        return true
    end
    
    return false
end

function DynamicCorsHandler:access(conf)
    local origin = kong.request.get_header("Origin")
    
    if origin then
        if conf.origins and #conf.origins > 0 then
            local found_match = false
            
            for _, pattern in ipairs(conf.origins) do
                if safe_wildcard_match(origin, pattern) then
                    found_match = true
                    break
                end
            end
            
            if not found_match then
                return kong.response.exit(403, "Cross-Origin Request Blocked")
            end
        end
    end
end

function DynamicCorsHandler:header_filter(conf)
    local origin = kong.request.get_header("Origin")
    
    if origin then
        local should_set_cors = false
        
        if not conf.origins or #conf.origins == 0 then
            should_set_cors = true
        else
            for _, pattern in ipairs(conf.origins) do
                if safe_wildcard_match(origin, pattern) then
                    should_set_cors = true
                    break
                end
            end
        end
        
        if should_set_cors then
            -- 基础 CORS 头
            kong.response.set_header("Access-Control-Allow-Origin", origin)
            kong.response.set_header("Vary", "Origin")
            
            if conf.credentials then
                kong.response.set_header("Access-Control-Allow-Credentials", "true")
            end
            
            if conf.exposed_headers and #conf.exposed_headers > 0 then
                local exposed_str
                if type(conf.exposed_headers) == "table" then
                    exposed_str = table.concat(conf.exposed_headers, ", ")
                else
                    exposed_str = conf.exposed_headers
                end
                kong.response.set_header("Access-Control-Expose-Headers", exposed_str)
            end
            
            -- 获取当前请求方法
            local request_method = kong.request.get_method()
            
            -- 如果是 OPTIONS 请求,设置预检头
            if request_method == "OPTIONS" then
                local methods
                if conf.methods then
                    if type(conf.methods) == "table" and #conf.methods > 0 then
                        methods = table.concat(conf.methods, ", ")
                    elseif conf.methods ~= "" then
                        methods = conf.methods
                    else
                        methods = "*"
                    end
                else
                    methods = "*"
                end
                kong.response.set_header("Access-Control-Allow-Methods", methods)
                
                local headers
                if conf.headers then
                    if type(conf.headers) == "table" and #conf.headers > 0 then
                        headers = table.concat(conf.headers, ", ")
                    elseif conf.headers ~= "" then
                        headers = conf.headers
                    else
                        headers = "*"
                    end
                else
                    headers = "*"
                end
                kong.response.set_header("Access-Control-Allow-Headers", headers)
                
                -- 缓存时间
                kong.response.set_header("Access-Control-Max-Age", conf.max_age or "86400")
            end
            
            -- 删除敏感头信息
            if conf.remove_headers and #conf.remove_headers > 0 then
                for _, header in ipairs(conf.remove_headers) do
                    kong.response.clear_header(header)
                end
            end
        end
    end
end

return DynamicCorsHandler

 

dynamic-cors-1.0.0-1.rockspec

package = "dynamic-cors"
version = "1.0.0-1"
source = {
    url = "git://localhost/dynamic-cors"
}
description = {
    summary = "Dynamic CORS Plugin for Kong with wildcard support",
    detailed = [[
        A Kong plugin that provides flexible CORS support with wildcard domains,
        regex patterns, and dynamic origin reflection.
    ]],
    homepage = "https://github.com/your-org/kong-dynamic-cors",
    license = "MIT"
}
dependencies = {
    "lua >= 5.1",
    "kong >= 3.8.0"
}
build = {
    type = "builtin",
    modules = {
        ["kong.plugins.dynamic-cors.handler"] = "handler.lua",
        ["kong.plugins.dynamic-cors.schema"] = "schema.lua"
    }
}

 

3、在配置文件里将自定义插件加上

/etc/kong/kong.conf

plugins = bundled,dynamic-cors

 

4、可以通过KongA可视化配置,也可以用命令

image

 

image

 

posted @ 2026-01-19 16:37  吃吃吃大王  阅读(0)  评论(0)    收藏  举报