实现客户端验证

作者:Jennifer Ball

问题描述

大部分 Web 应用程序都包含表单元素,允许用户输入值或从列表中选择项。Web 应用程序必须对这些值和选择进行验证,以防其业务对象使用无效的数据。恶意用户通常会使用这些无效数据来获取对运行 Web 应用程序的系统的控制。

开发者可以使用不同的技术将验证代码包含在 Web 应用程序中。这些技术包括在服务器、客户端或在二者上同时执行验证的方法。服务器端验证和客户端验证都有各自的优点和缺点。

在客户端上执行验证可减少网络流量和服务器负载,这样会缩短响应时间并带来更好的用户体验。但是,并非所有的客户端都支持必须执行验证的客户端脚本。此外,不同的客户端可能支持不同的脚本语言。因此,无论是否在应用程序中包含对客户端验证的支持,开发者都至少需要提供服务器端验证。

与客户端验证相反,服务器端验证需要执行服务器双向验证的额外操作,这样即便是最简单的验证检查,响应时间也会较慢。不过,当开发者仅在服务器上执行验证时,就不必担心由于客户端数量日益增多而带来的支持问题。

如果开发者选择同时提供客户端和服务器端验证,则需要维护两段冗余的代码。

开发者可以根据应用于服务器端验证代码的同一规则集合来生成客户端脚本,从而最大限度地利用客户端和服务器端验证的优点,同时,最大限度地避免各自的缺点。事实上,如果客户端脚本需要访问服务器上的某些信息以确定输入的有效性,则可能需要使用该策略。

解决方案

在 JavaServer[TM] Faces 众多强大的功能中,有一项称为验证模型的功能。利用该模型可以轻松地将基本的服务器端验证包括在应用程序中,并为实现定制解决方案提供灵活的编程模型。

JavaServer Faces 尚不包括对客户端验证的支持。但是,JavaServer Faces 组件呈现模型允许您创建呈现器,呈现器可以生成面向不同客户端的客户端验证代码。

对于可以在不同客户端上运行的应用程序而言,这是一个显著的优点,尽管如此,该策略仍需开发者维护两段冗余的代码:一段用于服务器端验证,另一段用于客户端验证。

对于仅以 HTML 格式呈现的应用程序而言,一种解决方案是在继续使用 JavaServer Faces 技术中包括的其他功能的同时使用 Struts Validator。Struts Validator 不仅包括一组更丰富的标准验证器,而且这些验证器既能执行服务器端验证,又能执行客户端验证。Struts Validator 还允许您插入定制验证器,具体方法是:将这些验证器添加到集中的 validator-rules.xml 文件,该文件包含验证器的相应的客户端 JavaScript[TM]。通过这些集中的规则文件,可以更轻松地维护客户端和服务器端代码。

考虑到所有这些信息,我们提供以下建议:

本文档提供有关单独使用 JavaServer Faces 技术或使用该技术与 Struts 的组合来执行客户端验证的策略的详细信息。

将客户端脚本添加到 JavaServer[TM] Faces 页

JavaServer Faces 技术提供可以向 HTML 客户端呈现的缺省组件。这意味着您可以将 HTML "onsubmit" 属性与 JavaServer Faces HTML 表单组件相关联。请切记:JavaServer Faces 属性名称是区分大小写的,该属性名称必须全部使用小写字符。

以下是包含在 JavaServer Faces 页中的 JavaScript 标记示例:



<script language="JavaScript">
function client_validation() {
if (document.forms[0].elements["validatorForm:color"].value =="red")
return true;
} else if (document.forms[0].elements["validatorForm:color"].value =="green") {
return true;
} else if (document.forms[0].elements["validatorForm:color"].value =="blue") {
return true;
} else {
alert("Enter a red, green, or blue for the color.");
return false;
}
}
</script>


<h:form id="validatorForm" onsubmit="return client_validation();">
...
<p>Enter a color (red,green, or blue):</p>
<h:inputText id="color" value="#{ValidatorBean.color}"/>
<p>
<h:commandButton id="submit" action="success" value="Submit" />
</p>
</h:form>


