Struts_token的令牌机制

来源:互联网 发布:现场音效软件 编辑:程序博客网 时间:2024/06/05 21:53

      通常在普通的操作当中,我们不需要处理重复提交的,而且有很多方法来防止重复提交。比如在登陆过程中,通过使用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,证明已经重复,不允许提交

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 被扇巴掌脸肿了怎么办 分到上海市金鼎学校怎么办 被列入维稳对象怎么办? 资金涉及诈骗案冻结了怎么办 小米浏览器浏览记录找不到了怎么办 米聊账号封了怎么办 管家婆创业版管理员忘记密码怎么办 手机不记得密码了怎么办 手机不记得开锁密码怎么办 oppo手机不记得密码怎么办 电脑密码不记得了怎么办 vivo手机不记得密码了怎么办 运管把车扣了怎么办 大学通选课挂科怎么办 通识必修课挂了怎么办 我想开3d艺术馆怎么办 档案回原籍报到证怎么办 服刑的人孩子上学怎么办 长沙终身教育网用户名忘记了怎么办 乡下卖服装没生意怎么办 没能力没学历该怎么办 没有学历的我该怎么办 补过的牙掉了怎么办 法院判完被告不给钱怎么办 b证到期未继续教育怎么办 宝宝上幼儿园中午要用尿不湿怎么办 嫁到北京农村怎么办居住证 2020年没脱贫的农民怎么办 2020年农民的土地怎么办 车停在停车场被划怎么办 专升本差两分怎么办 入职需要学士学位证怎么办 不喜欢写科研项目又没编制怎么办 易学堂密码忘了怎么办 易班手机号换了怎么办 易班登录不上怎么办 易到手机号换了怎么办 海外留学没有教育部认证怎么办 七过月宝宝便秘怎么办 6个月孩子便秘怎么办 6个月婴儿便秘怎么办