使用JSP标记库校验用户输入

80酷酷网    80kuku.com

  js

在任何一个基于Web的应用中,程序逻辑要求用户提交需要校验的信息,而应用的创建者则可以用两种方式来检测数据。第一种方法就是在客户端校验,甚至在信息提交到服务器上之前也可以进行。通常,这种校验使用运行在客户端因特网浏览器内的JavaScript就可以完成。尽管表格将要提交,但是脚本还是会检查所有请求的域,如果不符合就弹出错误信息。第二种方法就是在服务器端校验。在执行对数据的任何操作之前,使用应用服务器支持的技术来完成校验。

服务器端的校验使服务器更紧张,却给予了程序员更多的控制,并且保证了数据一定会检测。客户端的校验很容易被用户绕过(通过使JavaScript不能用),因此允许提交未检测的信息可以使速度更快,因为用户不必传送页到服务器,也不必从服务器上获取页。

目前,越来越多的在线应用依赖于服务器端的校验,主要原因是它保证了检测并且提高了应用的安全性,另一个原因是服务器硬件和软件性能在近几年内有较大的提高,还有一个原因是它虑及程序更大的灵活性并且往往易于实现。

目录

校验进程
JSP标记
具有校验功能的JSP视图
结论
代码列表

在本文中,我将讨论使用Java JSP标记库进行服务器端校验的唯一方法,并且简短的描述了JSP标记库的创建。我将在我以前文章"Applying MVC to web-based applications with JSP views"描述的Web应用上建立一个例子——一个在用户提交ZIP代码或者城市名之后显示天气信息的简单项目。该Web应用遵守Model-View-Controller (MVC) 结构并且使用Tomcat 应用服务器。

校验进程

在企业级应用中,数据校验是安全进程的必需部分。应用不但必须捕捉空域或者局部域,他还必须预防错误项。如果数据提交到RDBMS 存储器或者任何其他持久性存储器内,并且已用在SQL语句中,用户就能够附带(或者特意)提交导致SQL语句无效的坏数据。恶意用户甚至能够传递转义字符串或者将特殊数据输入到提交表格内,使他们执行Web应用开发者原本没有设计的任何操作。例如他们可以删除来自数据库的行或者表格,或者获取他们想要的信息。

我已经完成校验的JSP页在MVC设计中属于"Views" ,而MVC设计是在线天气程序的一部分。 每个JSP视图要么提交数据要么显示数据。当提交带有ZIP代码或者城市名的表格时,信息就被传递到服务器。在服务器上,Controller Servlet对象在特殊操作——来自于JSP的key 参数——的基础之上执行一个操作,然后重定向到下一个视图。更多细节我将在稍后讨论;现在我们先看看JSP标记的工作方式。

JSP标记

你可以将JSP标记看作是一个可由运行在服务器上的Java代码执行的自定义动作。在JSP中, 标记看起来就像一个标准的HTML标记,但是它的逻辑不在客户端上执行,而是作为JSP转换而成的Servlet 的一部分在服务器端执行。每个标记封装在一个单独的类中,并且他的名字和参数属性显示在带有tld 扩展名的特殊配置描述符文件内。该文件应该放在你的应用服务器内的web应用目录WEB-INF 下,并且在JSP中使用%taglib ... %指令显示该文件,在web.xml 文件中显示该文件。当JSP引擎遇到自定义标记时,它检测它自己是否知道标记类在哪里,如果知道的话,就执行相应的代码。标记类通常放在jar 文件内并且放在WEB-INF 下的lib 目录下。

Ex. ..\WEB-INF\mytags.tld - deployment descriptor file
Ex. ..\WEB-INF\lib\mytags.jar tag library (or a full directory structure)

