防止表单重复提交机制在JSF2中的实现

在B/S系统开发过程中,关于如何防止表单的重复提交问题,也是一个老生常谈的问题,这里说说如何在JSF2的开发环境下防止表单重复提交。

问题解决的思路基本和struts的思路是一致的,那就是

  1. 生成一个字符串(token),放置在session里,
  2. 在表单生成时,同时把这个token作为表单的一部分,放置在一个hidden input中,
  3. 表单提交时,在backingbean中验证一下页面提交过来的token是否和session中的一致。
  4. 业务完成之后,重置一下token.

因为如果是用浏览器的后退按钮退回到表单页面的话,表单的内容是不会变化的,包括表单里面的token,这样在后退再 提交的时候,由于session中的token已经重置,这时候,我们就认为提交是失败的。

具体实现比较简单,经过2次重构,已经有了比较友好的使用体验。

首先是一个session级的bean, 用它来存储和操作token

/** * @author Bill * @version 2012-03-21 */ @SessionScoped @ManagedBean public class FormTokenBean { public static final String BEAN_NAME = "formTokenBean"; private String token; public String getToken() { return token; } public void setToken(String token) { this.token = token; } public String resetToken() { return token = "T" + System.nanoTime(); } public boolean validateToken(String token) { return token != null && token.equals(this.token); } @PostConstruct public void init () { resetToken(); } }

然后需要一个Tag,

/** * @author Bill * @version 2012-03-27 */ @FacesComponent("org.billxiong.faces.FormToken") public class FormTokenTag extends HtmlInputHidden{ public FormTokenTag() { setRendererType("javax.faces.Hidden"); // render as a standard InputHidden addValidator(new FormTokenValidator()); String token = FacesUtils.getObject("formTokenBean.token", String.class); setValue(token); } @Override public void decode(FacesContext context) { super.decode(context); String clientId = getClientId(context); String submittedValue = (String) context.getExternalContext() .getRequestParameterMap().get(clientId); if(submittedValue != null) { setSubmittedValue(submittedValue); } } }

在taglib中注册组件,

<tag> <tag-name>formToken</tag-name> <component> <component-type>org.billxiong.faces.FormToken</component-type> </component> <attribute> <name>id</name> <required>false</required> <type>java.lang.String</type> </attribute> <attribute> <name>validatorMessage</name> <required>false</required> <type>java.lang.String</type> </attribute> </tag>

如何验证Token是否有效呢?根据JSF的特点,编写一个Validator,

@FacesValidator("formTokenValidator") public class FormTokenValidator implements Validator{ @Override public void validate(FacesContext context, UIComponent uiComponent, Object o) throws ValidatorException { String token = o == null ? null : o.toString(); FormTokenBean tokenBean = FacesUtils.getObject(FormTokenBean.BEAN_NAME, FormTokenBean.class); if (null == token || null == tokenBean || !tokenBean.validateToken(token)) { throw new ValidatorException(new FacesMessage( FacesMessage.SEVERITY_ERROR, FacesUtils.getMessage("global.exception.tokenExpired"), "")); } } }

在validator中检查一下是不是和session中一致。

最后,看看页面中的使用,

<h:form prependId="false"> <pgfn:formToken/> <h:messages errorClass="error-msgs" errorStyle="color: red;"/> <h:commandButton id="btnSubmit" action="#{xxxBean.xxxMethod}" value="Submit}" /> </h:form>

总结:得益于JSF2的大幅改进,使得编写一个标签组件是如此的容易,另外,也要感谢一下struts提供的思路 🙂

本文系原创,作者: Bill

© 2015-2023, Bill X.