使用自定义验证器在Adaptive Forms中扫描文件附件

文件附件病毒扫描程序/验证程序管道:已附加文件,您的FileAttachmentValidator正在运行,提交内容已被接受或拒绝

[AEM 表单]{class="badge positive" title="适用于AEM Forms"}

文件附件病毒扫描程序/验证程序功能在早期采用程序程序下。 您可以使用官方电子邮件 ID 写信给 aem-forms-ea@adobe.com,加入早期采用者计划并申请使用该功能。

自适应Forms可以在提交时将每个上传的附件传递到您选择的验证器。 验证器运行在服务器上,在内存中接收文件,并决定是接受还是拒绝提交,因此不会保留恶意或不兼容的文件。 由于您提供验证器,因此您可以连接已使用的任何防病毒引擎、恶意软件扫描程序或自定义验证逻辑。

本文介绍该功能,记录您实施的FileAttachmentValidator界面,并介绍如何构建防病毒验证器。 有关完整的引擎特定示例,请参阅随附教程使用ClamAV扫描文件附件。

工作原理 how-it-works

当用户提交具有选定验证器的自适应表单时,AEM会在处理提交之前调用已上传附件的验证器:

  1. 用户附加文件并选择​提交。
  2. 在服务器上,AEM调用验证器的validateFileAttachment方法,将附件(包括其原始字节)作为FileAttachmentWrapper传递。
  3. 您的验证器通过调用防病毒引擎、应用验证规则或两者来检查文件,然后返回FileAttachmentValidationResult。
  4. 如果结果有效,则继续提交。 如果无效,AEM将阻止提交,向用户显示结果中的消息,并标记附件字段。

此模型的两个属性对安全性至关重要:

  • 服务器端。 该检查在服务器上运行,因此无法从浏览器中绕过它。
  • 在持续之前扫描。 文件在存储之前会从内存中进行评估,因此拒绝的文件永远不会写入存储库。

可用性和先决条件 prerequisites

  • 基于核心组件的自适应表单。
  • 设置为生成和部署AEM捆绑包的Maven项目。
  • 提供com.adobe.forms.common.service接口(FileAttachmentValidator、FileAttachmentWrapper、FileAttachmentValidationResult、FileAttachmentValidatorManager)的编译时依赖关系: com.adobe.forms.foundation:adobe-xfaforms-common,捆绑在AEM Forms加载项SDK中。 请参阅ClamAV教程的步骤3,了解从何处获取它以及如何在本地安装。
  • 项目中的自适应表单容器​代理组件(扩展核心组件的标准模式),因此您可以在此处添加将验证器字段添加到表单对话框中所述的“提交”选项卡字段。 这仅适用于基于核心组件的表单。
NOTE
文件附件病毒扫描程序/验证器​字段是​不是现成提供的。 您可通过在项目的自适应表单容器组件上添加一个小型对话框扩展来添加它。 每个项目只执行一次步骤(不是每个验证器)。 请参阅将验证器字段添加到表单对话框。

FileAttachmentValidator接口 interface-reference

您可通过实施com.adobe.forms.common.service.FileAttachmentValidator并将其注册为OSGi服务来添加验证器。 接口定义了两种方法。

FileAttachmentValidator fileattachmentvalidator

方法
返回
描述
validateFileAttachment(FileAttachmentWrapper fileAttachmentWrapper)
FileAttachmentValidationResult
由框架为上载的附件调用。 检查附件并返回一个指示是否接受的结果。
getFileAttachmentValidatorName()
String
返回标识此验证器的名称。 此名称用于选择表单上的验证器。

FileAttachmentWrapper fileattachmentwrapper

利用包装器,可访问您上传的文件及其元数据的代码。 典型验证器中使用的访问器包括:

方法
返回
描述
getFileNameV2()
String
上载文件的名称,例如invoice.pdf。
getContentType()
String
为文件报告的MIME类型,例如application/pdf。
getValue()
byte[]
文件的原始内容,保存在内存中。 这是您传递给扫描引擎的内容。
NOTE
该表列出了验证器中常用的访问器。 您针对其构建的SDK版本可能会在FileAttachmentWrapper上公开其他方法。 检查依赖关系中的界面以获取完整的集。

FileAttachmentValidationResult fileattachmentvalidationresult

您的验证器返回FileAttachmentValidationResult以告知框架是否接受附件。 使用以下参数构造它:

