• apisix插件开发非对称性加签验签


    需求

    对所有的请求, 进行私钥加签, 公钥验签
    不对响应进行处理

    • 首先, 统一处理请求, 那就需要网关了
    • 选好网关后, 约定好加签验签的规则
    • 公钥私钥的格式, 代码中公私钥变量值的来源
    • 签名放在哪里, 时间戳放在哪里

    方案

    这里使用的是apisix网关
    使用apisix默认语言lua, 以插件的方式完成开发

    签名约定

    长度: 2048bit
    格式: PKCS#8
    内容约定: 公私钥begin和end
    生成地址: http://www.metools.info/code/c80.html
    举例:

    # 公钥
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Rkxr3ksqneR/pEZ95w/
    hPEbpg59tKzC1flDoNOn7m0yYdXubo3gwOFx+7urkyZ4MY6qB6qFDuRclBO8fy6n
    zs5/27+31XY5KmB2nfRaaCQHfV0iq46QFRcLXiUIPR0TeXrijsEA04LcX8a/jstR
    FH2JdL83qG9heSq7kmwdtP+U/9qu2XW0IiCf3h15DPfhxZq++7NNcOq89Fy19Uz/
    TQLTkqJ5XclVawPfwvEnbLTSIGv1zjell3aXBPgaMJ1cBV4Eoj5EWjnlyKTn5F/m
    Vmxwt++Z0+wqGNIY5NKkKyyyfdFddQmx+XOi6Z4MUfAlmLGjegnr5cmlUs74rkdo
    QwIDAQAB
    -----END PUBLIC KEY-----
    
    
    # 私钥
    -----BEGIN PRIVATE KEY-----
    MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDRGTGveSyqd5H+
    kRn3nD+E8RumDn20rMLV+UOg06fubTJh1e5ujeDA4XH7u6uTJngxjqoHqoUO5FyU
    E7x/LqfOzn/bv7fVdjkqYHad9FpoJAd9XSKrjpAVFwteJQg9HRN5euKOwQDTgtxf
    xr+Oy1EUfYl0vzeob2F5KruSbB20/5T/2q7ZdbQiIJ/eHXkM9+HFmr77s01w6rz0
    XLX1TP9NAtOSonldyVVrA9/C8SdstNIga/XON6WXdpcE+BownVwFXgSiPkRaOeXI
    pOfkX+ZWbHC375nT7CoY0hjk0qQrLLJ90V11CbH5c6LpngxR8CWYsaN6CevlyaVS
    zviuR2hDAgMBAAECggEBAI6iEftxvB84WzV8g2sdgVLNKaAXqHYzluBhHVm6p4YD
    pOeKCBAP5e2Mm7UtKnfBOSINAD0ke4lSCzjTUbSr/bobsKoU2HDbKVmX3bIXugfp
    89X3TywZnn1Ub+OzfTA7AkrOoXbhfw/I50zKBWeBl3hTvg0OVwglmicOGE9kQstw
    g5uUdkkDRQqK/BLJH34Zg+q2uaAveLSf4XYTIVKG+rIdryomLyUwoHEgH2KU7BoH
    XbDPtDTbdhCWCFVcy3xyMKdrTsqihUQG5f1vPxiViVlDN2d/AJtcQn90vvlGj8ec
    IbGINM+RQ07KQE7T5MEyTk+yimbUjdqdK2/VHxdKndkCgYEA/XpS/cgmuQ2NV7EB
    LLmH+FIixAjttA3go57KvmkXqCcAIaqG3pTZstrDC6gArb5b26gLgFPbyzAnETx2
    OOTn/blHFgD1YPz+sDJ9dTp3s7XClGMEW3G7IEhCd0o40XYpU3ZoWedCkVGbnWLy
    koXEAu1nKrYE3M6Wdjj5rWkm3McCgYEA0y3S24g28lfJSMvFhiOATQVYhG9GfAnS
    9WyQayegrOJPfY/1e2tLB+q+qH0cisB2l8nWtaUmCV8XuBCS3wnDx/yG3BaSV3Pz
    68D4IJxU7nv3H6FewbG4kQjT2n15IuSuM0Z1camzEY96+9Yj2nnwxzB7ljV41nMW
    CUK6ZzT4BKUCgYEAsNHlFN3LjWmTwKlcLWvbGvGJCQFFeEX5/4mk2sEK3KUgJVwE
    qz5gwrByQS5YEttozsjiBQn6mDol9pMb0UJ1Rvw5R3MxuQ+jRxxhgiZgHD/d1Y9h
    Gb0zkSh3HSnsismHuI4v6w8005R/HoJIvseLXZNoVVYV/EYslZnXKg3hKz8CgYA8
    DByX1cyh7jpK275HnRKfU/TOe4GURYrZxEvwXC1A23z03BlWRbTpBGPALwsNnRpb
    oMXPkq0VHxf0e6n3h6RG2lRSgoyMF2l1UMJ9K1avFUq4kL8L3of3nYX365OlS1cJ
    N3CvqCxFwwGaWFKLjf7b9Lo/hObeO405huLP8+zODQKBgQD7XL5XINl48XltSjoi
    Y2iyB8Ay9VVgshN0fQ/qZmxqdhrfogoIyG1ONK2P8mzdYijqh0VWu4qXXE/Gwg5e
    fYEH9mNfy5CYMp+XSSXtAfVvx0k2OyYqWJk31NKv+ls95TCwMnYcB4EX82+Cf7GK
    4LYEFbMjPBDNP01tSbDKdQ/uDA==
    -----END PRIVATE KEY-----
    

    lua语言的公私钥加签验签

    参考文档

    https://github.com/spacewander/lua-resty-rsa

    该依赖也可以加密, 解密; 也可生成公私钥, 具体参考github readme

    依赖文件

    https://github.com/spacewander/lua-resty-rsa/blob/master/lib/resty/rsa.lua

    使用方法

    项目克隆

    git clone https://github.com/spacewander/lua-resty-rsa.git
    git clone git@github.com:spacewander/lua-resty-rsa.git
    

    方法参考

    local resty_rsa = require "resty.rsa"
    
    -- 公钥
    local rsa_public_key = [[-----BEGIN PUBLIC KEY-----
    xxx
    -----END PUBLIC KEY-----]]
    
    -- 私钥
    local rsa_priv_key = [[-----BEGIN PRIVATE KEY-----
    xxx
    -----END PRIVATE KEY-----]]
    
    -- 算法
    local algorithm = "SHA256"
    
    local priv, err = resty_rsa:new({ private_key = rsa_priv_key, algorithm = algorithm })
    if not priv then
        ngx.say("new rsa err: ", err)
        return
    end
    
    -- 加签
    local str = "hello"
    local sig, err = priv:sign(str)
    if not sig then
        ngx.say("failed to sign:", err)
        return
    end
    
    local pub, err = resty_rsa:new({ public_key = rsa_public_key, algorithm = algorithm })
    if not pub then
        ngx.say("new rsa err: ", err)
        return
    end
    
    -- 验签
    local verify, err = pub:verify(str, sig)
    if not verify then
        ngx.say("verify err: ", err)
        return
    end
    

    注意

    该依赖生成的签名, 打印出来是乱码, 且有换行
    使用其自带的签名生成算法, 和padding内容, 都无效
    导致header传递签名的时候, 客户端需要序列化, 服务端需要反序列化
    解决参考: https://www.cnblogs.com/loseself/p/16184360.html

    image

    local priv, err = resty_rsa:new({
        private_key = rsa_priv_key,
        padding = resty_rsa.PADDING.RSA_PKCS1_PADDING,
        algorithm = algorithm,
    }) 
    

    插件开发

    参考文档

    插件开发参考
    https://www.cnblogs.com/loseself/p/16151876.html

    公私钥的来源与传递参考
    https://www.cnblogs.com/loseself/p/16157785.html

    客户端-加签内容

    请求的uri, get入参, 请求体, 请求方法
    时间戳, 签名放在header里

    -- 获取加签验签的内容
    local function get_signing_string(time, ctx)
        -- 请求方发, 请求头, url参数, 请求体
        local request_method = ngx.req.get_method()
        local query_params = ngx.req.get_uri_args()
        local signing_string_items = {
            ctx.var.uri,
            request_method,
            query_params_string = core.json.encode(query_params),
            time
        }
    
        local body = core.request.get_body()
        if body then
            signing_string_items.body = body
        end
    
        local signing_string = core.table.concat(signing_string_items, "\n") .. "\n"
        return signing_string
    end
    

    客户端-签名, 放入header

    注意: lua依赖的方法, 要放在该方法上面
    HEADER_KEY_SIGNATURE 相关的大写变量是常量, 可以自己补充, 同下

    -- 请求, 租户私钥加签, 在rewrite阶段即可
    function _M.rewrite(conf, ctx)
        local time = ngx_time()
        local signing_string = get_signing_string(time, ctx)
        -- 获取私钥的方式, 自己定义
        local private_key = get_private_key()
        local signature, err = generate_signature(private_key, signing_string)
        if not signature then
            return HTTP_ERROR_CODE, { msg = "请求加签失败, 错误信息: " .. err }
        end
    
        -- 签名有换行, 乱码, 等. 
        -- 传递时为防止被转义, 需要序列化, 服务端需要反序列化
        local json_signature = core.json.encode(signature)
    
        -- 赋值header
        core.request.set_header(ctx, HEADER_KEY_SIGNATURE, json_signature)
        core.request.set_header(ctx, HEADER_KEY_SIGNATURE_TIME, time)
    end
    

    服务端-验签

    -- 公钥验签
    local function validate(ctx, rsa_public_key)
        local time = core.request.header(ctx, HEADER_KEY_SIGNATURE_TIME)
        local signature = core.request.header(ctx, HEADER_KEY_SIGNATURE)
        if not time or not signature then
            return nil, "请求头时间或者签名不存在"
        end
    
        -- 防重放攻击
        local diff = abs(ngx_time() - time)
        if diff > 60 then
            return nil, "签名超时, 请重新生成签名"
        end
    
        -- 按照约定的序列化规则, 反序列化签名
        local decode_signature, err3 = core.json.decode(signature)
        if err3 then
            return nil, "签名约定反序列化失败, " .. err3
        end
    
        if type(decode_signature) ~= "string" then
            return nil, "签名数据类型错误"
        end
    
        local publicObject, err = resty_rsa:new({ public_key = rsa_public_key, algorithm = ALGORITHM })
        if not publicObject then
            return nil, err
        end
    
        local signing_string = get_signing_string(time, ctx)
        local verify, err2 = publicObject:verify(signing_string, decode_signature)
        return verify, err2
    end
    -- 请求, 租户公钥验签, 在rewrite阶段即可
    function _M.rewrite(conf, ctx)
        local verify, err = validate(ctx, get_tenant_public_key(ctx))
        if not verify then
            return HTTP_VALIDATE_ERROR_CODE, { msg = "请求验签失败, 错误信息: " .. err }
        end
    end
    

    优化

    参考插件可以参考apisix提供的对称性加密插件hmac-auth
    该插件除了加密方式外, 其他思想都是相同的
    因为现在是简单实现, 具体优化的地方, 性能的优化可以根据需求, 然后参考, 来进行操作
    https://apisix.apache.org/zh/docs/apisix/plugins/hmac-auth

  • 相关阅读:
    CLBZDQ
    CF1559D 题解
    DP 的凸优化
    正睿暑期集训7B
    基于 TiSpark 的海量数据批量处理技术
    PowerDesigner16.5下载和安装教程
    使用TiDB MPP
    使用 TiDB 构建实时应用
    oracle转mysql数据库
    kafka-jdbc-connector-sink实现kafka中的数据同步到mysql
  • 原文地址:https://www.cnblogs.com/loseself/p/16184372.html
Copyright © 2020-2023  润新知