Token在struts中使用详解

1、你想发贴时,点击“我要发贴”链接的代码可以里这样的:
〈html:link action="subject.do?method=add"〉我要发贴〈/html:link〉
subject.do 和 method 这些在struct-config.xml如何定义我就不说了,点击链接后,会执行subject.do的add方法,代码如上面说的,跳转到subjectAdd.jsp页面。页面的代码大概如下:
〈html:form action="subjectForm.do?method=insert"〉
  〈html:text property="title" /〉
  〈html:textarea property="content" /〉
  〈html:submit property="发表" /〉
  〈html:reset property="重填" /〉
〈html:form〉
如果你在add方法里加了“saveToken(request);”这一句,那在subjectAdd.jsp生成的页面上,会多一个隐藏字段,类似于这样〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,2、点击发表后,表单提交到subjectForm.do里的insert方法后,你在insert方法里要将表单的数据插入到数据库中,如果没有进行重复提交的控制,那么每点击一次浏览器的刷新按钮,都会在数据库中插入一条相同的记录,增加下面的代码,你就可以控制用户的重复提交了。
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
注意,你必须在add方法里使用了saveToken(request),你才能在insert里判断,否则,你每次保存操作都是重复提交。
记住一点,Struts在你每次访问Action的时候,都会产生一个令牌,保存在你的Session里面,如果你在Action里的函数里面,使用了saveToken(request);,那么这个令牌也会保存在这个Action所Forward到的jsp所生成的静态页面里。
如果你在你Action的方法里使用了isTokenValid,那么Struts会将你从你的request里面去获取这个令牌值,然后和Session里的令牌值做比较,如果两者相等,就不是重复提交,如果不相等,就是重复提交了。

由于我们项目的所有Action都是继承自BaseDispatchAction这个类,所以我们基本上都是在这个类里面做了表单重复提交的控制,默认是控制add方法和insert方法,如果需要控制其它的方法,就自己手动写上面这些代码,否则是不需要手写的,控制的代码如下:
public abstract class BaseDispatchAction extends BaseAction {
protected ActionForward perform(ActionMapping mapping, ActionForm form,
    HttpServletRequest request, HttpServletResponse response)
    throws Exception {
        String parameter = mapping.getParameter();
        String name = request.getParameter(parameter);
if (null == name) { //如果没有指定 method ,则默认为 list
            name = "list";
        }

        if ("add".equals(name)) {
            if ("add".equals(name)) {
                saveToken(request);
            }
        } else if ("insert".equals(name)) {
            if (!isTokenValid(request, true)) {
                resetToken(request);
                saveError(request, new ActionMessage("error.repeatSubmit"));
                log.error("重复提交!");
                return mapping.findForward("error");
            }
        }
        return dispatchMethod2(mapping, form, request, response, name);
    }
}

注意:表单必须是struts标签!

原理:
1、在session中放入同步令牌。原理:调用TokenProcessor类的saveToken(HttpServletRequest arg0);源码如下:
在sesssion中放入名称为Globale.TRANSACTION_TOKEN_KEY的同步令牌:token
          public synchronized void saveToken(HttpServletRequest request)...{
            HttpSession session=request.getSession();
            String token=generateToken(request);
            if(token!=null)...{
               session.setAttribute(Globale.TRANSACTION_TOKEN_KEY,token);
            }
         }

2、在打开usermesg.jsp时,应用服务器遇到<html:form>时,便会调用FormTag类的renderToken()方法创建hidden元素。源码如下:
protected String renderToken()...{
               StringBuffer result=new StringBuffer();
               HttpSession session=pageContext.getSession();
               if(session!=null)...{
                  String token=(String)session.getAttribute(Globale.TRANSACTION_TOKEN_KEY);
                  if(token!=null)...{
                    result.append("<input type="hidden" name="");
                    result.append(Constants.TOKEN_KEY);
                    result.append("" value="");
                    result.append(token);
                    if(this.isXhtml)...{
                      result.append("" />");
                    }else...{
                      result.append("" >");
                    }
                  }
               }
               return result.toStriong();
         }
hidden元素Constants.TOKEN_KEY的值就是session中的名称为Globale.TRANSACTION_TOKEN_KEY的同步令牌值
3、验证同步令牌,原理:调用TokenProcessor的isTokenValid(HttpServletRequest arg0,boolean arg1)源码如下:
public synchronized boolean isTokenValid(HttpServletRequest request,boolean reset)...{
            HttpSession session request.getSession(false);
            if(session==null) return false;
            
            String saved=(String)session.getAttribute(Globale.TRANSACTION_TOKEN_KEY);
            
            if(saved==null) return false;
            
            if(reset) this.resetToken(request);

            String token=request.getParameter(Constants.TOKEN_KEY);
            
             if(token==null) return false;
            
             return saved.equals(token);
          }
从叶面取出同步令牌值和session中的进行比较。如果相等则为第一次,不等就是重复提交。前提就是传进来的参数reset必须是true。不然每次都相同。其中resetToken()方法如下:
                public synchronized void resetToken(HttpServletRequest request)...{
                    HttpSession session request.getSession(false);
                    if(session==null)...{
                      return;
                    }
                    session.removeAttribute(Globale.TRANSACTION_TOKEN_KEY);
                }
这就是在isTokenValid方法中boolean参数的作用:清除session中的同步令牌,避免重复提交。如果把true改为false 将不会起到避免重复提交的作用



另外:
建一个action基类TokenAction,使用模板方法,如下:

public ActionForward execute( ActionMapping mapping,
                    ActionForm form,
                    HttpServletRequest request,
                    HttpServletResponse response) throws Exception {
  ActionForward forward = null;
  if (isTokenValid(request, true)) {
    forward = doExecute(mapping, form, request, response);  
  } else {
    saveToken(request);
    throw new TokenRepeatException(MessageLocator.getMessage("error.token"));
  }
  return forward;
}

public abstract ActionForward doExecute( ActionMapping mapping,
                            ActionForm form,
                            HttpServletRequest request,
                            HttpServletResponse response) throws Exception;

注:TokenRepeatException是自定义异常,它的父类ApplicationException在struts-config.xml中被声明,以便于action捕获。如下(我的错误消息资源是用spring来配的,没有用struts的):

<struts-config>

<global-exceptions>
  <exception handler="com.chage.framework.struts.BusinessExceptionHandler" key="todo" type="com.chage.framework.exception.ApplicationException" />
  
</global-exceptions>

.....

</struts-config>

然后,观察这个异常处理类:

public class BusinessExceptionHandler extends ExceptionHandler {

public ActionForward execute(Exception exception,
                    ExceptionConfig config,
                    ActionMapping mapping,
                    ActionForm form,
                    HttpServletRequest request,
                    HttpServletResponse response) throws ServletException {
    request.setAttribute("exception", exception);
    if (exception instanceof TokenRepeatException) {
        return mapping.findForward("tokenRepeat");  
    } else
      //display error info in action come from
      return mapping.getInputForward();
}

}

有几个注意事项,记录备案:
1.关于异常信息哪里显示:   异常显示当然不能在表单页面上了,比如说,你成功更新了一条记录后,返回list的页面,当用户回退重复提交,再返回这个list页并显示提示信息是比较合理的,最好是定义另外一个forward,我统一转向forward名称为 “tokenRepeat“,每个需要控制重复提交的action中必须定义它。最后在jsp页上加上异常显示:

<logic:present name="exception">
<font color=red> <bean:write name="exception" property="message"/></font><br>
</logic:present>


2.你必须通过action来转发而不是直接进入jsp页,因为你得调用saveToken方法来记录令牌字。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值