环境搭建

安装配置OpenResty, 基本操作指令

  1. 源码下载
    OpenResty

  2. 依赖安装

    yum -y install readline-devel pcre-devel openssl-devel perl lua lua-devel luajit luajit-devel openssl openssl-devel
    
  3. 编译安装

    ./configure --prefix=/opt/openresty --with-luajit --without-http_redis2_module --with-http_iconv_module
    gmake && gmake install
    
  4. 设置环境变量

    vim /etc/profile
    # 末尾添加
    export PATH=$PATH:/opt/openresty/nginx/sbin
    source /etc/profile
    # 验证安装
    nginx -V
    
  5. 创建工作目录

    # 在工作目录下
    mkdir openresty openresty/conf openresty/logs
    # 创建conf文件
    touch openresty/conf/nginx.conf
    
    # nginx.conf
    worker_processes 1;         # nginx worker 数量
    error_log logs/error.log;   # 指定错误日志文件路径
    
    events {
        worker_connections 1024;    # 最大连接数
    }
    
    http {
        server {
            listen 1080;    #监听端口
            location / {
                default_type text/html;
                content_by_lua_block {
                    ngx.say("Hello Lua.")
                }
            }
        }
    }
    
  6. 操作命令

    # 在工作目录下
    # 启动
    nginx -p ./openresty
    # 重启, 每次修改配置文件后都需要重启nginx
    nginx -s reload -p ./openresty
    # 停止
    nginx -s stop
    
  7. 测试

    curl http://ip:port -i
    # 正确返回应为 HTTP/1.1 200 OK
    # 浏览器测试直接打开 http://ip:port 
    

C Module 代码及编译

编写一个C Module, 输入参数为HTTP POST 请求Json, 经cJSON 解析后, 返回处理后的数据.
将其编译为静态库, 供Nginx 调用

  1. 处理代码 clogin.cpp

    // clogin.cpp
    #include <stdio.h>
    #include "cJSON.h"
    
    extern "C" {
        extern int login_request_received(const char *content, char *token){
            if(content==NULL){
                return -1;
            }
    
            cJSON *content_json = NULL;
            cJSON *username_json = NULL;
            cJSON *password_json = NULL;
    
            content_json = cJSON_Parse(content);
            if(content_json==NULL){
                return -1;
            }
    
            username_json = cJSON_GetObjectItem(content_json, "username");
            if(username_json==NULL){
                return -1;
            }
    
            password_json = cJSON_GetObjectItem(content_json, "password");
            if(password_json==NULL){
                return -1;
            }
    
            sprintf(token, "%s:%d", username_json->valuestring, password_json->valueint);
    
            return 0;
        }
    }
    
  2. 目录结构及依赖文件

    .
    ├── cJSON.h
    ├── clogin.cpp
    ├── libxmsdk.a
    
  3. 编译

    g++ -shared -fPIC clogin.cpp -o libclogin.so -I./ -L./ -lxmsdk -Xlinker --unresolved-symbols=ignore-in-shared-libs
    
  4. 配置

    mkdir openresty/lualib
    cp libclogin.so openresty/lualib
    

C Module 配置及Lua 代码编写

配置OpenResty, 编写Lua 脚本获取HTTP POST 请求body Json, 调用C Module 接口, 处理接收数据, 将结果返回给请求端

  1. ngxin.conf 配置

    # nginx.conf
    ...
    http {
        server {
            listen 1080;    #监听端口
            location /login {
                content_by_lua_file api/login.lua;                                         
            }
        }
    }
    
  2. login.lua 处理代码

    mkdir openresty/api
    touch openresty/api/login.lua
    
    -- login.lua
    
    local ffi = require("ffi")
    -- 最好直接写绝对路径, 避免出错
    local cloginffi = ffi.load("/home/louis/openresty/lualib/libclogin.so")
    
    ffi.cdef[[
    int login_request_received(const char *content, char *token);
    ]]
    
    -- table 序列化为string
    local function serialize(obj)
        local lua = ''
        local t = type(obj)
        if t == "number" then
            lua = lua .. obj
        elseif t == "boolean" then
            lua = lua .. tostring(obj)
        elseif t == "string" then
            lua = lua .. string.format('%s', obj)
        elseif t == "table" then
            for k, v in pairs(obj) do
                lua = lua .. serialize(k) .. serialize(v)
            end
        elseif t == "nil" then
            return nil
        else
            error("can not serialize a " .. t .. " type.")
        end
    
        return lua
    end
    
    ngx.req.read_body()
    -- 获取到的body 为table 格式
    local data = ngx.req.get_post_args()
    local data_str = serialize(data)
    
    local token = ffi.new("char[128]")
    local re = cloginffi.login_request_received(data_str, token)
    -- TODO: 待补充异常处理
    -- 转为lua string
    local token_str = ffi.string(token)
    
    ngx.say(token_str)
    
  3. 测试

    curl -v -i -H "Accept: application/json" -H "Content-Type:application/json" -X POST -d '{"username":"tianlu","password":123456}' http://123.59.27.192:1080/login
    

添加SSL 支持