参数
类型
描述
isValid
boolean
true接受附件,false拒绝附件并阻止提交。
message
String
易于用户识别的消息。 在拒绝时,这将说明文件未被接受的原因。
fileAttachmentWrapper
FileAttachmentWrapper
已验证的包装器。

例如:new FileAttachmentValidationResult(false, "The attached file failed the security scan.", wrapper)。

NOTE
如果您需要读取FileAttachmentValidationResult,例如,在测试或直接调用验证器的代码中,请使用isFileAttachmentValid()和getResponseString()/setResponseString()。 该类不公开isValid()或getMessage()方法;这些名称描述上述构造函数参数,而不是getter。

FileAttachmentValidatorManager fileattachmentvalidatormanager

AEM注册一个FileAttachmentValidatorManager服务,该服务位于框架与您部署的每个验证器之间。 您通常不需要自己调用它:AEM使用它查找在表单上选择的验证器并调用它。 了解故障排除的时间非常有用,因为它是提交选项卡的下拉列表和核心提交管道所执行的操作。

方法
返回
描述
getValidators()
List<FileAttachmentValidator>
所有当前注册的FileAttachmentValidator OSGi服务。
getFileAttachmentValidator(String name)
FileAttachmentValidator
getFileAttachmentValidatorName()与name匹配的验证器,如果没有匹配项,则抛出。
validateFileAttachment(String name, List<FileAttachmentWrapper> attachments)
List<FileAttachmentValidationResult>
同时针对多个附件运行命名验证器。 当提交包含多个文件时,在内部使用。

注册OSGi服务 register-service

为实施添加批注,以便在FileAttachmentValidator界面下注册该实施。 部署该插件后,验证器在您的表单上变为可选状态。

NOTE
本文使用现代OSGi Declarative Services注释(org.osgi.service.component.annotations)。 在其他位置找到的旧示例使用Apache Felix SCR注释(org.apache.felix.scr.annotations);这些示例已弃用,不应用于新验证器。

构建防病毒验证器 build-validator

以下实施显示了验证器的结构,但未将其绑定到特定引擎。 将scanWithYourEngine方法替换为对防病毒、扫描API或验证服务的调用。

package com.example.forms.security;

import com.adobe.forms.common.service.FileAttachmentValidator;
import com.adobe.forms.common.service.FileAttachmentValidationResult;
import com.adobe.forms.common.service.FileAttachmentWrapper;
import org.osgi.service.component.annotations.Component;

@Component(service = FileAttachmentValidator.class)
public class CustomFileAttachmentValidator implements FileAttachmentValidator {

    private static final String VALIDATOR_NAME = "Custom Antivirus Scanner";

    @Override
    public FileAttachmentValidationResult validateFileAttachment(FileAttachmentWrapper wrapper) {

        if (wrapper == null) {
            return new FileAttachmentValidationResult(false, "No attachment was received.", wrapper);
        }

        byte[] content = wrapper.getValue();
        if (content == null || content.length == 0) {
            return new FileAttachmentValidationResult(false, "The attached file is empty.", wrapper);
        }

        try {
            boolean clean = scanWithYourEngine(content);
            if (clean) {
                return new FileAttachmentValidationResult(true, "File passed the security scan.", wrapper);
            }
            return new FileAttachmentValidationResult(false,
                    "The attached file failed the security scan and was rejected.", wrapper);

        } catch (Exception e) {
            // Fail closed: if the scanner cannot be reached, reject rather than let an
            // unscanned file through. See Best practices for the fail-open alternative.
            return new FileAttachmentValidationResult(false,
                    "The file could not be scanned at this time. Please try again later.", wrapper);
        }
    }

    @Override
    public String getFileAttachmentValidatorName() {
        return VALIDATOR_NAME;
    }

    /**
     * Integrate your antivirus or validation engine here.
     * Return true if the file is safe to accept, false if it should be rejected.
     */
    private boolean scanWithYourEngine(byte[] content) {
        // TODO: call a local scanning daemon (for example over a socket)
        // or a remote scanning API, and interpret its verdict.
        return true;
    }
}

生成和部署捆绑包 build-deploy

编译项目并安装捆绑包,以便在AEM实例上注册验证器。

在开发期间部署到本地实例。 从项目根目录中,构建捆绑包并将其安装到正在运行的创作实例:

mvn clean install -PautoInstallBundle

