在webWork中有Token标签,可以直接搞定重复提交的问题,但在Struts2.0以下的版本,传统的做法只有通过提供的Token编程来搞定,代码虽然不多但是,这样的细节涉及了两个Action,对于页面的跳转控制来说是一个额外的负担,必须处处小心,本文阐述了如何通过Filter通过配置来避免struts的Form重复提交问题。
核心代码如下:
package com.yapulan.util.filter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.log4j.Logger;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* 重复提交令牌自动加载器 功能:读取过滤器中设置的信息,读入令牌设置组
*/
public class TokenFilter implements Filter {
private String TokenConfig = "TokenConfig.xml";
private HashMap TokenMap = null;
private HashMap ErrorMap = null;
protected FilterConfig filterConfig;
static Logger logger = Logger.getLogger(TokenFilter.class.getName());
public void init(FilterConfig config) throws ServletException {
this.filterConfig = config;
this.TokenConfig = config.getInitParameter("tokenfile");
logger.debug("Tokenlist init OK!");
}
/**
* 初始化系统的xml文件,读入令牌列表
*/
@SuppressWarnings({ "unchecked", "deprecation" })
public void initConfig(ServletRequest srequest) {
HttpServletRequest httpRequest = (HttpServletRequest) srequest;
try {
TokenMap = new HashMap();
ErrorMap = new HashMap();
SAXReader reader = new SAXReader();
Document document = reader.read(httpRequest.getRealPath(this.TokenConfig));
List list1 = document.getRootElement().selectNodes("/TokenList/Token/TokenForm");
List list2 = document.getRootElement().selectNodes("/TokenList/Token/TokenAction");
List list3 = document.getRootElement().selectNodes("/TokenList/Token/ErrorPage");
Iterator iter1 = list1.iterator();
Iterator iter2 = list2.iterator();
Iterator iter3 = list3.iterator();
while (iter1.hasNext()&&iter2.hasNext()&&iter3.hasNext()) {
Element element1 = (Element) iter1.next();
Element element2 = (Element) iter2.next();
Element element3 = (Element) iter3.next();
TokenMap.put(element1.getStringValue(), element2.getStringValue());
ErrorMap.put(element1.getStringValue(), element3.getStringValue());
}
logger.debug("TokenFilter Read "
+ httpRequest.getRealPath(this.TokenConfig)
+ " is OK!");
} catch (Exception e) {
logger.error("TokenFilter Read "
+ httpRequest.getRealPath(this.TokenConfig)
+ " is Error!");
}
}
public void doFilter(ServletRequest srequest, ServletResponse sresponse,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) srequest;
try {
//取出实际的文件路径直接调用文件,如index.html,login.jsp等
String toURI = httpRequest.getRequestURI().replaceFirst(httpRequest.getContextPath(), "");
if (TokenMap==null)
{
initConfig(httpRequest);
}
//检测为提交jsp页
if (TokenMap.get(toURI) != null)
{
FromTokenAction token = new FromTokenAction();
token.execute(null, null, srequest, sresponse);
httpRequest.getSession().setAttribute("PRE_TOKEN_FORM", toURI);
logger.debug("TokenFilter save '"+toURI +"' at 'PRE_TOKEN_FORM' of Session!");
logger.debug("TokenFilter saveToken to '"+toURI +"' is OK!");
chain.doFilter(srequest, sresponse);
return;
}
@SuppressWarnings("unused")
String preURI =(String)httpRequest.getSession().getAttribute("PRE_TOKEN_FORM");
//检测到为Action接收提交页面
if (TokenMap.get(preURI).equals(toURI))
{
TOTokenAction token = new TOTokenAction();
token.execute(null, null, srequest, sresponse);
chain.doFilter(srequest, sresponse);
return;
}
} catch (Exception e)
{
logger.error(e);
}
chain.doFilter(srequest, sresponse);
}
public void setFilterConfig(final FilterConfig filterConfig) {
this.filterConfig = filterConfig;
}
public void destroy() {
TokenMap.clear();
ErrorMap.clear();
this.filterConfig = null;
}
//检测到需要令牌增加一个令牌
public class FromTokenAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
this.saveToken(request);
return null;
}
}
//到达Action前检测令牌
public class TOTokenAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
@SuppressWarnings("unused")
String preURI =(String)request.getSession().getAttribute("PRE_TOKEN_FORM");
//如果检测令牌错误执行错误页,正确将继续执行
if (!isTokenValid(request, true))
{
@SuppressWarnings("unused")
String toURI = (String)ErrorMap.get(preURI);
if (toURI!=null)
{
RequestDispatcher disp = request.getRequestDispatcher(toURI);
try {
disp.forward(request, response);
}catch(Exception e)
{
logger.error(e);
}
}
}
return null;
}
}
}
Web.xml的配制
<!-- 令牌自动加载配制 -->
<filter>
<filter-name>tokenFilter</filter-name>
<filter-class>com.yapulan.util.filter.TokenFilter</filter-class>
<init-param>
<param-name>tokenfile</param-name>
<param-value>/WEB-INF/TokenConfig.xml</param-value>
</init-param>
</filter>
注意:将代码包中web.xml做以下修改
<filter-mapping>
<filter-name>tokenFilter</filter-name>
<url-pattern/*</url-pattern>
</filter-mapping>
设置好过滤器,只要配置列表即可避免所有的重复提交问题,不必在编程时再次考虑了
<?xml version="1.0" encoding="UTF-8"?>
<TokenList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="TokenConfig.xsd">
<Token>
<TokenForm>/index.jsp</TokenForm>
<TokenAction>/TokenAction.do</TokenAction>
<ErrorPage>/error.html</ErrorPage>
</Token>
<Token>
<TokenForm>/index1.jsp</TokenForm>
<TokenAction>/TokenAction1.do</TokenAction>
<ErrorPage>/error1.jsp</ErrorPage>
</Token>
<Token>
<TokenForm>/index2.jsp</TokenForm>
<TokenAction>/TokenAction2.do</TokenAction>
<ErrorPage>/error2.jsp</ErrorPage>
</Token>
<Token>
<TokenForm>/index3.jsp</TokenForm>
<TokenAction>/TokenAction3.do</TokenAction>
<ErrorPage>/error3.jsp</ErrorPage>
</Token>
</TokenList>
注意:本代码可以很好的验证非法的提交,对于管理非法的提交是一个不可多得的具有一定安全意义封装。
请热心的朋友分析有无其它没有考虑的细节问题,并且是否有可以进一部完善的地方,谢谢!