在之前的文章《编写 Nginx 模块进行 RSA 加解密》中,我提到了如何编写 Nginx 模块,并借助 Nginx 实现相对高性能的加解密。正巧 Nginx 新版本发布,初步具备了原生“RSA加解密”能力。

那么,就来换一种更轻量的方式进行实现之前提到的功能吧。

写在前面

随着 Nginx 版本来到了 1.21.4 ,NJS 也升级到了 0.7 版本。这个版本可以说是具有突破意义的版本,因为这个版本的 NJS 添加了符合 W3C 的标准的 WebCrypto API

这意味着,以往需要单独起一套服务来说接口加密鉴权的时代或许可以过去了。

官方实现这个功能主要是通过添加 njs_webcrypto.c 加解密模块,引入 OpenSSL 的部分能力。如果你的需求包含了针对指定的 RSA 密钥(带密码)的加解密,那么目前 NJS 还做不到。不过,你可以修改上面的代码,添加我在《编写 Nginx 模块进行 RSA 加解密》一文中,提到的“计算部分”的代码实现:将 PEM_read_bio_RSAPrivateKey 携带密码的部分添加进去,并对 NJS 做一些函数绑定,最后记得清理干净 RSA 相关引用就好了。

好在在多数情况下,考虑到调用性能,针对业务接口进行加解密,不太倾向使用添加密码的密钥。

接下来,我将介绍如何使用 Nginx NJS 的这个新能力,一步步的实现一个能够根据业务接口内容,进行 RSA 自动加解密的接口服务。

使用浏览器生成 RSA 证书

你没有看错小标题,这次我们要使用浏览器而不是“传统命令行中OpenSSL”来生成我们的证书。

这里主要会用到两个 API:

文档枯燥,这里直接划出重点。在生成算法中,本文采用 WEB Crypto API 唯一支持的非对称加密算法 RSA-OAEP,在导出生成证书时,需要根据密钥类型,针对性的选择对应的导出格式。

从浏览器中生成和导出的 RSA 密钥对

为了方便我的读者玩耍,我写了一段简单的 JavaScript 脚本,将内容复制粘贴到你的浏览器控制台里(推荐 Chrome ),然后执行即可。不出意外,你的浏览器将会自动下载到两个名为 “rsa.pub”和“rsa.key”文件,我们稍后会使用。