该表单显示使用 JavaScript 的验证。请注意,签入 JavaScript 中的表单元素的 id 使用 [form name]:[form element id] 命名。在此示例中,您将引用使用 validatorForm:color 作为名称的颜色输入元素。您将无法使用命名方法从 JavaScript 访问表单元素,例如使用 window.document.validatorForm.validatorForm:color.value == "red",因为表单元素的 id 中使用了一个冒号。

请注意,JavaScript 代码是硬编码到页面中的。如果 JavaScript 代码仅在一个页面或少数页面中使用,并且不需要服务器提供任何数据或进行任何处理,则该策略是可接受的。

如果脚本在多个页面中使用,那么将脚本保留在单独的页面(如 clientValidation.js)中并使用专用的脚本标记版本将其包括在各个页面中会更有意义:

<script language="Javascript" type="text/javascript" src="clientValidation.js"/>

但是,该解决方案不能解决脚本如何从服务器获取所需信息这一问题。鉴于各种原因,生成客户端脚本通常是一种更好的做法,如下一部分所述。

使用定制呈现器生成 JavaScript[TM]

如果客户端脚本需要服务器提供一些数据来执行自己的功能,则在服务器上生成脚本时,需要在脚本中包括这些数据。通过生成脚本,您便无需将脚本手动复制到可能需要该脚本的其他页面中。

生成脚本的另一个原因是:您可能希望不同的客户端(包括不支持 JavaScript 的客户端)都能够运行该应用程序。JavaServer Faces 技术提供了丰富的组件呈现模式,允许您创建可用于以不同方式向不同客户端呈现组件的定制呈现器。

组件类本身封装了组件的功能。例如,标准 UISelectOne 组件允许用户从一组值中选择某一个值。呈现器可以定义该组件应该如何显示在页面上。通过对 UISelectOne 组件应用相应的呈现器,可将该组件呈现为一组单选按钮、一个菜单、一个列表框或其他形式。通过使用页面上表示组件和呈现器组合的标记,页作者可以选择该组合。标准标记 selectOneRadio 表示 UISelectOne 组件和 Radio 呈现器的组合,该组合会呈现一组单选按钮。

您也可以使用定制呈现器来呈现客户端脚本,从而允许您在服务器端生成脚本,进而允许脚本访问服务器端数据。此外,您可以编写单独的呈现器,每个呈现器可以为特定客户端呈现相应的脚本,这样,您便可以使用更灵活的、可移植的方法将客户端脚本集成到应用程序中。

在生成客户端脚本时,您只需创建定制呈现器即可,而不必另外创建定制组件。但是,您可以使用其中的一个标准组件。要使用组件创建定制呈现器,您需要执行下列任务:

要演示如何使用定制呈现器呈现脚本,请考虑上一部分中的示例。这次,我们将编写一个可生成脚本的定制呈现器,而不是将脚本硬编码到页面中。

选择一个组件

要通过定制呈现器呈现定制标记,必须将呈现器与标准组件或定制组件配成一对。由于此示例仅在表单中呈现 script 标记,因此,使用简单的组件 UIOutput 即可。如果要将客户端脚本添加到个别表单组件,则必须将该组件创建为定制组件,以便为其创建定制呈现器;或者要求页作者将脚本添加到页面中相应的组件标记。例如,页作者可以将具有一些 JavaScript 或使用 JavaServer Faces EL 引用该脚本的 onselect 属性添加到表示复选框组件的标记中。

创建呈现器类

呈现器类通常包括用于生成标记的编码方法。下面显示的是 ColorRenderer 中的 encodeEnd 方法。JavaServer Faces 实现通过其尝试呈现的组件来调用 encodeEnd 方法,该组件是与呈现器相关联的 UIOutput 组件。如 encodeEnd 方法中所示,呈现器编写一个脚本,该脚本与我们硬编码到页面的脚本作用相同。请参见上面的部分“将客户端脚本添加到 JavaServer Faces 页”。

