You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Oracle APEX 5应用集成USB令牌实现文件数字签名方案咨询

可行性与集成方案

当然可行!我之前帮不少开发者做过类似的APEX集成场景,给你梳理下具体的实现思路和注意要点:

核心逻辑的可行性

Node.js确实是处理USB令牌数字签名的可靠选择,像node-webcrypto-p11这类成熟的库可以轻松调用PKCS#11兼容的USB令牌完成签名操作,这部分技术路径已经非常成熟了。

APEX 5 与 Node.js 的集成路径

APEX 5作为Oracle的低代码Web平台,和Node.js的集成主要通过RESTful服务或者本地代理来实现,分两种常见场景处理:

场景1:令牌部署在服务器端(共享签名场景)

如果你的USB令牌是部署在服务器端的(比如团队共享的签名令牌),可以直接搭建Node.js REST服务,然后在APEX中调用:

  1. 搭建签名服务
    用Express写一个简单的服务,暴露签名接口,示例代码片段:
    const express = require('express');
    const { Crypto } = require('node-webcrypto-p11');
    const cors = require('cors');
    const app = express();
    app.use(cors()); // 允许APEX跨域请求
    app.use(express.json());
    
    // 初始化PKCS#11驱动,替换成你的令牌驱动路径
    const crypto = new Crypto({
      library: "/usr/lib/pkcs11/token-driver.so",
      name: "MySignatureToken",
      slot: 0
    });
    
    app.post('/api/sign-file', async (req, res) => {
      try {
        const { fileHash, userId } = req.body;
        // 从令牌获取签名密钥(需提前配置好权限)
        const privateKey = await crypto.subtle.getKey(
          { name: "RSASSA-PKCS1-v1_5" },
          false,
          ["sign"]
        );
        // 对文件哈希签名
        const signatureBuffer = await crypto.subtle.sign(
          { name: "RSASSA-PKCS1-v1_5" },
          privateKey,
          Buffer.from(fileHash, 'base64')
        );
        res.json({ signature: Buffer.from(signatureBuffer).toString('base64') });
      } catch (err) {
        res.status(500).json({ error: `签名失败:${err.message}` });
      }
    });
    
    app.listen(3000, () => console.log('签名服务已启动在端口3000'));
    
  2. 在APEX中调用服务
    用APEX的动态动作或者自定义JavaScript触发请求,比如在用户点击"签名"按钮时:
    // 获取待签名文件的哈希(提前在APEX中计算好)
    const fileHash = apex.item("P1_FILE_HASH").getValue();
    // 调用Node.js签名服务
    apex.server.invoke(
      "https://your-server-domain/api/sign-file",
      { fileHash: fileHash, userId: apex.item("P1_USER_ID").getValue() },
      {
        success: function(response) {
          // 将签名结果保存到APEX页面项或数据库
          apex.item("P1_SIGNATURE").setValue(response.signature);
          apex.message.showPageSuccess("文件签名完成!");
        },
        error: function(xhr) {
          apex.message.showErrors([{ message: `签名失败:${xhr.responseText}` }]);
        }
      }
    );
    

场景2:用户本地USB令牌(个人签名场景)

如果是用户自己的USB令牌(本地设备),服务器端的Node.js服务无法直接访问,这时候需要用本地Node.js代理方案:

  1. 本地代理服务
    写一个轻量的Node.js本地服务,运行在用户电脑上,通过HTTP和APEX应用通信,负责访问本地USB令牌:
    const express = require('express');
    const { Crypto } = require('node-webcrypto-p11');
    const cors = require('cors');
    const app = express();
    app.use(cors());
    app.use(express.json());
    
    let crypto;
    // 初始化令牌连接(需要用户输入PIN)
    app.post('/local/init-token', async (req, res) => {
      try {
        crypto = new Crypto({
          library: req.body.driverPath,
          name: req.body.tokenName,
          slot: 0,
          pin: req.body.pin // 注意:仅本地传输,不要传到服务器
        });
        res.json({ status: "令牌初始化成功" });
      } catch (err) {
        res.status(400).json({ error: `令牌初始化失败:${err.message}` });
      }
    });
    
    app.post('/local/sign-file', async (req, res) => {
      try {
        const { fileHash } = req.body;
        const privateKey = await crypto.subtle.getKey(
          { name: "RSASSA-PKCS1-v1_5" },
          false,
          ["sign"]
        );
        const signatureBuffer = await crypto.subtle.sign(
          { name: "RSASSA-PKCS1-v1_5" },
          privateKey,
          Buffer.from(fileHash, 'base64')
        );
        res.json({ signature: Buffer.from(signatureBuffer).toString('base64') });
      } catch (err) {
        res.status(500).json({ error: `签名失败:${err.message}` });
      }
    });
    
    app.listen(8080, () => console.log('本地签名代理已启动在端口8080'));
    
  2. APEX前端交互
    在APEX页面中添加引导用户启动本地代理的提示,然后通过AJAX和本地代理通信:
    // 用户点击"启动本地签名"按钮触发
    $("#P1_START_PROXY").on("click", function() {
      apex.message.alert("请先运行本地签名代理程序,然后输入令牌信息");
      // 显示令牌配置表单
      $("#token-config-form").show();
    });
    
    // 提交令牌配置,初始化本地代理
    $("#submit-token-config").on("click", function() {
      const driverPath = apex.item("P1_DRIVER_PATH").getValue();
      const tokenName = apex.item("P1_TOKEN_NAME").getValue();
      const pin = apex.item("P1_PIN").getValue();
    
      $.post("http://localhost:8080/local/init-token", {
        driverPath: driverPath,
        tokenName: tokenName,
        pin: pin
      }, function(response) {
        apex.message.showPageSuccess(response.status);
        // 启用签名按钮
        $("#P1_SIGN_BUTTON").prop("disabled", false);
      }).fail(function(xhr) {
        apex.message.showErrors([{ message: xhr.responseJSON.error }]);
      });
    });
    
    // 执行签名
    $("#P1_SIGN_BUTTON").on("click", function() {
      const fileHash = apex.item("P1_FILE_HASH").getValue();
      $.post("http://localhost:8080/local/sign-file", { fileHash: fileHash }, function(response) {
        apex.item("P1_SIGNATURE").setValue(response.signature);
        apex.message.showPageSuccess("文件签名完成!");
      }).fail(function(xhr) {
        apex.message.showErrors([{ message: xhr.responseJSON.error }]);
      });
    });
    

关键注意事项

  • 安全性:PIN码绝对不能传到服务器端,本地代理场景下仅在用户本地传输;签名操作尽量在本地完成,只传递签名结果。
  • CORS配置:Node.js服务需要配置CORS,允许APEX应用的域名发起请求。
  • 令牌兼容性:不同USB令牌的PKCS#11驱动路径不同,需要提供用户配置驱动路径的入口。
  • APEX权限:确保APEX页面的JavaScript代码有足够的权限发起AJAX请求,避免被浏览器的安全策略拦截。

内容的提问来源于stack exchange,提问作者Alexandra Giurgiteanu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:15:11