(async () => {
  const ab2str = (buffer) => String.fromCharCode.apply(null, new Uint8Array(buffer));
  const saveFile = async (files) => {
    Object.keys(files).forEach(file => {
      const blob = new Blob([files[file]], { type: 'text/plain' });
      with (document.createElement('a')) { download = file; href = URL.createObjectURL(blob); click(); }
      URL.revokeObjectURL(blob);
    });
  }
  const exportKey = (content) => new Promise(async (resolve) => { await crypto.subtle.exportKey(content.type === "private" ? "pkcs8" : "spki", content).then((data) => resolve(`-----BEGIN ${content.type.toUpperCase()} KEY-----\n${btoa(ab2str(data))}\n-----END ${content.type.toUpperCase()} KEY-----`)); });
  const { privateKey, publicKey } = await crypto.subtle.generateKey({ name: "RSA-OAEP", modulusLength: 4096, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, true, ["encrypt", "decrypt"])
  saveFile({ "rsa.key": await exportKey(privateKey), "rsa.pub": await exportKey(publicKey) });
})();

使用 NJS 进行 RSA 加解密

虽然 Nginx 和 NJS 官方文档中,还未提及新添加的 WEB Crypto API 如何使用,但是我们可以从代码仓库中最新的测试用例中看到接口的用法

我们参考之前的文章《使用 Docker 和 Nginx NJS 实现 API 聚合服务(前篇)》中“使用 NJS 编写 Nginx 基础接口”的代码为基础,先写一个“糙一些”的版本出来,体验下使用 NJS 进行 Nginx 原生 RSA 加解密:

const fs = require('fs');
if (typeof crypto == 'undefined') {
  crypto = require('crypto').webcrypto;
}

function pem_to_der(pem, type) {
  const pemJoined = pem.toString().split('\n').join('');
  const pemHeader = `-----BEGIN ${type} KEY-----`;
  const pemFooter = `-----END ${type} KEY-----`;
  const pemContents = pemJoined.substring(pemHeader.length, pemJoined.length - pemFooter.length);
  return Buffer.from(pemContents, 'base64');
}

const rsaKeys = {
  public: fs.readFileSync(`/etc/nginx/script/rsa.pub`),
  private: fs.readFileSync(`/etc/nginx/script/rsa.key`)
}

async function simple(req) {

  const spki = await crypto.subtle.importKey("spki", pem_to_der(rsaKeys.public, "PUBLIC"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
  const pkcs8 = await crypto.subtle.importKey("pkcs8", pem_to_der(rsaKeys.private, "PRIVATE"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);

  let originText = "假设这是需要加密的内容,by soulteary";

  let enc = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, spki, originText);
  let decode = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, pkcs8, enc);

  req.headersOut["Content-Type"] = "text/html;charset=UTF-8";
  req.return(200, [
    '<h2>原始内容</h2>',
    `<code>${originText}</code>`,
    '<h2>加密后的内容</h2>',
    `<code>${Buffer.from(enc)}</code>`,
    '<h2>解密后的内容</h2>',
    `<code>${Buffer.from(decode)}</code>`,
  ].join(''));
}

export default { simple };

上面的代码定义了一个简单的接口“simple”,用于加载我们刚刚生成的 RSA Keys,然后对一段指定的内容(originText)进行加密再解密。将上面的内容保存为 app.js,我们继续编写一段简单的 Nginx 配置(nginx.conf):

load_module modules/ngx_http_js_module.so;

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events { worker_connections 1024; }

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    js_import app from script/app.js;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    keepalive_timeout 65;
    gzip on;

    server {
        listen 80;
        server_name localhost;

        charset utf-8;
        gzip on;

        location / {
            js_content app.simple;
        }
    }
}

为了使用简单,这里同样给出一份容器配置(docker-compose.yml):

version: '3'

services:

  nginx-rsa-demo:
    image: nginx:1.21.4-alpine
    ports:
      - 8080:80
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./scripts:/etc/nginx/script

使用 docker-compose up 启动容器,然后在浏览器中访问 localhost:8080,可以看到下面的内容。

使用 Nginx NJS 针对内容进行 RSA 加解密

顺便看一下响应时间,在笔记本的容器里大概十来ms,如果放到生产环境,加上一些优化,控制在个位数里问题不大。

接口响应时间

好了,能力验证到此就结束了。我们来稍加改造和优化,实现网关产品中的全自动的 RSA 加解密功能。

构建具备 RSA 加解密能力的网关

下面具体实战一下,如何使用 Nginx 的 NJS 针对请求进行加解密。先来编写 Nginx 配置部分。

调整 Nginx 配置使用的 NJS 导出函数

考虑到调试方便,我们将“入口点”(接口)拆分为三个,你可以根据实际使用场景进行调整,比如在入口处添加 IP 访问限制、额外的身份验证功能,或者取消“统一的入口”,直接使用两个主要的加解密接口为程序“入口点”:

server {
    listen 80;
    server_name localhost;

    charset utf-8;
    gzip on;

    location / {
        js_content app.entrypoint;
    }

    location /api/encrypt {
        js_content app.encrypt;
    }

    location /api/decrypt {
        js_content app.decrypt;
    }
}

完成了 Nginx 配置的编写后,就可以开始正餐了:编写 NJS 程序。

调整 NJS 程序:调整导出函数

Nginx 配置修改之后,同样的, NJS 中的导出函数也需要进行调整:

export default { encrypt, decrypt, entrypoint };

修改完毕导出函数后,我们依次来实现三个接口函数的功能。

实现 NJS 程序:默认入口函数

因为目前 NJS 的开发调试还处于非常不方便的状态,所以我们先来编写入口函数,以方便调试过程(app.js):

function debug(req) {
  req.headersOut["Content-Type"] = "text/html;charset=UTF-8";
  req.return(200, JSON.stringify(req, null, 4));
}

function encrypt(req) {
  debug(req)
}

function decrypt(req) {
  debug(req)
}

function entrypoint(r) {
  r.headersOut["Content-Type"] = "text/html;charset=UTF-8";
  switch (r.method) {
    case 'GET':
      return r.return(200, [
        '<form action="/" method="post">',
        '<input name="data" value=""/>',
        '<input type="radio" name="action" id="encrypt" value="encrypt" checked="checked"/><label for="encrypt">Encrypt</label>',
        '<input type="radio" name="action" id="decrypt" value="decrypt"/><label for="decrypt">Decrypt</label>',
        '<button type="submit">Submit</button>',
        '</form>'
      ].join('<br>'));
    case 'POST':
      var body = r.requestBody;
      if (r.headersIn['Content-Type'] != 'application/x-www-form-urlencoded' || !body.length) {
        r.return(401, "Unsupported method\n");
      }

      var params = body.trim().split('&').reduce(function (prev, item) {
        var tmp = item.split('=');
        var key = decodeURIComponent(tmp[0]).trim();
        var val = decodeURIComponent(tmp[1]).trim();
        if (key === 'data' || key === 'action') {
          if (val) {
            prev[key] = val;
          }
        }
        return prev;
      }, {});

      if (!params.action || (params.action != 'encrypt' && params.action != 'decrypt')) {
        return r.return(400, 'Invalid Params: `action`.');
      }

      if (!params.data) {
        return r.return(400, 'Invalid Params: `data`.');
      }

      function response_cb(res) {
        r.return(res.status, res.responseBody);
      }

      return r.subrequest(`/api/${params.action}`, { method: 'POST' }, response_cb)
    default:
      return r.return(400, "Unsupported method\n");
  }
}

export default { encrypt, decrypt, entrypoint };

上面60来行代码中,我们实现了哪些功能呢?

  • 一个简单的 Web 表单界面,用于接收我们调试开发过程中的“加解密动作”、“需要加解密的数据”。
  • 根据我们选择的动作,自动进行“加解密”操作,并返回具体加解密接口的处理结果。
  • 简单 Mock 了加解密接口,目前实际调用一个名为 debug 的函数打印我们的提交内容。

使用浏览器访问界面,能够看到这个简单的提交界面:

使用 NJS 制作的简单调试页面

在调试表单里的文本框中随便写一点内容,进行提交,可以看到函数运行符合预期,提交内容被正确的打印了出来:

函数运行符合预期

接着,我们来实现 NJS 的 RSA 加密函数。

实现 NJS 程序:RSA 加密函数

参考前文,稍作调整,不难实现这个加密函数,大概五行左右就够了。

async function encrypt(req) {
  const spki = await crypto.subtle.importKey("spki", pem_to_der(rsaKeys.public, "PUBLIC"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
  const result = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, spki, req.requestText);
  req.return(200, Buffer.from(result));
}

再次运行 Nginx ,提交内容,可以看到数据已经被顺利的进行了 RSA 加密。

NJS RSA 加密函数默认输出

因为 RSA 默认加密后的内容不具备可读性,所以一般情况下,如果明文传输,我们会套一层 Base64 来展示。所以,我们需要对这个函数以及上一步中的函数进行一些调整,先拿入口函数“开刀”。

function entrypoint(r) {
  r.headersOut["Content-Type"] = "text/html;charset=UTF-8";

  switch (r.method) {
    case 'GET':
      return r.return(200, [
        '<form action="/" method="post">',
        '<input name="data" value=""/>',
        '<input type="radio" name="action" id="encrypt" value="encrypt" checked="checked"/><label for="encrypt">Encrypt</label>',
        '<input type="radio" name="action" id="decrypt" value="decrypt"/><label for="decrypt">Decrypt</label>',
        '<input type="radio" name="base64" id="base64-on" value="on" checked="checked"/><label for="base64-on">Base64 On</label>',
        '<input type="radio" name="base64" id="base64-off" value="off" /><label for="base64-off">Base64 Off</label>',
        '<button type="submit">Submit</button>',
        '</form>'
      ].join('<br>'));
    case 'POST':
      var body = r.requestBody;
      if (r.headersIn['Content-Type'] != 'application/x-www-form-urlencoded' || !body.length) {
        r.return(401, "Unsupported method\n");
      }

      var params = body.trim().split('&').reduce(function (prev, item) {
        var tmp = item.split('=');
        var key = decodeURIComponent(tmp[0]).trim();
        var val = decodeURIComponent(tmp[1]).trim();
        if (key === 'data' || key === 'action' || key === 'base64') {
          if (val) {
            prev[key] = val;
          }
        }
        return prev;
      }, {});

      if (!params.action || (params.action != 'encrypt' && params.action != 'decrypt')) {
        return r.return(400, 'Invalid Params: `action`.');
      }

      if (!params.base64 || (params.base64 != 'on' && params.base64 != 'off')) {
        return r.return(400, 'Invalid Params: `base64`.');
      }

      if (!params.data) {
        return r.return(400, 'Invalid Params: `data`.');
      }

      function response_cb(res) {
        r.return(res.status, res.responseBody);
      }

      return r.subrequest(`/api/${params.action}${params.base64 === 'on' ? '?base64=1' : ''}`, { method: 'POST', body: params.data }, response_cb)
    default:
      return r.return(400, "Unsupported method\n");
  }
}

我们在调试入口添加了一个是否开启 Base64 编码的选项,并在开启 Base64 编码的情况下,调用加解密接口的时候,额外添加了一个 ?base64=1 的请求参数。

加密函数的改造也很简单,差不多十行就行了:

async function encrypt(req) {
  const needBase64 = req.uri.indexOf('base64=1') > -1;
  const spki = await crypto.subtle.importKey("spki", pem_to_der(rsaKeys.public, "PUBLIC"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
  const result = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, spki, req.requestText);
  if (needBase64) {
    req.return(200, Buffer.from(result).toString("base64"));
  } else {
    req.headersOut["Content-Type"] = "application/octet-stream";
    req.return(200, Buffer.from(result));
  }
}

重启 Nginx 服务,选择使用 Base64 编码,可以看到输出结果已经符合预期了。

Base64 化之后的NJS RSA 加密函数默认输出

将内容复制保存,稍后使用。我们来接着实现 RSA 解密功能。

实现 NJS 程序:RSA 解密函数

有了 RSA 加密函数,写出解密函数就更简单了,这里就不和加密函数一样,拆解步骤了,直接照顾到“是否启用 Base64”这个选项类型就好。

async function decrypt(req) {
  const needBase64 = req.uri.indexOf('base64=1') > -1;
  const pkcs8 = await crypto.subtle.importKey("pkcs8", pem_to_der(rsaKeys.private, "PRIVATE"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
  const encrypted = needBase64 ? Buffer.from(req.requestText, 'base64') : Buffer.from(req.requestText);
  const result = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, pkcs8, encrypted);
  req.return(200, Buffer.from(result));
}

使用上一步里的 Base64 后的 RSA 加密结果进行提交,可以看到我们前文中加密的内容就能被正确解密了。

NJS 计算 RSA 解密结果

有了上面的基础,接下来我们来折腾自动化加解密。

构建具备自动加解密能力的网关

为了模拟真实业务场景,我们得分别调整 Nginx 配置、容器配置。

调整 Nginx 配置:模拟业务接口

还是先进行 Nginx 配置的调整。

先模拟两个新的服务,并设定它们输出的内容,分别为原始数据和已被 RSA 加密过的数据。为了保持简单,我们还是使用 NJS 来模拟服务端接口响应内容:

server {
    listen 8081;
    server_name localhost;

    charset utf-8;
    gzip on;

    location / {
        js_content mock.mockEncData;
    }
}

server {
    listen 8082;
    server_name localhost;

    charset utf-8;
    gzip on;

    location / {
        js_content mock.mockRawData;
    }
}

为了在模拟服务中使用 NJS,记得在 Nginx 全局配置中添加额外的 NJS 脚本引用声明:

js_import mock from script/mock.js;

为了方便本地调试,我们还可以调整容器编排配置,将上面两个服务的接口公开出来:

version: '3'

services:

  nginx-api-demo:
    image: nginx:1.21.4-alpine
    restart: always
    ports:
      - 8080:80
      - 8081:8081
      - 8082:8082
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./scripts:/etc/nginx/script

实现 NJS 程序:编写业务模拟接口

这里参考上文,可以迅速写出两个业务接口,它们分别会输出后续需要加密原始数据,以及RSA加密后的数据。为了模拟真实场景,这里使用随机函数,随机的针对三个不同的内容进行具体计算。

function randomPick() {
    const powerWords = ['苏洋博客', '专注硬核', '分享有趣'];
    return powerWords[Math.floor(Math.random() * powerWords.length)];
}

function mockRawData(r) {
    r.headersOut["Content-Type"] = "text/html;charset=UTF-8";
    r.return(200, randomPick());
}

const fs = require('fs');
if (typeof crypto == 'undefined') {
    crypto = require('crypto').webcrypto;
}

function pem_to_der(pem, type) {
    const pemJoined = pem.toString().split('\n').join('');
    const pemHeader = `-----BEGIN ${type} KEY-----`;
    const pemFooter = `-----END ${type} KEY-----`;
    const pemContents = pemJoined.substring(pemHeader.length, pemJoined.length - pemFooter.length);
    return Buffer.from(pemContents, 'base64');
}

const publicKey = fs.readFileSync(`/etc/nginx/script/rsa.pub`);

async function mockEncData(r) {
    const spki = await crypto.subtle.importKey("spki", pem_to_der(publicKey, "PUBLIC"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
    const result = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, spki, randomPick());

    r.headersOut["Content-Type"] = "text/html;charset=UTF-8";
    r.headersOut["Encode-State"] = "ON";
    r.return(200, Buffer.from(result).toString("base64"));
}

export default { mockEncData, mockRawData };

一切就绪之后,我们访问不同的端口,可以看到“业务接口”已经就绪啦。这里通过对已加密的数据添加 Encode-State 请求头,来做数据类型区别。如果你不希望添加额外字段,也可以在 Content-Type 中进行响应数据类型标识。

使用 NJS  模拟业务接口

调整网关 Nginx 配置:聚合业务接口

业务实际使用方法有两种,一种是业务接口调用我们前文中的网关加解密功能,进行数据加解密,然后进行响应。而另外一种,则是网关聚合业务接口,根据数据响应类型调整对应的输出结果。

本文选择后一种方案,搭配 Traefik 可以实现快速的水平扩容,以提高服务响应能力。

因为 NJS 的子请求有请求来源限制,为了能够和业务数据进行交互,需要在网关的 Nginx 配置中添加两个接口,代理远端的需要加密或解密的业务数据。

location /remote/need-encrypt {
    proxy_pass http://localhost:8082/;
}

location /remote/need-decrypt {
    proxy_pass http://localhost:8081/;
}

配置完毕,你就可以通过 http://localhost:8080/remote/need-encrypthttp://localhost:8080/remote/need-encrypt 访问上一小节中的内容了。

同时,为了我们能够访问自动加解密的接口,还需要再添加一个接口,用于调用 NJS 函数进行数据的自动加解密。(实际业务使用,追求极致性能,可以考虑拆分成两个)

location /auto{
    js_content app.auto;
}

实现 NJS 程序:自动加解密业务数据

我们先来实现一个能够根据我们指定的数据源(加密过的数据、未解密的数据),进行数据的自动处理。

async function auto(req) {
  req.headersOut["Content-Type"] = "text/html;charset=UTF-8";

  let remoteAPI = "";
  switch (req.args.action) {
    case "encrypt":
      remoteAPI = "/remote/need-encrypt";
      break;
    case "decrypt":
    default:
      remoteAPI = "/remote/need-decrypt";
      break;
  }

  async function autoCalc(res) {
    const isEncoded = res.headersOut['Encode-State'] == "ON";
    const remoteRaw = res.responseText;
    if (isEncoded) {
      const pkcs8 = await crypto.subtle.importKey("pkcs8", pem_to_der(rsaKeys.private, "PRIVATE"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
      const encrypted = Buffer.from(remoteRaw, 'base64');
      const result = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, pkcs8, encrypted);
      req.return(200, Buffer.from(result));
    } else {
      const spki = await crypto.subtle.importKey("spki", pem_to_der(rsaKeys.public, "PUBLIC"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
      const dataEncrypted = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, spki, remoteRaw);
      req.return(200, Buffer.from(dataEncrypted).toString("base64"));
    }
  }

  req.subrequest(remoteAPI, { method: "GET" }, autoCalc)
}


export default { encrypt, decrypt, entrypoint, auto };

重启 Nginx ,分别访问代理远端数据接口 /remote/need-encrypt 和自动加密的网关接口,可以看到程序已经能够符合预期的运行了。

NJS 根据请求自动加密业务接口数据

为了让程序更智能一些,达到数据加解密的完全自动化,可以再进行一个简单调整,让程序不是根据我们指定的参数去访问原始数据,而是随机访问原始数据。(为了能够直观验证行为,这里我们将输出内容也进行调整)

async function auto(req) {
  req.headersOut["Content-Type"] = "text/html;charset=UTF-8";

  function randomSource() {
    const sources = ["/remote/need-encrypt", "/remote/need-decrypt"];
    return sources[Math.floor(Math.random() * sources.length)];
  }

  async function autoCalc(res) {
    const isEncoded = res.headersOut['Encode-State'] == "ON";
    const remoteRaw = res.responseText;
    if (isEncoded) {
      const pkcs8 = await crypto.subtle.importKey("pkcs8", pem_to_der(rsaKeys.private, "PRIVATE"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["decrypt"]);
      const encrypted = Buffer.from(remoteRaw, 'base64');
      const result = await crypto.subtle.decrypt({ name: "RSA-OAEP" }, pkcs8, encrypted);
      req.return(200, [
        "<h2>原始内容</h2>",
        `<code>${remoteRaw}</code>`,
        "<h2>处理后的内容</h2>",
        `<code>${Buffer.from(result)}</code>`
      ].join(""));
    } else {
      const spki = await crypto.subtle.importKey("spki", pem_to_der(rsaKeys.public, "PUBLIC"), { name: "RSA-OAEP", hash: "SHA-256" }, false, ["encrypt"]);
      const dataEncrypted = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, spki, remoteRaw);
      req.return(200, [
        "<h2>原始内容</h2>",
        `<code>${remoteRaw}</code>`,
        "<h2>处理后的内容</h2>",
        `<code>${Buffer.from(dataEncrypted).toString("base64")}</code>`
      ].join(""));
    }
  }

  req.subrequest(randomSource(), { method: "GET" }, autoCalc)
}

再次重启 Nginx ,多刷新几次,就能看到根据内容自动进行 RSA 加解密的结果啦。

NJS 实现 RSA 内容自动加解密

其他:接口安全考虑

实际使用过程中,除了推荐在业务前添加额外的鉴权验证、频率限制外,同样建议根据实际情况使用 internal 限制 Nginx 接口的“作用域”,让数据源和基础计算接口仅允许被 NJS 程序内部访问。

location /remote/need-encrypt {
    internal;
    proxy_pass http://localhost:8082/;
}

location /remote/need-decrypt {
    internal;
    proxy_pass http://localhost:8081/;
}

location /api/encrypt {
    internal;
    js_content app.encrypt;
}

location /api/decrypt {
    internal;
    js_content app.decrypt;
}

其他:如果你追求更高效的计算

上面为了演示,我们将计算结果都进行了 Base64 编码,考虑实际生产环境中超高压力,我们一般对函数计算复杂度锱铢必较,所以可以考虑将证书硬编码到代码中,以及尽可能的去掉不必要的 Base64(只在调试模式中打开)。

最后

网络上关于 NJS 的参考资料目前还是比较少的,希望本文会成为连接你和 NJS 的纽带。

上述内容,我存放在了 GitHub 上,感兴趣的同学可以自取

–EOF