您的位置:首页 > 编程语言 > Java开发

利用struts的同步令牌机制避免form的重复提交

2008-11-27 11:48 330 查看
 
基本原理: 

  服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。

    根据用户会话ID和当前系统时间来生成一个唯一(对于每个会话)令牌的,具体实现可以参考SynchroToken类中的generateToken()方法。

1、在待提交的jsp页面中生成令牌并保存在session和request(在form表单里增加一个hiddean元素);

    //设置同步令牌值,同时存放在session和request中
    SynchroToken synchroToken =
SynchroToken.getInstance();
    String token_Key =
synchroToken.generateToken(request);

    session.setAttribute(ItsConstants.TRANSACTION_TOKEN_KEY,token_Key);

    <input type="hidden" name="<%=ItsConstants.REQUEST_TOKEN_KEY
%>" value="<%=token_Key %>">

在存放常量的类ItsConstants事先定义2个常量:

    /** 处理重复表单提交存放在session中的key name */
    public static final String
TRANSACTION_TOKEN_KEY = "SS_TOKEN_NAME";
    /** 处理重复表单提交存放在REQUEST中的key
name */
    public static final String REQUEST_TOKEN_KEY = "RQ_TOKEN_NAME";

2、在servlet端进行验证;
boolean ifDoubleSubmit =
SynchroToken.getInstance().isTokenValid(HttpServletRequest, true);

方法isTokenValid()会比较自动获取session.getAttribute(ItsConstants.TRANSACTION_TOKEN_KEY)和req.getParameter(ItsConstants.REQUEST_TOKEN_KEY)的值并进行比较,该方法的第二个参数“true”表示比较完之后从session中删除该令牌session.removeAttribute(ItsConstants.TRANSACTION_TOKEN_KEY);

3、类SynchroToken的代码如下:
package common;
import
java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpSession;

/**
*
利用同步令牌(Token)机制来解决Web应用中重复提交的问题
*
* @author Tony Lin
* @since
2008-9-9
* @version 1.0
* @see Struts中的TokenProcessor类
*/
public
class SynchroToken {
    /**
     * The singleton instance of this
class.
     */
    private static SynchroToken instance = new
SynchroToken();
    /**
     * Retrieves the singleton instance of this
class.
     */
    public static SynchroToken getInstance() {

        return instance;
    }
    /**
     * Protected
constructor for TokenProcessor.  Use TokenProcessor.getInstance()
     * to
obtain a reference to the processor.
     */
    protected
SynchroToken() {
        super();
    }
    /**
     * Return
<code>true</code> if there is a transaction token stored in
    
* the user's current session, and the value submitted as a request
     *
parameter with this action matches it.  Returns <code>false</code>

     * under any of the following circumstances:
     * <ul>

     * <li>No session associated with this request</li>
    
* <li>No transaction token saved in the session</li>
     *
<li>No transaction token included as a request parameter</li>

     * <li>The included transaction token value does not match the

     *     transaction token in the user's session</li>
     *
</ul>
     *
     * @param request The servlet request we are
processing
     */
    public synchronized boolean
isTokenValid(HttpServletRequest request) {
        return
this.isTokenValid(request, false);
    }
    /**
     * Return
<code>true</code> if there is a transaction token stored in
    
* the user's current session, and the value submitted as a request
     *
parameter with this action matches it.  Returns <code>false</code>

     * <ul>
     * <li>No session associated with this
request</li>
     * <li>No transaction token saved in the
session</li>
     * <li>No transaction token included as a
request parameter</li>
     * <li>The included transaction token
value does not match the
     *     transaction token in the user's
session</li>
     * </ul>
     *
     * @param request
The servlet request we are processing
     * @param reset Should we reset
the token after checking it?
     */
    public synchronized boolean
isTokenValid(
        HttpServletRequest request,
        boolean reset)
{
        // Retrieve the current session for this request

        HttpSession session = request.getSession(false);
        if
(session == null) {
            return false;
        }
        //
Retrieve the transaction token from this session, and
        // reset it if
requested
        String saved = (String)
session.getAttribute(ItsConstants.TRANSACTION_TOKEN_KEY);        
        if
(saved == null) {
            return false;
        }
        if
(reset) {
            this.resetToken(request);
        }
        //
Retrieve the transaction token included in this request
        String token
= request.getParameter(ItsConstants.REQUEST_TOKEN_KEY);
        if (token ==
null) {
            return false;
        }
        return
saved.equals(token);
    }
    /**
     * Reset the saved
transaction token in the user's session.  This
     * indicates that
transactional token checking will not be needed
     * on the next request
that is submitted.
     *
     * @param request The servlet request we
are processing
     */
    public synchronized void
resetToken(HttpServletRequest request) {
        HttpSession session =
request.getSession(false);
        if (session == null) {

            return;
        }

        session.removeAttribute(ItsConstants.TRANSACTION_TOKEN_KEY);

    }
    /**
     * Save a new transaction token in the user's
current session, creating
     * a new session if necessary.
     *

     * @param request The servlet request we are processing
     */

    public synchronized void saveToken(HttpServletRequest request) {

        HttpSession session = request.getSession();
        String token
= generateToken(request);
        if (token != null) {

            session.setAttribute(ItsConstants.TRANSACTION_TOKEN_KEY, token);

        }
    }
    /**
     * Generate a new transaction token,
to be used for enforcing a single
     * request for a particular
transaction.
     *
     * @param request The request we are processing

     */
    public String generateToken(HttpServletRequest request) {

        HttpSession session = request.getSession();
        try {

            byte id[] = session.getId().getBytes();
            byte
now[] = new Long(System.currentTimeMillis()).toString().getBytes();

            MessageDigest md = MessageDigest.getInstance("MD5");

            md.update(id);
            md.update(now);

            return this.toHex(md.digest());
        } catch
(IllegalStateException e) {
            return null;
        } catch
(NoSuchAlgorithmException e) {
            return null;
        }

    }
    /**
     * Convert a byte array to a String of hexadecimal
digits and return it.
     *<p>
    
*<strong>WARNING</strong>: This method is not part of
TokenProcessor's
     *public API.  It's provided for backward compatibility
only.
     *</p>
     * @param buffer The byte array to be
converted
     */
    public String toHex(byte buffer[]) {

        StringBuffer sb = new StringBuffer();
        String s = null;

        for (int i = 0; i < buffer.length; i++) {
            s =
Integer.toHexString((int) buffer[i] & 0xff);
            if (s.length()
< 2) {
                sb.append('0');
            }

            sb.append(s);
        }
        return sb.toString();

    }

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息