生成自签名证书, 配置OpenResty 支持SSL 双向验证

  1. CA 生成

    mkdir ssl
    # 执行CA 生成脚本
    ./generate_ca.sh
    
    #!/bin/bash
    
    COUNTRY_NAME="CN"
    STATE_OR_PROVINCE_NAME="ZHEJIANG"
    LOCALITY_NAME="HANGZHOU"
    ORIGANIZATION_NAME="XIONGMAITECH"
    ORIGANIZATIONAL_UNIT_NAME="YUNPINGTAI"
    SERVER_COMMON_NAME="123.59.27.192"       # 部署OpenResty 服务器IP
    CLIENT_COMMON_NAME="10.2.5.51"           # 请求客户端机器IP
    
    # Generate the openssl configuration files.
    cat > ca_cert.conf << EOF
    [ req ]
    distinguished_name     = req_distinguished_name     # 可识别字段名DN, 引用req_distinguished_name 段设置
    prompt                 = no     # 设置为no, 不提示输入DN, 而是从配置文件中读取, 此是需要同时设置DN默认值
    
    [ req_distinguished_name ]
     C                       = $COUNTRY_NAME
     ST                      = $STATE_OR_PROVINCE_NAME
     L                       = $LOCALITY_NAME
     O                       = $ORIGANIZATION_NAME root Certificate Authority
     OU                      = $ORIGANIZATIONAL_UNIT_NAME
    EOF
    
    cat > server_cert.conf << EOF
    [ req ]
    distinguished_name     = req_distinguished_name
    prompt                 = no
    
    [ req_distinguished_name ]
     C                       = $COUNTRY_NAME
     ST                      = $STATE_OR_PROVINCE_NAME
     L                       = $LOCALITY_NAME
     O                       = $ORIGANIZATION_NAME Server Certificate
     OU                      = $ORIGANIZATIONAL_UNIT_NAME
     CN                      = $SERVER_COMMON_NAME   # 必须和网站本身一致
    EOF
    
    cat > client_cert.conf << EOF
    [ req ]
    distinguished_name     = req_distinguished_name
    prompt                 = no
    
    [ req_distinguished_name ]
     C                       = $COUNTRY_NAME
     ST                      = $STATE_OR_PROVINCE_NAME
     L                       = $LOCALITY_NAME
     O                       = $ORIGANIZATION_NAME Client Certificate
     OU                      = $ORIGANIZATIONAL_UNIT_NAME
     CN                      = $CLIENT_COMMON_NAME   # 必须和客户端本身一致
    EOF
    
    # 创建目录
    if [ ! -d "./ca" ];then
        mkdir -p ./ca
    else
        rm -f ./ca/*
    fi
    
    if [ ! -d "./server" ];then
        mkdir -p ./server
    else
        rm -f ./server/*
    fi
    
    if [ ! -d "./client" ];then
        mkdir -p ./client
    else
        rm -f ./client/*
    fi
    
    if [ ! -d "./certDER" ];then
        mkdir -p ./certDER
    else
        rm -f ./certDER/*
    fi
    
    # private key generation
    openssl genrsa -out ./ca/ca.key 1024
    openssl genrsa -out ./server/server.key 1024
    openssl genrsa -out ./client/client.key 1024
    
    # cert requests
    openssl req -out ./ca/ca.req -key ./ca/ca.key -new -config ./ca_cert.conf
    openssl req -out ./server/server.req -key ./server/server.key -new -config ./server_cert.conf
    openssl req -out ./client/client.req -key ./client/client.key -new  -config ./client_cert.conf
    
    # generate the actual certs.
    # 证书有效时间可以自己修改
    openssl x509 -req -in ./ca/ca.req -out ./ca/ca.crt -sha1 -days 3650 -signkey ./ca/ca.key
    openssl x509 -req -in ./server/server.req -out ./server/server.crt -sha1 -CAcreateserial -days 365 -CA ./ca/ca.crt -CAkey ./ca/ca.key
    openssl x509 -req -in ./client/client.req -out ./client/client.crt -sha1 -CAcreateserial -days 30 -CA ./ca/ca.crt -CAkey ./ca/ca.key
    
    #openssl x509 -in ./ca/ca.crt -outform DER -out ./certDER/ca.der
    #openssl x509 -in ./server/server.crt -outform DER -out ./certDER/server.der
    #openssl x509 -in ./client/client.crt -outform DER -out ./certDER/client.der
    
    rm ./*conf
    rm ./ca/*req 
    rm ./certDER/*req
    rm ./client/*req
    rm ./server/*req   
    
  2. nginx.conf 配置SSL支持

    # nginx.conf
    ...
    http {
        server {
            listen 1080;    # 监听端口
            ssl on;         # SSL 开启
              
            ssl_certificate      /home/louis/ssl/server/server.crt;  # 服务器证书
            ssl_certificate_key  /home/louis/ssl/server/server.key;  # 服务器私钥           
            ssl_client_certificate  /home/louis/ssl/ca/ca.crt;       # ca 证书用于验证客户端
            ssl_verify_client on;                                   # 开启对客户端的验证
            ssl_session_timeout 5m;                                 # session有效期
            ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2;        # 支持的SSL/TLS 协议版本
            ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;  # 加密算法
            ssl_prefer_server_ciphers on;                           # 依赖SSLv3和TLSv1协议的服务器密码将优先于客户端密码
    
            location /login {
                content_by_lua_file api/login.lua;                                         
            }
        }
    }
    
  3. 测试

    # 测试前先reload nginx
    # 注意证书路径, ca证书及client 证书来自第一步生成
    # 由于开启双向验证, 客户端IP也必须与实际填写的一致
    curl -v -i --cacert ./ca/ca.crt --cert ./client/client.crt --key ./client/client.key -H "Accept: application/json" -H "Content-Type:application/json" -X POST -d '{"username":"tianlu","password":123456}' https://123.59.27.192:1080/login