autoInstallBundle配置文件将编译后的捆绑包推送到本地AEM实例(默认为http://localhost:4502)。 如果您的实例在其他主机、端口或凭据上运行,请将它们传递到内部版本,例如:

mvn clean install -PautoInstallBundle -Daem.host=localhost -Daem.port=4502 -Dvault.user=admin -Dvault.password=admin

部署到更高的环境。 对于暂存和生产,请勿手动安装捆绑包。 通过CI/CD管道将其作为项目all内容包的一部分部署。 有关完整过程,请参阅部署到AEM as a Cloud Service。

确认服务已注册。 打开组件控制台并搜索您的验证器类:

http://localhost:4502/system/console/components

该组件应列出,其状态显示为​活动 (或​满足),已在FileAttachmentValidator服务下注册。 如果不满意,请参阅验证程序是否加载并运行。

组件控制台将自定义验证器及其数据源servlet显示为活动

将验证器字段添加到表单对话框 add-dialog-field

本节适用于基于​ 核心组件 ​的自适应Forms。 现成的自适应表单容器对话框中不存在​ 文件附件病毒扫描程序/验证器 ​字段。 您可在自己的项目中将它添加一次,作为小型扩展。 您之后部署的每个验证器(请参阅构建一个防病毒验证器)随后都会自动显示为选项;请不要对每个验证器重复此步骤。

您项目的自适应表单容器是一个​代理组件:/apps下的组件,它使用sling:resourceSuperType指向真正的核心组件,例如:

<!-- /apps/<project>/components/adaptiveForm/formcontainer/.content.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:Component"
    jcr:title="Form Container"
    sling:resourceSuperType="core/fd/components/form/container/v2/container"
    xmlns:sling="http://sling.apache.org/jcr/sling/1.0"/>

由于sling:resourceSuperType,您可以将​ 一个新字段 ​添加到继承的“提交”选项卡,而无需复制整个对话框。 Sling资源合并器只需要重新创建一直到插入点的节点名称路径。 有关常规机制,请参阅扩展组件对话框。 真正的OOTB提交选项卡将其字段保留在content/items/tabs/items/submitActions/items/columns/items/下,因此这是镜像的路径:

<!-- /apps/<project>/components/adaptiveForm/formcontainer/_cq_dialog/.content.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
    jcr:primaryType="nt:unstructured">
  <content jcr:primaryType="nt:unstructured">
    <items jcr:primaryType="nt:unstructured">
      <tabs jcr:primaryType="nt:unstructured">
        <items jcr:primaryType="nt:unstructured">
          <submitActions jcr:primaryType="nt:unstructured">
            <items jcr:primaryType="nt:unstructured">
              <columns jcr:primaryType="nt:unstructured">
                <items jcr:primaryType="nt:unstructured">
                  <fileAttachmentValidator
                      jcr:primaryType="nt:unstructured"
                      sling:resourceType="granite/ui/components/coral/foundation/form/select"
                      fieldLabel="File Attachment Virus Scanner/Validator"
                      fieldDescription="Select a registered validator configuration to scan submitted file attachments. Select None to disable validation for this form."
                      emptyText="None"
                      name="./fileAttachmentValidator">
                    <datasource
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="<project>/datasources/fileattachmentvalidators"/>
                  </fileAttachmentValidator>
                </items>
              </columns>
            </items>
          </submitActions>
        </items>
      </tabs>
    </items>
  </content>
</jcr:root>

有两个细节值得一提:

  • emptyText="None"​为字段提供了未选择的默认状态,因此未选择任何内容的表单根本不会应用验证器。 您无需向以下数据源添加虚假的“无”条目。
  • 需要多个配置。 您可以注册多个验证器,例如每个防病毒引擎注册一个,或注册同一引擎的多个配置不同的实例。 每个注册的FileAttachmentValidator都有自己的getFileAttachmentValidatorName(),因此每个都自动显示为单独的选项;此对话框片段的更改不取决于您注册的数量。

datasource子节点指向了一个您也编写的小型Sling servlet。 它有一个作业:向FileAttachmentValidatorManager询问getValidators(),并将每个验证器的getFileAttachmentValidatorName()转换为下拉选项。 有关完整、现成的实施,请参阅ClamAV教程中的步骤7 🔗:不论您使用哪个验证器引擎,该servlet都是相同的,因此您可以按原样将其复制到项目中。

将这两个文件部署为项目的ui.apps内容包和core包的一部分(请参阅生成并部署包)。 部署后,此对话框扩展经过验证工作正常:该字段显示在“提交”选项卡的现有OOTB字段旁边(未替换它们),设置它会在引导容器节点上保留一个纯fileAttachmentValidator属性,AEM提交管道通过FileAttachmentValidatorManager.getFileAttachmentValidator(name)读取该属性。

在表单上选择验证器 select-validator

  1. 打开自适应表单进行编辑,然后选择​ 指南容器 ​以打开​ 自适应表单容器 ​属性。
  2. 转到​ 提交 ​选项卡。
  3. 从​ 文件附件病毒扫描程序/验证器 ​下拉列表中,选择您的验证器。 假设您已完成将验证器字段添加到表单对话框,此列表将使用getFileAttachmentValidatorName()为每个当前注册的验证器返回的名称填充。 选择​无(默认值)禁用此表单的验证。
  4. 选择​ 完成 ​并保存表单。

使用在文件附件病毒扫描程序/验证程序字段中选择的验证程序的“自适应表单容器”对话框的 提交选项卡

IMPORTANT
如果在“提交”选项卡上完全没有看到​ 文件附件病毒扫描程序/验证器 ​字段,则您(或您的项目)尚未完成将验证器字段添加到表单对话框。 对于基于核心组件的表单,此字段绝不会开箱即用。

运行时行为和用户体验 runtime-behavior

  • 文件已接受。 当验证器返回有效结果时,提交将按常规方式进行。
  • 文件被拒绝。 当验证器返回无效结果时,AEM将停止提交,显示从结果发送给用户的消息,并将文件附件字段标记为验证错误。 表单数据未提交。

验证验证器是否加载并运行 verify

  1. 部署后,请确认包处于活动状态并且组件在FileAttachmentValidator服务下方列出的OSGi控制台(状态 > 组件)中得到了满足。
  2. 打开表单的​ 提交 ​设置,并确认您的验证器显示在下拉列表中。
  3. 使用测试文件提交表单,并确认已调用您的验证器(在开发期间添加日志语句)。

如果​ 文件附件病毒扫描程序/验证器 ​字段本身未出现在“提交”选项卡上:

如果出现字段,但您的验证器未在下拉列表中列出:

  • 确认包已安装并处于活动状态,且组件已在FileAttachmentValidator下注册且未处于未满足状态。
  • 确认getFileAttachmentValidatorName()返回非空的唯一名称。
  • 部署包后重新加载表单编辑器。

最佳实践和注意事项 best-practices

决定扫描的位置。 您的验证器可调用在本地(或与AEM位于同一位置)运行的扫描引擎或远程扫描API。 本地或异地引擎可将文件内容保留在您的环境中,这有助于满足数据驻留和法规遵从性要求。 远程API在环境之外发送文件字节,因此在选择数据之前请确认数据是可接受的。

有意选择故障模式。 当扫描引擎无法访问或出现错误时,您可以失败关闭(如上述示例中拒绝文件)或失败打开(接受文件)。 对于安全敏感型表单,失效关闭是较安全的默认设置。 未通过开放偏好。 选择明确而非意外。

考虑延迟。 扫描在提交期间同步进行,因此扫描时间会添加到用户的提交体验中。 如果依赖于外部服务,则为引擎的呼叫设置合理的超时,并通过服务级别协议来设置期望值。

规划大批量公开提交。 延迟和吞吐量取决于您集成的第三方扫描服务,而不是依赖于AEM Forms。 对于公共、高流量的表单,调整扫描服务的大小以获得尖峰提交量,保持高可用性(因为它位于提交路径中),并将其与机器人保护(例如CAPTCHA)配对,以减少垃圾提交。 典型的附件卷由大多数扫描解决方案处理;请确认对提供商的SLA的预期负载。

已绑定有效负载。 getValue()以内存中byte[]的形式返回文件,因此大型上载占用栈。 使用文件附件组件的最大文件大小和支持的文件类型限制源位置的上传,因此验证器仅处理大小合理、预期的文件。

对控件进行分层。 验证器是多个层中最强的一层。 在文件附件组件上限制文件类型和大小,使用验证器扫描提交时的文件,并添加机器人保护(例如CAPTCHA)。 单个层本身并不足够。

写入清除拒绝消息。 结果中的message向用户显示,因此使其可操作,例如“不允许使用此文件类型。 上传PDF。” 如果您的表单提供多种语言,请考虑本地化。

安全并监视扫描通道。 使用安全传输到扫描引擎,保持其高可用性(因为它位于提交路径中),并记录扫描结果以进行审核和故障排除。

常见问题解答 faq

什么是AEM Forms中的文件附件验证器?
它是一项在AEM中注册的服务器端服务,在提交时检查通过自适应表单上传的每个文件,并告知AEM是接受还是拒绝文件。 您通过​ 文件附件病毒扫描程序/验证程序 ​设置在表单上选择它。

AEM何时调用验证器?
在提交时,在保留表单数据和附件之前。 AEM使用附件调用您的validateFileAttachment方法,如果结果无效,将阻止提交。

文件一附上,验证运行时间能否早于提交时间?
是的,作为单独的机制。 在用户提交之前,使用自适应Forms规则编辑器中的​ 调用服务 ​操作对文件字段的更改事件调用扫描服务。 这与本文中描述的FileAttachmentValidator接口无关。 而是调用表单数据模型(FDM)服务。 请参阅规则编辑器中的调用服务增强功能。

验证器是否扫描输入字段或URL,还是只扫描文件附件?
它会在提交时扫描文件附件。 验证器不会扫描其他字段中的值,例如键入文本字段的URL。 要验证这些规则,请使用自适应Forms规则编辑器中的填充时间规则(例如,正则表达式模式)或​ 调用服务 ​操作。 如果URL指向要扫描的文件,则在填充时通过调用服务将该URL发送到扫描服务。

验证程序是在浏览器中运行还是在服务器上运行?
在服务器上。 无法通过禁用JavaScript或在浏览器中编辑页面来绕过此检查。

我能否在此界面中使用任何防病毒?
是。 该界面不依赖于任何特定引擎。 您的实施可以调用本地扫描守护程序、商业防病毒或数据丢失防护产品,或远程扫描API,然后将结果映射到FileAttachmentValidationResult。 没有Adobe批准或认证的扫描产品列表;请根据您的需求、数量和服务级别协议选择服务。

可以扫描哪些文件类型?
要检查哪些文件由您集成的第三方扫描服务决定,而不是由AEM Forms决定。 AEM将原始附件字节传递到验证器,因此可扫描的内容(PDF、PNG、JPG、DOCX等)取决于该引擎。 要控制用户首先可以附加哪些文件,请使用文件附件组件支持的文件类型和最大文件大小设置,无论使用何种验证器,这些设置均适用。

是否在AEM中保存被拒绝的文件?
不会。 因为在保留文件之前会从内存中评估该文件,因此拒绝的文件永远不会写入存储库。

当文件被拒绝时,如何显示自定义消息?
在构造FileAttachmentValidationResult时设置message值。 AEM在拒绝文件时向用户显示该消息。

示例场景 example-scenarios

  • 方案:​每个文件都将被拒绝,包括干净文件。
    操作:​检查扫描引擎或您的验证器调用是否可访问。 失败关闭的验证器在无法连接时会拒绝所有内容,因此请先确认连接,然后检查验证器的日志中是否有连接错误。

  • 方案:​文件附件病毒扫描程序/验证程序字段完全不显示在“提交”选项卡中。
    操作:​确认您的项目已部署来自的对话框扩展和数据源Servlet将验证器字段添加到表单对话框,并且已为您的项目启用任何功能切换基础管理器功能。

  • 方案:​出现该字段,但您的验证器未在下拉列表中列出。
    操作:​确认包处于活动状态并在FileAttachmentValidator下注册(未处于未满足状态),getFileAttachmentValidatorName()返回非空的唯一名称,并在部署后重新加载表单编辑器。

  • 方案:​添加验证器后,表单提交速度明显变慢。
    操作:​扫描在提交期间同步运行,因此扫描时间会添加到用户的提交体验中。 检查扫描引擎的延迟,并设置合理的超时。 请参阅考虑延迟。

  • 方案:​您需要支持多个防病毒引擎,或同一引擎的多个配置不同的实例。
    操作:​将每个操作注册为自己的FileAttachmentValidator实现。 每个已注册的验证器都会通过自己的getFileAttachmentValidatorName()自动显示在“提交”选项卡下拉列表中,因此无需更改任何有关对话框扩展的内容。

  • 方案:​您希望在用户提交之前运行验证,而不只是在提交时运行。
    操作:​请参阅验证能否早于提交运行? 以上。 在规则编辑器中使用Invoke Service操作,而不使用此验证器,或者与此验证器一起使用。

recommendation-more-help
experience-manager-cloud-service-help-main-toc