public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {

if ((context == null) || (component == null)) {
throw new NullPointerException();
}
UIOutput script = (UIOutput) component;
UIInput currentColor = (UIInput) component.findComponent("color");
ResponseWriter writer = context.getResponseWriter();
StringBuffer sb = null;

System.out.println("currentColor =" + currentColor);
if (currentColor == null)
return;
sb = new StringBuffer("function client_validation() {\n");
sb.append("if (document.forms[0]['");
sb.append(currentColor.getClientId(context));
sb.append("'].value=='red' ) {").append("\n");
sb.append("return true;");
sb.append("\n} else if (document.forms[0]['");
sb.append(currentColor.getClientId(context));
sb.append("'].value=='green' ) {").append("\n");
sb.append("return true;");
sb.append("\n} else if (document.forms[0]['");
sb.append(currentColor.getClientId(context));
sb.append("'].value=='blue' ) {").append("\n");
sb.append("return true;");
sb.append("\n } else {\n");
sb.append("alert('Enter red, green, or blue for the color.');");

sb.append("\nreturn false;\n");
sb.append("} \n }");
sb.append("\n");
if (writer != null) {
writer.startElement("script", script);
writer.writeAttribute("language", "JavaScript", "script");
writer.write(sb.toString());
writer.endElement("script");
}
}

注册定制呈现器

创建呈现器之后,需要使用应用程序配置文件在呈现工具包中注册该呈现器。呈现工具包表示一组呈现器,这些呈现器可以输出适用于某个特定客户端的标记。

下面是用于注册 ColorRenderer(已在上一部分中讨论)的 render-kit 元素。Component-family 元素是一种组件类型,用于识别该呈现器可以呈现的组件。Renderer-type 元素是用于识别呈现器的呈现器类型。Renderer-class 元素是类的实际名称。

<render-kit>
<renderer>
<component-family>javax.faces.component.UIOutput</component-family>
<renderer-type>Color</renderer-type>
<renderer-class>ColorRenderer</renderer-class>
</renderer>
</render-kit>

如果我们说您的应用程序可以使用多个呈现器,那么这就意味着,每个呈现器都可以为特定的客户端正确呈现脚本。如果已经创建了一组呈现器,请务必在应用程序配置资源文件中添加相应的呈现器元素标记。

在下一部分中,我们将讨论标记处理程序类如何使用组件类型和呈现器类型来提示 JavaServer Faces 实现呈现组件的方法。

创建标记处理程序类

标记处理程序的一个功能是检索与标记关联的组件的类型。它还将呈现器的类型(如果有)返回到 JavaServer Faces 实现,这样便可在处理标记时执行组件的编码。

以下是执行这些功能的 ScriptTag 方法示例:

public String getComponentType() {
return ("javax.faces.component.UIOutput");
}

public String getRendererType() {
return ("Color");
}

请注意,这些方法返回的组件类型和呈现器类型元素必须与您在应用程序配置文件中按呈现工具包元素配置的相应类型匹配。有关如何创建标记处理程序类的更多详细信息,请参见《J2EE 1.4 Tutorial》。

如果您的应用程序使用多个呈现器,每个呈现器都可以为特定客户端正确呈现脚本,则需要将以下内容添加到标记处理程序类的 getRendererType 方法中:

