在JSF2中的防止表单重复提交

伪猫 发布于 2012/03/28 21:13
阅读 4K+
收藏 7
JSF
在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
加载中
0
菊子秋天
菊子秋天
非常好,学习了
0
崔钢
崔钢
加个js,把提交按钮变灰就可以了。不用如此的麻烦。
0
伪猫
伪猫

引用来自“崔钢”的答案

加个js,把提交按钮变灰就可以了。不用如此的麻烦。
主要是防止用浏览器的“回退按钮” 退回来再次提交的情况 
0
hantsy
hantsy

这是不可能发生的。。。非IE浏览器自己都有保护。

用 Seam 2(现在用 Seam3/JSF2)两年,基本都用 Conversation,一般提交操作跳转到其它页面,一提交Converation我会在事务提交过立即让它结束。

所以这是不可能发生的。

 

 

0
伪猫
伪猫

引用来自“hantsy”的答案

这是不可能发生的。。。非IE浏览器自己都有保护。

用 Seam 2(现在用 Seam3/JSF2)两年,基本都用 Conversation,一般提交操作跳转到其它页面,一提交Converation我会在事务提交过立即让它结束。

所以这是不可能发生的。

 

 

您是说    浏览器的“回退按钮” 退回来再次提交  也没问题? 另外,我没用过seam, 只用jsf-ri 从1.2到2.0
0
hantsy
hantsy
用 JSF 2 你可以提交时 redirect 到其它页面(或者当前View)。。。回退过去和打开新页面一样。jsf 2 新特性太多,很多是从 Seam2 移植过去,jsf 2中的 redirect, 支持 get 方式 这些都是从 seam2 中移植。
0
伪猫
伪猫

引用来自“hantsy”的答案

用 JSF 2 你可以提交时 redirect 到其它页面(或者当前View)。。。回退过去和打开新页面一样。jsf 2 新特性太多,很多是从 Seam2 移植过去,jsf 2中的 redirect, 支持 get 方式 这些都是从 seam2 中移植。
我用的就是redirect, 这是因为浏览器缓存了前一个页面,退回去的时候,表单的内容不会变。 
0
hantsy
hantsy
那是因为你的 Backend Bean用的 Session Scoped吧。。。这和 Redirecte 没什么关系吧。
0
hantsy
hantsy
当然另外一个E Commerce 网站项目,客户要求不能留任何缓存,所以用一个 PhaseLisener 全局的禁用掉Cache了。
0
hantsy
hantsy

再说一个你这 Token的问题吧。假使按你说的,回退到输入页面之后,如果我是想重新输入一些数据,那不是不能再次提交了。

返回顶部
顶部