ITEEDU

Struts Gossip: 伺服端窗体验证

  验证使用者的数据是否正确,对于Web应用程序来说是相当重要的,尤其是对一些私密资源的保护,只允许通过验证的使用者观看,这种需求对Web应用程序来说处处可见。

验证有很多形式,这边针对两个部份,一个是数据完整性验证,一个是数据正确性验证。

  • 数据完整性
指的是验证使用者是否输入了应用程序所要求的所有讯息,以及讯息的格式是否符合程序要求等等,例如应用程序可能要求使用者同时提供使用者名称与密码,并且密码长度不得少于6个字符。

  • 数据正确性验证
是在通过完整性验证之后的检查,例如应用程序检查使用者提供的使用者名称与密码是否符合数据库中的数据。

另外,验证的地点可以是在伺服端,也可以是在客户端。

  • 客户端
通常是透过JavaScript来完成,验证工作直接在客户端进行,不用传回给伺服端,可以节省网络与服务器资源。

  • 伺服端
只依赖客户端验证是不够的,毕竟客户端可能跳过它,所以伺服端也必须验证数据,这边要介绍的就是如何使用Struts的窗体对象进行伺服端数据验证。

其实很简单,只要在继承 ActionForm 之后,重新定义其validate()方法即可,不过这边要绕个弯,在不使用Struts标签的情况下要如何进行这项工作,这么作的目的,在于让您更了解Struts的运作流程。

如果您不想绕弯,记得下面的设定若配合Struts <html:messages> 标签的话会很方便的达成。

首先来看看窗体对象如何撰写:
UserForm.java?
package onlyfun.caterpillar; 

import java.util.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.Globals;
import org.apache.struts.util.MessageResources;

public class UserForm extends ActionForm {
private String username;
private String password;

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public void reset(ActionMapping mapping,
HttpServletRequest req) {
username = null;
password = null;
}

public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {

Map errModel = new HashMap();
MessageResources messageResources =
(MessageResources) request.getAttribute(
Globals.MESSAGES_KEY);

if(getUsername() == null ||
getUsername().length() < 1) {
String msg =
messageResources.getMessage(
"error.invalidUsername");
errModel.put("invalidUsername", msg);
}

if(getPassword() == null ||
getPassword().length() < 1) {
String msg =
messageResources.getMessage(
"error.invalidPassword");
errModel.put("invalidPassword", msg);
}

if(errModel.get("invalidUsername") == null &&
errModel.get("invalidPassword") == null) {
// no error happened
// return null to proceed the Action
return null;
}
else {
request.setAttribute("errors", errModel);

// fake codes, just tell RequestProcessor
// not to invoke Action
ActionErrors errors = new ActionErrors();
errors.add(ActionMessages.GLOBAL_MESSAGE,
new ActionMessage(""));
return errors;
}
}
}
主要看看validate()方法,在这边从request中取得了 MessageResources ,这是为了能取得讯息资源文件中的讯息设定,程序中使用一个Map对象来记录我们所发现的错误讯息,最后将之设定给request。

validate()方法要传回一个ActionErrors对象,它是ActionMesssage的子类别,如果想直接使用它们,搭配 Struts <html:messages>标签会比较方便,但这边先不打算使用。

RequestProcessor 根据validate()传回的ActionErrors是否为null或其中是否包括有ActionMessage对象,以判断是不是要进一步呼叫 Action 对象,如果发现传回的不是null或是包括有ActionMessage,中断接下来呼叫Action的流程,而直接跳到您所设定好的验证错误页面。

虽然这边不打算使用<html:messages>卷标,但希望上面的流程可以正常运作,所以当发现有错误时,仍然回传一个 ActionErrors对象,这只是用来满足RequestProcessor完成上述流程运作所需要的条件。。

当传回的ActionErrors不为null,则会跳至您所设定的验证错误页面,这是在struts-config.xml中设定:
struts-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">

<struts-config>
<form-beans>
<form-bean
name="userForm"
type="onlyfun.caterpillar.UserForm"/>
</form-beans>

<action-mappings>
<action
path="/login"
type="onlyfun.caterpillar.LoginAction"
name="userForm"
validate="true"
input="/WEB-INF/pages/fail.jsp">

<forward
name="helloUser"
path="/WEB-INF/pages/hello.jsp"/>
<forward
name="loginFail"
path="/WEB-INF/pages/fail.jsp"/>
</action>
</action-mappings>

<message-resources parameter="resources/messages"/>
</struts-config>
您必须设定<action>的validate属性为true,这样RequestProcessor才会执行validate()方法, <action>的input属性则是让您设定当验证错误时,应该导向的页面。

进一步的,看看LoginAction中要作的正确性验证,它检查使用者名称与密码是否符合设定:
LoginAction.java
package onlyfun.caterpillar;

import java.util.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.util.MessageResources;
import org.apache.commons.beanutils.PropertyUtils;

public class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {

String username = (String)
PropertyUtils.getSimpleProperty(
form, "username");
String password = (String)
PropertyUtils.getSimpleProperty(
form, "password");

request.setAttribute("username", username);

if(username.equals("caterpillar") &&
password.equals("1234")) {
return mapping.findForward("helloUser");
}

Map msgModel = new HashMap();

MessageResources messageResources =
(MessageResources) getResources(request);

msgModel.put("namePasswordMismatched",
messageResources.getMessage(
"message.namePasswordMismatched"));

request.setAttribute("messages", msgModel);

return mapping.findForward("loginFail");
}
}
这边使用了PropertyUtils辅助类别,这可以很方便的帮您取出窗体对象中的属性。这边还需要一个讯息档案来管理讯息:
message_zh_TW.properties
# errors
error.invalidUsername=使用者名称不得为空!
error.invalidPassword=密码不得为空!

# messages
message.namePasswordMismatched=使用者名称或密码输入错误!
来看看fail.jsp,这边并没有使用Struts标签,所以只要如下取出讯息即可:
fail.jsp
<html>
<head>
<title>Sorry!</title>
</head>
<body>
<H1>

${errors["invalidUsername"]}<br>
${errors["invalidPassword"]}<br>
${messages["namePasswordMismatched"]}

</H1>
<p>
<a href='/strutsapp/html/form.htm'>Login</a>
</body>

</html>
form.htm与hello.jsp就使用 使用 ActionForm 中介绍过的即可。

在不使用ActionMessage与Struts标签的情况下,撰写这样的程序好像复杂了一些,如果您想省点功夫,您可以考虑搭配 <html:messages> 来撰写这个程序。