String agent = (String) getFacesContext().getExternalContext().getRequestHeaderMap().get("User-Agent");
if ((agent != null) && (agent.contains("Mozilla")) {
.... this is a Mozilla client, so return the type of the
renderer that handles this client ...

}...

该方法确定哪个客户端正在运行应用程序并返回相应的呈现器类型。正如上一部分中所述,您还需要在应用程序配置资源文件中注册相应的呈现器。

最后一步是创建用于定义定制标记的 TLD 文件。以下是示例中的一段 TLD 文件:

<taglib>
...
<display-name>customTags</display-name>
<tag>
<name>script</name>
<tag-class>ScriptTag</tag-class>
<body-content>empty</body-content>
...
</tag>
</taglib>

最后,请将 TLD 文件的标记库指令和定制标记包括在页面中,如下所示:

<%@ taglib prefix="customTags" uri="WEB-INF/customTags.tld" %>
...
<h:form id="validatorForm" onsubmit="return client_validation();">
<p>Enter a color (red, green, or blue):</p>
<h:inputText id="color" >
...
<customTags:script/>
</h:form>

无论是否提供客户端验证,在服务器上验证数据都是一种很好的做法,如本文档的前面部分所述。下一部分说明如何将服务器端验证添加到此示例。

包括服务器端验证

如果 JavaServer Faces 技术附带的某个标准验证器能够满足您的需要,则可以使用它。在这种情况下,您只需通过创建定制呈现器来提供相应的客户端验证脚本即可。

如果您需要提供自己的服务器端验证代码,则可参阅 Java[TM] BluePrints Solutions Catalog 中的 Server-side Validation 项,该条目归纳了许多执行定制服务器端验证的技术。在应用程序中使用服务器端验证代码和客户端验证代码时,您可以采用这些技术中的任意一种。

我们的示例提供了定制验证器类。页作者可以使用标准的 validator 标记来引用这个类。该解决方案在我们的示例中最有意义,因为页作者不需要配置任何验证参数,所以我们不需要提供定制标记来表示页面上的验证器。

以下是 ColorValidator 中的 validate 方法:

public void validate(FacesContext context, UIComponent component, 
Object toValidate) {
String value = null;
if ((context == null) || (component == null)) {
throw new NullPointerException();
}

if (!(component instanceof UIInput)) {
return;
}

if ( null == toValidate) {
return;
}

value = toValidate.toString();
if (value.equals("red") || value.equals("green") ||
value.equals("blue")) {
return;
} else {
System.out.println("value "+value);
FacesMessage errMsg = MessageFactory.getMessage(context,
COLOR_INVALID_MESSAGE_ID);
throw new ValidatorException(errMsg);
}
}
}

请注意,定制验证器与呈现器类基本上具有相同的验证逻辑。然而,这却意味着您必须维护两段冗余的代码。由于您必须采用呈现器编码方法将脚本手动写入 String,而且必须在验证器中生成 Java 代码,因此,您很难创建便于维护的客户端和服务器端验证。

如果这对您的特定情况有帮助,您可以通过传递给呈现器的 encodeEnd 方法的组件来利用该方法访问定制验证器:

public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
...
UIInput inputComponent = (UIInput) component;
Validator[] = inputComponent.getValidators();
// then get the validator you're looking for from the array
...
}

使用呈现器的 encodeEnd 方法访问服务器端验证代码之后,您便可以使用该代码中的验证规则生成客户端脚本。如果呈现器可以识别验证器并可根据验证器确定其执行何种检查,则呈现器可以随后生成适当的 Javascript。为了使呈现器访问符合此目的的服务器端验证规则,您必须使用呈现器可识别的定制验证器类来定义这些规则,而不是使用支持 Bean 方法,因为呈现器无法确定支持 Bean 方法执行的验证规则。

另一种好的解决方案是使用一个定义了一组验证规则的外部文件,这些规则可以决定客户端和服务器端代码的行为。Struts Validator 提供了允许您执行此操作的客户端验证解决方案。通过使用 struts-faces 集成库,您可以在 JavaServer Faces 应用程序中使用 Struts Validator。

在 JavaServer Faces 应用程序中使用 Struts Validator

正如本文档前面部分所述,标准 JavaServer Faces 组件的呈现器不提供对客户端验证的支持。要将客户端验证集成到您的应用程序,您需要生成一个定制解决方案(如上所述),或使用一个可在 JavaServer Faces 应用程序中使用并支持客户端验证的库。

Struts-Faces 集成库用于将 JavaServer Faces 功能集成到现有的 Struts 应用程序中。您也可以使用该库将 Struts 功能(如 Struts Validator 功能)集成到您的 JavaServer Faces 应用程序中。Struts Validator 提供一组基本的验证器,这些验证器附带有已在多个客户端上测试过的 JavaScript 代码。下表列出了这些验证器。

  1. Struts 标准验证器
验证器 用途
required 检查用户是否输入了一个值
mask 检查值是否与 mask 属性指定的正则表达式匹配
range 检查该值是否在两个值之间
maxLength
检查字段长度是否小于或等于 max 属性指定的值
minLength 检查字段长度是否大于或等于 min 属性指定的值
byte, short, integer, long, float, double 检查值是否可以转换为这些基本类型之一
date 检查值是否为有效的日期以及是否符合可选模式。
creditCard 检查值是否为有效的信用卡号
email 检查值是否为有效的电子邮件地址