为了展示如何使用标记进行校验,我创建了一个自定义JSP标记来校验ZIP代码域,名叫notValidZip。标记可以执行任何动作,如HTML格式化、域操作、或者甚至是数据库查询。为了其他的检测,我使用了来自Coldbeans Software (http://www.servletsuite.com)的标记库,也拥有了校验HTML表格域的非常真实的标记。Coldbeans Software可免费用于非商业用途。如果你需要在商业站点使用标记,你可以自己编写或者购买。

下面是一个配置描述符文件 validtag.tld ,它能够处理我的notValidZip标记:

<tag>
<name>notValidZip</name>
<tagclass>com.cj.valid.notValidZip</tagclass>
<bodycontent>JSP</bodycontent>
<info>tests zip code</info>

<attribute>
<name>value</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>

<attribute>
<name>length</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>

显示结果为:它的名字叫 notValidZip,它的代码在com.cj.valid.notValidZip 类中,他有两个属性,一个属性必需,一个属性备选。

为了让Web应用知道我包括了一个自定义标记库,我将这个标记库的信息添加到web.xml 配置描述符文件内。下面是代码:

<taglib>
<taglib-uri>/WEB-INF/validtag.tld</taglib-uri>
<taglib-location>/WEB-INF/validtag.tld</taglib-location>
</taglib>

最后,为了把这个标记库包括在JSP之内,我在我的JSP页首添加了这行代码:

<% taglib uri="/WEB-INF/validtag.tld" prefix="valTag" %>

现在,通过编写标记前缀就可以使用来自该软件包中的任何标记;例如:

<valTag:notValidZip value="<%=request.getParameter(\"ZIP\")%>">
<!-- body -->
</valTag:notValidZip>

标记通常有一个体和备选属性。

<valTag:tagname attribute1="value1" attribute2="value2">
<!-- body -->
</valTag:tagname>

但是有些标记可能没有体;例如

<valTag:tagname />.

在定义标记库时你可以编写你想要的任何前缀,但是标记名必须匹配tld 文件中的名。

正如我所说,真实的校验逻辑是在Java类中进行的,这个Java类也被称为"notValidZip",放在标记库jar 文件内。该标记类扩展了抽象TagSupport 类,并且引入了javax.servlet.jsp.JspException, javax.servlet.jsp.tagext. 标记和javax.servlet.jsp.tagext.TagSupport 类。主要的动作逻辑在doStartTag()方法内。 这个方法在JSP引擎遇到我的新标记时调用。

doStartTag()方法返回的整数代码告诉JSP引擎它是否应该操作标记的体。见本文末尾的Listing 1 。

现在我们总结一下。首先,你编写标记类,将它放在web应用'lib' 目录下的jar 文件中,为标记创建一个配置描述符文件,使用它的位置信息更新web.xml 文件,最后使用taglib 指令将它包括进来,你就可以在你的JSP中使用它了。如果我需要在多个地方进行ZIP代码校验,我只要将我的标记包括在多个页中。见Listing 2.

具有校验功能的JSP视图

通常要添加校验功能,程序员需要一个单独的JSP页,这个页看起来像原版表格页,如果表格域有问题,就显示错误信息,并且由服务器显示(重定向到)该页。我只在原版JSP页中添加了错误逻辑。表格第一次显示时不进行错误检测。在提交动作中,表格将提交到他自身上,并且使用JSP标记校验域(在服务器端, JSPs编译成Servlets),并且如果一切正确,数据传递到主要的Controller Servlet.。否则,用户在同一个JSP中可以看到错误信息。

在JSP页中,我有一个Java scriplet ,它可以创建逻辑标记变量"validate",并且如果有一个"validate"参数提交给该JSP,它的值为true。

<% boolean validate = ("true".equals((String)request.getParameter("validate"))); %>

基于此逻辑变量值,JSP使用我的标记进行校验。在页面第一次装载时,这个变量为false ,并且不需要校验。

为了提交页面给它本身,然后将它重定向到主要的Controller Servlet,我修改表格动作指向

<%=request.getRequestURI()%>

,并且默认JSP前进到标记

<jsp:forward page="../MainServlet" />。

在用户提交表格时,他将所有的值都传递给同一个JSP,设定变量validate 的值为true,检测使用的标记,并且如果数据得到认可,JSP将所有的值传递给Controller Servlet。

如果出现问题,标记体将执行,并且告诉JSP重新提交值到它本身上,然后根据错误的不同显示新的(或者不同的)错误信息。请注意:标记体内另外一个变量"success" 也要设定为false 。这个变量一开始设定为true ,并且只检查是否已经执行过任何标记体。这就保证了只有当"validate" 和 "success"变量都为真时才传递表格。见Listing 2.

如果你有多个域需要校验,来自标记体的错误信息将显示不正确的域,这就减少了用户纠正和重新提交的次数。

错误的ZIP代码产生如下页面:

正确的ZIP代码产生以下页面:

结论

我输入到JSP中的附加逻辑可以在此JSP页面中执行校验操作,将该逻辑与标记结合使用,就可以为服务器端的数据校验创建一个简单而又非常可再用的方案,而且不用使用多个JSP或者Servlets。校验任何一种类型的域只要一个标记,你可以拥有用于不同的域类型的专用标记;例如电子邮件、电话、或者只有整数的域。这种设计扩展了Model-View-Controller 项目的JSP视图层,并且我可以用它来增强来自逻辑层的图像的分离度。如果标记代码发生了改变,使用JSP标记校验的Web 设计师和开发者也不必修改JSP中的任何代码;而且,他们不必知道它是如何校验的或者他使用了何种Java语法。他们只要把类似HTML的标记包括到他们的JSP页面中就可以了。【original text】【Download source code】

代码列表

Listing 1

public int doStartTag() throws JspException {
// retrun code of 1 will cause tag body to execute
if (value == null)
return this.EVAL_BODY_INCLUDE; //check if we have zip code
if (value.equals(null))
//check if value is not null
return this.EVAL_BODY_INCLUDE;
if (value.length() == 5) {
//has to be an integer! short case of zip code xxxxx
try {
Integer.parseInt(value);
return this.SKIP_BODY;
} catch (NumberFormatException e) {
return this.EVAL_BODY_INCLUDE;
}
} else if (value.length() == 10) {
// long case of Zip code xxxxx-xxxx
String part1 = value.substring(0, 5);
String dash = value.substring(5, 6);
String part2 = value.substring(6);
if (!dash.equals("-"))
return this.EVAL_BODY_INCLUDE;
try {
Integer.parseInt(part1);
Integer.parseInt(part2);
return this.SKIP_BODY;
} catch (NumberFormatException e) {
return this.EVAL_BODY_INCLUDE;
}
}
return this.EVAL_BODY_INCLUDE; // all other cases

Listing 2

<FORM action="<%=request.getRequestURI()%>" method="post">
<TABLE border="1">
<input type="hidden" name="ACTIONKEY" value="WeatherAction.viewByZip">
<input type="hidden" name="REDIRECTKEY" value="weather_data">
<input type="hidden" name="validate" value="true">
<TBODY>
<TR>
<TD><font face=Arial size=2>Zip:</font></TD>
<TD>
<% if (validate) { %>
<valTag:notValidZip value="<%=request.getParameter(\"ZIP\")%>">
<!-- do something if field is empty -->
<font color=red face=arial size=2>
Please enter valid Zip Code
</font>

<% success=false; %>
</valTag:notValidZip>
<% } %>
<INPUT type="text" name="ZIP"
value="<%=((validate)?request.getParameter("ZIP"):"")%>">
<% if (success && validate) { %>
<jsp:forward page="../MainServlet" />
<% } %>
</TD>
</TR>
<TR>
<TD></TD>
<TD><INPUT type="submit" name="view" value="View"></TD>
</TR>
</TBODY>
</TABLE>
</FORM>

Listing 3

About the Author

Vlad Kofman 是一个从事政府国防合同中的项目的系统构造师。他还参与了主要的华尔街公司和美国政府的企业级项目。他的主要兴趣在于面向对象的程序设计方法和设计模式。




分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: