【开源中国 APP 全新上线】“动弹” 回归、集成大模型对话、畅读技术报告”
在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>
业务方法执行完毕,需要重置一下Token.
formTokenBean.resetToken();
总结:得益于JSF2的大幅改进,使得编写一个标签组件是如此的容易,另外,也要感谢一下struts提供的思路 :)
------------------------
Bill 2012/3/28
引用来自“崔钢”的答案
这是不可能发生的。。。非IE浏览器自己都有保护。
用 Seam 2(现在用 Seam3/JSF2)两年,基本都用 Conversation,一般提交操作跳转到其它页面,一提交Converation我会在事务提交过立即让它结束。
所以这是不可能发生的。
引用来自“hantsy”的答案
这是不可能发生的。。。非IE浏览器自己都有保护。
用 Seam 2(现在用 Seam3/JSF2)两年,基本都用 Conversation,一般提交操作跳转到其它页面,一提交Converation我会在事务提交过立即让它结束。
所以这是不可能发生的。
引用来自“hantsy”的答案
再说一个你这 Token的问题吧。假使按你说的,回退到输入页面之后,如果我是想重新输入一些数据,那不是不能再次提交了。