如果您已经阅读了 Server-side Validators 设计主题,便会注意到 Struts 具有的标准验证器组要比 JavaServer Faces 技术所具有的验证器组更大。但是,由于 JavaServer Faces 验证模型非常灵活且可扩展,因此在不久的将来,JavaServer Faces 实现器和验证器以及组件的供应商极有可能丰富这个标准验证器组的功能。

如果您的 JavaServer Faces 应用程序已使用了标准 JavaServer Faces 验证器,而且您希望添加客户端验证以便与该验证器一起使用,则可创建定制呈现器,如使用定制呈现器生成 JavaScript 中所述。但是,尽管引用实现提供了标准服务器端验证代码,当您将 JavaScript 添加到应用程序时,还是会存在两段冗余代码这一问题。

当您通过 Struts Validator 使 Struts 验证器组可供 JavaServer Faces 应用程序使用时,您便可解决这一问题。此时,您可以利用功能更强大的客户端验证框架,而且仍可获得在相同应用程序中使用 JavaServer Faces 技术的所有显著优点。

将 Struts Validator 添加到 JavaServer Faces 应用程序

将 Struts Validator 集成到您的 JavaServer Faces 应用程序需要涉及以下几个步骤:

  1. 下载 StrutsStruts-Faces 集成库
  2. 将以下 JAR 文件添加到 JavaServer Faces 应用程序的 WEB-INF/lib 目录中:struts.jarcommons-validator.jarcommons-lang.jarjakarta-oro.jar 和 struts-faces.jar
  3. 在部署描述符 (web.xml) 中声明 Struts servlet:
    <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>
    org.apache.struts.action.ActionServlet
    </servlet-class>
    <init-param>
    <param-name>config</param-name>
    <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
    </servlet>
  4. 将映射添加到 Struts servlet,如下所示。请确保您使用的映射类型(扩展名或前缀)与映射到 FacesServlet 时使用的映射类型相同。
  5. 	<servlet-mapping>
      <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
    </servlet-mapping>
  6. 声明要在您的应用程序中使用的所有 Struts TLD。不需要声明 struts-faces TLD。
  7. 将 Struts 项目附带的 validator-rules.xml 文件添加到应用程序的 WEB-INF 目录。该文件声明了服务器端验证器例程并定义了应用程序使用的相应客户端 JavaScript 代码。
  8. 将 struts-faces 集成库导入到您的页面:

    <%@ taglib prefix="s" uri="http://struts.apache.org/struts/tags-faces" %>

  9. 将 struts-faces 集成库的 javascript 标记添加到页面,可将其置于表单标记之外的某个位置:

    <s:javascript formName="myForm" />

  10. 使用 struts-faces 集成库中的 s:form 标记替换页面中的 h:form 标记。确保 form 标记具有一个 ID,并与 javascript 标记的 formName 属性匹配。
  11. onsubmit 属性添加到 form 标记,该属性可以调用 JavaScript 的 validate MyForm (this) 函数,其中 MyFormformName 属性和表单的 ID 匹配:
  12. <s:form id="myForm" onsubmit="return validateMyForm(this);">

  13. 创建一个名为 validator.xml 的 XML 文件,该文件用于定义如何在应用程序中应用 validator-rules.xml 中包含的验证器例程。例如,如果您的页面包含登录表单,而且希望限制 username 字段中的输入值长度(类似于标准 JavaServer Faces ValidateLength 验证器可以实现的操作),则可应用 Struts minlength 和 Struts maxlength 验证器。以下示例演示了将输入长度限制为至少 3 个字符且不超过 16 个字符的操作。如果特定类型的验证失败,则各个 arg0arg1arg2 标记都会引用显示的消息。

    <formset>
    <form name="logonForm">
    <field property="username"
    depends="minlength,maxlength">
    <arg0 key="prompt.username"/>
    <arg1 key="${var:minlength}" name="minlength"
    resource="false"/>
    <arg2 key="${var:maxlength}" name="maxlength"
    resource="false"/>
    <var>
    <var-name>maxlength</var-name>
    <var-value>16</var-value>
    </var>
    <var>
    <var-name>minlength</var-name>
    <var-value>3</var-value>
    </var>
    </field>
    ...
    </formset>
  14. 创建与需要验证的表单相对应的 ActionForm Bean。此类包含与表单的 UI 组件对应的属性。有关详细信息,请参阅 Struts 文档。
  15. 为了处理请求,请创建 Action 类。有关详细信息,仍请参阅 Struts 文档。
  16. 包括 struts-config.xml 文件,该文件指定验证消息的资源包、验证配置文件、操作映射以及所有表单 Bean(对于包含需要验证的字段的表单)。以下是各项内容的示例:
  	<form-beans>
