`

Struts_token的令牌机制

 
阅读更多

      通常在普通的操作当中,我们不需要处理重复提交的,而且有很多方法来防止重复提交。比如在登陆过程中,通过使用redirect,可以让用户登陆之上重定向到后台首页界面,当用户刷新界面时就不会触发重复提交了。或者使用token,隐藏在表单中,当提交时进行token验证,验证失败也不让提交。这都是一般的做法。

 

    我们这次碰到的问题是重复提交本身就是一个错误,重复提交会导致一些相关数据的逻辑不再正确。而这些重复提交并不是通过普通的刷新界面,或者两次点击按钮来进行的。在普通的操作当中,我们可以通过一系列的手段,使得相应参数被清零,从而防止数据上的不正确。但是,在一种情况下,这些手段都不再有效,那就是并发的重复提交。

    并发重复提交,那就是在同一时间内(时间间隔可以缩短到0.X秒之内),在这种情况下,所有的常规逻辑都不再有效,因为多个请求,同时进入系统,系统已不能判断出这些请求是否是无效的,它们同时通过常规的重复逻辑判断,并最终在同一时间内将数据写入到数据库中,引起数据错误。

 

    举一个简单的例子,在系统中销售一个商品,首先通过该商品id进入到系统逻辑判断,判断此商品是否已售出,如果未售出,就进行数据存取操作。商品是否售出,是一个逻辑判断,是验证数据存储到数据库的一道门。在常规的判断当中,前一请求通过这道门之后,后一请求就不能通过了,因为验证为false。但在并发请求中,两个或多个请求同时通过了这道门,因为都是同时进入到判断,在判断之前都验证商品没有被售出,所以就同时进入到数据的存储当中。

 

    在常规的java开发中,对于这种情况,临界资源,通常是使用加锁来保证这种情况的先后顺序。但是加锁有一个问题即是,它是对于全局信息的加锁,即对整个将要销售的商品进行加锁了。对于BS应用来说,我们必须保证另一个操作人员的同一种商品的销售请求通过,即只限制同一个操作人员销售的并发请求,不限制多个操作人员不同请求的处理。

    在这种情况下,我们的加锁就不能简单的锁定在商品上,而是要锁定在与操作人员有关的信息上,这就是session。

 

    session是一个在单个操作人员整个操作过程中,与服务器端保持通信的惟一识别信息。在同一操作人员的多次请求当中,session始终保证是同一个对象,而不是多个对象,因为可以对其加锁。当同一操作人员多个请求进入时,可以通过session限制只能单向通行。

    本文正是通过使用session以及在session中加入token,来验证同一个操作人员是否进行了并发重复的请求,在后一个请求到来时,使用session中的token验证请求中的token是否一致,当不一致时,被认为是重复提交,将不准许通过。

 

    原理: 服务器端在处理客户端的请求之前,会将请求中包含的令牌值与保存在当前会话中的令牌值进行比较,看是否匹配。在处理完该请求后,并且在信息达到客户端之前,将产生一个新的令牌。该令牌值将会替换当前会话中的令牌值,并且传到客户端。这样如果用户回退到刚才的提交页面并再一次提交的话,客户端传过来的令牌与服务其中的令牌值不一致,从而有效的防止了提交。 实现: 首先在预添加的Action的execute()方法中创建并保存一个令牌 saveToken(request); 功能:创建一个新令牌值,并且将它保存到当前的session中,如果HttpSession对象不存在的话,就先创建这个对象。 由预添加的Action将令牌传到了添加的页面上,作为一个隐藏域。在添加页面提交给添加AddAction后,在execute()方法中: 先判断当前会话中的令牌值和请求中的令牌值是不是一致的: isTokenValid(request) 如果不是一致的,给出错误信息,并且通过saveToken(request);刷新令牌值。 如果是一致的,那么就执行sql语句保存(添加),再通过resetToken(request)方法删除当前会话中的令牌。

    整个流程可以由如下流程来表述:

 

客户端申请token

服务器端生成token,并存放在session中,同时将token发送到客户端

客户端存储token,在请求提交时,同时发送token信息

服务器端统一拦截同一个用户的所有请求,验证当前请求是否需要被验证(不是所有请求都验证重复提交)

验证session中token是否和用户请求中的token一致,如果一致则放行

session清除会话中的token,为下一次的token生成作准备

并发重复请求到来,验证token和请求token不一致,请求被拒绝

 

由以上的流程,我们整个实现需要以下几个东西

token生成器,负责生成token

客户token请求处理action,负责处理客户请求,并返回token信息

token拦截器,用于拦截指定的请求是否需要验证token

token请求拦截标识,用于标识哪些请求是需要被拦截的

客户端token请求处理方法,用于请求token,并存放于特定操作中,并在提交时发送到请求中

 

token生成器

    token生成器在这里使用了一个随机数来实现,即随机生成一个数字,即实现token生成,如下所示:

 

private static final Random random = new Random(System.currentTimeMillis());

public static final String TOKENPARAM = "session-token";

 

/** 生成一个token */

public static synchronized String generateToken(HttpSession session) {

    String s = String.valueOf(random.nextLong());

    session.setAttribute(TOKENPARAM, s);

    return s;

}

 

 

    token请求处理action

    请求处理action,即接收相应的请求,然后直接返回相对应的token即可,如下即为一个为ajax请求生成token的处理action:

 

public String generateTokenAjax() {

    String token = SessionTokenGenerator.generateToken(ServletActionContext.getRequest().getSession());

    AjaxSupport.sendSuccessText(token);

    return NONE;

}

 

    token请求拦截标识

    拦截标识,即表示哪些方法需要被拦截,这里可以使用注解来实现,即在要拦截的方法上追加类似@TokenNeed的注解,或者使用配置文件,将需要拦截的方法列表记录在配置文件中,在本文中,使用了一个配置文件来记录

 

    token拦截器

    token拦截器实现了我们所需要的拦截处理,在当碰到需要拦截的方法请求中,将同步进行token的判断和处理,并根据处理结果判断是否该继续放行或拦截之:

 

public String intercept(ActionInvocation invocation) throws  Exception {

    String action = invocation.getProxy().getAction().getClass().getName();

    String method = invocation.getProxy().getMethod();

    final HttpSession session = ServletActionContext.getRequest().getSession();

    if(includeMethodSet.contains(action + "." + method)) {

        synchronized(session) {

            String paramSessionToken = ServletActionContext.getRequest().getParameter(SessionTokenGenerator.TOKENPARAM);

            String sessionSessionToken = (String) session.getAttribute(SessionTokenGenerator.TOKENPARAM);

            if(sessionSessionToken == null || paramSessionToken == null || !paramSessionToken.equals(sessionSessionToken))

                return fail();

            session.removeAttribute(SessionTokenGenerator.TOKENPARAM);

        }

    }

    return invocation.invoke();

}

 

    如上即是判断处理的方法是否在拦截列表中,如果是,则取得参数中的token,再将其与session中的token相比,如果不一致,则直接返回fail,随后将其从session中移除。

 

    客户端token实现

    作为客户端,只需要在进行请求提交之前申请一个token,在请求时,将此token加到请求中即可。在本文中,有一个jquery的ajax方法来处理token请求,随后在进行ajax请求时将此token一起加入到param。如下即为token的jquery请求

 

m_ylf.token = function() {

    m_ylf.invoke("/token/generateToken",{}, function(re) {

        re = re["result"];

        window["session-token"] = re;

    });

}

  即在处理时将接收到的token放到window中,要提交请求时再将其从window中取出,一并提交即可,如下的统一ajax处理方法:

 

//追加session-token

if(window["session-token"])

    param["session-token"] = window["session-token"];

  至此,整个防session Token请求即完成。如果在客户端模拟多个请求中,首先会有一个请求被成功处理,其它的请求即直接返回类似“不能重复提交”的错误警告(对于ajax请求)。

 

==========================================================================================

struts2 重复提交拦截器用法 token与token-session

标签: token-session token 拦截器 struts2.0 it

    首先要在jsp的from标签里加入<s:token/>防重复提交标签,<s:token/> 生成如下的内容:(struts.token.name 标识哪个隐藏域存了 token 值)

     <input type="hidden" name="struts.token.name" value="struts.token"/>

     <input type="hidden" name="struts.token" value="7GXL55LPSGU19SDC9D3VP54I20XT3BVA"/>

注意自定义的表单域别重名了。它的作用是防止表单重复提交,每次加载页面 struts.token 的值都不一样,如果两次提交时该值一样,则认为是重复提交。此时要启用 TokenInterceptor(token) 拦截器,最好是也启用 TokenSessionStoreInterceptor(token-session) 拦截器,不然后台会出现错误提示:

 

2008-11-17 20:39:21 com.opensymphony.xwork2.interceptor.ParametersInterceptor setParameters

严重: ParametersInterceptor - [setParameters]: Unexpected Exception catched: Error setting expression 'struts.token' with value '[Ljava.lang.String;@1c2e163'

2008-11-17 20:39:21 com.opensymphony.xwork2.interceptor.ParametersInterceptor setParameters

严重: ParametersInterceptor - [setParameters]: Unexpected Exception catched: Error setting expression 'struts.token.name' with value '[Ljava.lang.String;@abaf8c'

 

但不影响使用。不过如果只有 token-session 拦截器却是不行的。

 

token 和 token-session 拦截器的启用,是在 struts.xml 配置文件中,既可以为包启用,也可以单独为某个 action 启用:

 

1) 为包启用 token 和 token-session

<package name="TestStruts" extends="struts-default">  

    <interceptors>  

    <interceptor-stack name="myStack">  

        <interceptor-ref name="token"/>  

        <interceptor-ref name="token-session"/>  

    <interceptor-ref name="defaultStack" />               

    </interceptor-stack>  

    </interceptors>  

    <default-interceptor-ref name="myStack" />  

    <action name="Login" class="com.unmi.struts2.action.LoginAction">  

        <result name="input">/login.jsp</result>  

        <result name="invalid.token">/exception.jsp</result>  

    </action>  

............................................................................ 

2) 为 Action 启用 token 和 token-session

<action name="Login" class="com.unmi.struts2.action.LoginAction">  

    <interceptor-ref name="token" />  

    <interceptor-ref name="token-session" />  

    <interceptor-ref name="defaultStack" />  

    <result name="input">/login.jsp</result>  

    <result name="invalid.token">/exception.jsp</result>   

</action> 

............................................................................

 

注意 token、token-session 和 defaultStack 的顺序要保证,还需要加上名为 "invalid.token" 的 result,当发现重复提交时转向到这个逻辑页,如 /exception.jsp,在 /exception.jsp 加上 <s:actionerror /> 在出现重复提交时就会提示:The form has already been processed or no token was supplied, please try again.

<interceptor-ref name="token"/> 

<interceptor-ref name="token-session"/>

<!--注意struts2.0 拦截器名字为token-session struts2.1.2 已经更改为tokenSession -->

token: 在活动中检查合法令牌(token), 防止表单的重复提交;

token-session: 同上, 但是在接到非法令牌时将提交的数据保存在session中;

 

=====================================================================

另有

1,先在一个Action中,调用saveToken(HttpServletRequest request)方法。然后转向带有表单的JSP页面。

 

2,在JSP页面提交表单给一个Action,再这个Action中进行是否为重复提交的判断。

              if (isTokenValid(request, true)) {

                  // 未重复提交时,正确的时候应该做的事情

                  return mapping.findForward("success");

              } else {

              // 重复提交时,需要做的事情

                  saveToken(request);

                  return mapping.findForward("error");

              }

 

Struts Token 机制:

1,  由第一个Action调用saveToken(HttpServletRequest request),这个方法内部实现如下:

    protected void saveToken(HttpServletRequest request) {

        token.saveToken(request);

}

 

token.saveToken(request);

这个方法的实现如下:

    public synchronized void saveToken(HttpServletRequest request) {

        HttpSession session = request.getSession();

        String token = generateToken(request);

        if (token != null) {

            session.setAttribute(Globals.TRANSACTION_TOKEN_KEY, token);

        }

}

 

 

这个方法调用generateToken方法实现如下:

    public synchronized void saveToken(HttpServletRequest request) {

        HttpSession session = request.getSession();

        String token = generateToken(request);

        if (token != null) {

            session.setAttribute(Globals.TRANSACTION_TOKEN_KEY, token);

        }

}

 

generateToken完毕后,将得到的唯一值setAttribute到session中。

            session.setAttribute(Globals.TRANSACTION_TOKEN_KEY, token);

Globals.TRANSACTION_TOKEN_KEY的值是:” org.apache.struts.action.TOKEN”

然后跳转到JSP页面。

 

2,  JSP页面的Struts自定义标签 <html:form>的标签类:org.apache.struts.taglib.html. FormTag 这个类的doStartTag()方法会调用本类的renderToken()方法。

    protected String renderToken() {

        StringBuffer results = new StringBuffer();

        HttpSession session = pageContext.getSession();

        if (session != null) {

            String token = (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY); 

            if (token != null) {

                results.append("<input type=\"hidden\" name=\"");

                results.append(Constants.TOKEN_KEY);

                results.append("\" value=\"");

                results.append(token);

                if (this.isXhtml()) {

                    results.append("\" />");

                } else {

                    results.append("\">");

                }

            }

        }

        return results.toString();

}

 

这样子会生成类似于 <input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"> 的隐藏标签。

然后提交到一个Action中,在Action中用isTokenValid()方法进行比较session中” org.apache.struts.action.TOKEN”的这个key所对应的值和提交来的request中的” org.apache.struts.action.TOKEN”的这个value是否一致。

如果为true,那么证明可以提交。如果为false,证明已经重复,不允许提交

分享到:
评论

相关推荐

    struts2_token控制刷新重复提交

    Struts2提供了一种名为“token”的机制来解决这个问题。 **什么是Token机制?** Token机制是一种防止重复提交的方法,它通过在客户端(通常是浏览器的session或者cookie)和服务器端存储一个唯一的令牌,确保每个...

    struts2的令牌机制

    Struts2的令牌机制是其防止重复提交和CSRF(跨站请求伪造)攻击的一种重要安全策略。在Web开发中,尤其是使用MVC框架如Struts2时,确保用户请求的唯一性和安全性至关重要。令牌机制就是这样的一个工具,它通过在表单...

    struts 令牌机制(Token)

    在Struts框架中,令牌机制(Token)是一种防止重复提交的有效方法,它主要用于处理表单数据的并发控制,防止用户意外或者恶意地多次提交表单,从而确保数据的一致性和安全性。 在Web应用中,用户可能会因为网络延迟...

    struts令牌token实例

    在你提供的压缩包文件"token"中,可能包含了一个简单的Struts2令牌实例,你可以通过查看源代码学习如何在实际项目中集成和使用令牌机制。学习和理解这个实例,将有助于你更好地理解和应用Struts2的令牌功能。

    利用Struts2的令牌机制。

    为了避免这种情况,Struts2引入了令牌(Token)机制。 令牌机制的工作原理是这样的: 1. 当用户访问一个需要防重提交的页面时,服务器会生成一个唯一的令牌,并将其存储到用户的session中。 2. 这个令牌会被嵌入到...

    Struts1.x令牌(Token)的使用.rar

    而Struts1.x的令牌机制(Token)则是防止重复提交、跨页请求攻击的重要手段。在此,我们将深入探讨Struts1.x令牌的使用方法及其背后的原理。 首先,理解为何需要令牌。在Web应用中,用户可能会意外或恶意地多次点击...

    struts+token机制解决表单重复提交

    在提供的压缩包文件"struts+token机制解决表单重复提交"中,可能包含了具体的Struts配置文件、Action类、Interceptor实现以及示例代码,可以帮助读者更深入地理解并实践这个机制。通过对这些代码的学习和研究,...

    struts2 令牌使用例子

    在处理表单提交时,为了防止重复提交或者跨站请求伪造(CSRF)攻击,Struts2引入了令牌机制。这个"struts2 令牌使用例子"是一个很好的实践教程,帮助开发者理解如何在实际应用中实施这一安全策略。 首先,我们需要...

    struts2token回退刷新

    而Struts2的Token机制要求每个提交都带有服务器生成的唯一令牌,这使得伪造表单无法通过验证,从而有效地防御了CSRF攻击。 **四、Struts2 Token配置** 在Struts2中,可以通过配置Action或者全局Interceptor来启用...

    Struts1.3 备忘笔记

    09 Struts_09Token : Structs的令牌机制,避免重复提交问题 10 Struts_10SmartUpload : structs的文件上传 11 Struts_11Internationalize : Structs的国际化和消息文件的使用 12 Struts_12GeneralApply : Structs的...

    用STRUTS的TOKEN机制解决表单重复提交,转载自:百度文库

    Struts的Token机制是Web应用中防止表单重复提交的一种常用方法。在处理表单提交时,如果用户意外地多次点击了提交按钮,可能会导致数据的重复录入,从而引起不必要的问题,例如订单重复、数据库数据异常等。为了解决...

    struts的令牌机制,防止重复提交

    ### Struts的令牌机制:防止重复提交 在Web应用程序开发中,特别是在基于MVC(Model-View-Controller)架构的应用程序如Struts框架中,防止重复提交是一个非常重要的功能。Struts框架内置了一种名为“令牌机制”的...

    struts2令牌

    ### Struts2令牌机制详解 #### 一、Struts2令牌机制概述 在Web应用程序开发过程中,为了防止重复提交表单或CSRF(跨站请求伪造)等安全问题,Struts2框架提供了一种名为“令牌”的机制。该机制通过在用户会话中...

    使用struts的同步令牌避免form的重复提交

    为了有效地防止Web应用程序中的表单重复提交问题,Struts框架提供了一种简单而强大的解决方案——同步令牌模式(Synchronization Token Pattern, STP)。下面详细介绍如何在Struts项目中实现这一功能。 1. **生成...

    struts token机制解决表单重复提交

    Struts Token机制的核心思想是在客户端(浏览器)和服务器端(应用服务器)之间维持一个唯一的令牌(Token)。当用户首次提交表单时,服务器会生成一个随机的、唯一标识的Token,并将其存储到用户的会话(Session)...

    struts1的令牌解决页面重复提交问题

    为了解决这个问题,Struts1引入了“令牌”机制,也称为Token Session或Token Interceptor。下面我们将详细探讨如何使用Struts1的令牌来防止页面重复提交。 ### 1. 令牌机制概述 令牌机制的基本思路是在用户提交...

    struts2令牌解决页面重复提交问题

    Struts2提供了一种称为“令牌”或“Token Session”的机制来解决这个问题。 令牌机制的工作原理是,在用户提交表单前,服务器会生成一个唯一的令牌,并将其添加到表单中隐藏字段或者放入session。当用户提交表单时...

    Struts之Token解决表单那重复提交

    本文将深入探讨如何在Struts框架中利用Token机制来解决这个问题。 一、表单重复提交问题 表单重复提交可能发生在用户点击提交按钮后,由于网络延迟或刷新页面等原因,导致请求被多次发送到服务器。如果不加以控制,...

Global site tag (gtag.js) - Google Analytics