<form-bean name="validatorForm"
type="com.sun.j2ee.blueprints.catalog.validator.LoginForm">
</form-bean>
</form-beans>
<action-mappings>
<action path="/logon"
type="com.sun.j2ee.blueprints.catalog.validator.LoginAction"
name="validatorForm"
scope="request"
input="validator"/>
</action-mappings>
<message-resources
parameter="com.sun.j2ee.bleuprints.catalog.validator.ApplicationMessages"/>

可以看出,使用一个标准 struts 验证器所需执行的操作远远多于使用一个标准服务器端 JavaServer Faces 验证器所需执行的操作。因此,如果您执行的不是客户端验证,并且某个 JavaServer Faces 标准验证器能够满足您的需要,则应使用该验证器。

另一方面,由于 Struts Validator 可以提供更大的标准验证器组,而且每个验证器都内置有客户端验证代码,因此在下列情况下,您应该考虑使用 Struts Validator:

如果要为非 HTML 客户端执行客户端验证,则需使用 JavaServer Faces 验证器,因为您需要一个定制呈现器来为非 HTML 客户端正确呈现脚本。

使用 Struts Validator 实现定制验证器

Struts Validator 还可以用于在 JavaServer Faces 应用程序中实现定制验证器。任何标准 Struts 验证器都不能执行将客户端脚本添加到 JavaServer Faces 页中的示例验证代码。无论您使用的是 JavaServer Faces 验证模型还是 Struts Validator,您都必须实现此示例中的定制验证器。如果该应用程序仅在 HTML 客户端上运行,则可使用 Struts,这可能是一个更好的解决方案,因为它只需要较少的操作便能实现定制验证器,并且它的集中规则文件使得维护验证代码更为轻松。

要创建并使用定制验证器,您需要执行以下操作:

  1. validation.xml 文件中定义验证规则。
  2. 实现处理服务器端验证的方法。
  3. 使用表示新验证器的 validator 元素来更新 validator-rules.xml 文件。新元素还将定义客户端 JavaScript 代码。

创建该定制验证器不属于本主题讨论范围;我们将其留给读者作为练习。值得注意的一点是:执行验证所用的规则是在集中的验证文件中定义的,该文件是应用程序所特有的。更改该文件中的规则将会更改服务器端和客户端验证行为,因为每一种验证的代码都会访问该文件中的规则。

您可以使示例中执行验证的代码更具有通用性,这样代码便可以比较任何一组 String 的值,从而使其区别于 ColorValidator 或 ColorRenderer,这两个代码仅检查输入值是否与字符串 "red"、"green" 或 "blue" 相匹配。您可以定义哪些字符串要在 validation.xml 中进行比较。当然,这意味着您在同步客户端和服务器端代码时会感觉更轻松。

创建了自己的验证器之后,您需要在包括标准 Struts 验证器声明和定义的同一个 validator-rules.xml 文件中声明您的验证器并定义其相应的客户端 JavaScript。

有关使用 Struts 创建定制验证器的帮助,请阅读《Struts Validator Guide》中的 "Pluggable Validators"(可插接式验证器)部分。

使用 Struts Validator 创建定制验证器具有许多优点。通过在不考虑验证代码行为的情况下单独定义验证规则这一做法,您可以使验证代码更便于维护、更易于插接,并与应用程序代码的耦合更加松散。

参考资料

有关本主题的详细信息,请参阅以下资料:


© Sun Microsystems 2004。Java BluePrints Solutions Catalog 中的所有内容受版权保护,未经 Sun Microsystems 的明确书面许可,不得在其他产品中发布。