springmvc下的基于token的防重复提交

标签: springmvc token | 发表时间:2016-07-25 14:52 | 作者:
出处:http://m635674608.iteye.com

问题描述:

 

现在的网站在注册步骤中,由于后台要处理大量信息,造成响应变慢(测试机器性能差也是造成变慢的一个因素),在前端页面提交信息之前,等待后端响应,此时如果用户
再点一次提交按钮,后台会保存多份用户信息。为解决此问题,借鉴了struts2的token思路,在springmvc下实现token。

 

实现思路:

 

在springmvc配置文件中加入拦截器的配置,拦截两类请求,一类是到页面的,一类是提交表单的。当转到页面的请求到来时,生成token的名 字和token值,一份放到redis缓存中,一份放传给页面表单的隐藏域。(注:这里之所以使用redis缓存,是因为tomcat服务器是集群部署 的,要保证token的存储介质是全局线程安全的,而redis是单线程的)

 

当表单请求提交时,拦截器得到参数中的tokenName和token,然后到缓存中去取token值,如果能匹配上,请求就通过,不能匹配上就不 通过。这里的tokenName生成时也是随机的,每次请求都不一样。而从缓存中取token值时,会立即将其删除(删与读是原子的,无线程安全问题)。

 

 

 

实现方式:

 

TokenInterceptor.java

 

  1. package com.xxx.www.common.interceptor;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.HashMap;  
  5. import java.util.Map;  
  6. import javax.servlet.http.HttpServletRequest;  
  7. import javax.servlet.http.HttpServletResponse;  
  8. import org.apache.log4j.Logger;  
  9. import org.springframework.beans.factory.annotation.Autowired;  
  10. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;  
  11. import com.xxx.cache.redis.IRedisCacheClient;  
  12. import com.xxx.common.utility.JsonUtil;  
  13. import com.xxx.www.common.utils.TokenHelper;  
  14.   
  15. /** 
  16.  *  
  17.  * @see TokenHelper 
  18.  */  
  19. public class TokenInterceptor extends HandlerInterceptorAdapter  
  20. {  
  21.       
  22.     private static Logger log = Logger.getLogger(TokenInterceptor.class);  
  23.     private static Map<String , String> viewUrls = new HashMap<String , String>();  
  24.     private static Map<String , String> actionUrls = new HashMap<String , String>();  
  25.     private Object clock = new Object();  
  26.       
  27.     @Autowired  
  28.     private IRedisCacheClient redisCacheClient;  
  29.     static  
  30.     {  
  31.         viewUrls.put("/user/regc/brandregnamecard/", "GET");  
  32.         viewUrls.put("/user/regc/regnamecard/", "GET");  
  33.           
  34.         actionUrls.put("/user/regc/brandregnamecard/", "POST");  
  35.         actionUrls.put("/user/regc/regnamecard/", "POST");  
  36.     }  
  37.     {  
  38.         TokenHelper.setRedisCacheClient(redisCacheClient);  
  39.     }  
  40.       
  41.     /** 
  42.      * 拦截方法,添加or验证token 
  43.      */  
  44.     @Override  
  45.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception  
  46.     {  
  47.         String url = request.getRequestURI();  
  48.         String method = request.getMethod();  
  49.         if(viewUrls.keySet().contains(url) && ((viewUrls.get(url)) == null || viewUrls.get(url).equals(method)))  
  50.         {  
  51.             TokenHelper.setToken(request);  
  52.             return true;  
  53.         }  
  54.         else if(actionUrls.keySet().contains(url) && ((actionUrls.get(url)) == null || actionUrls.get(url).equals(method)))  
  55.         {  
  56.             log.debug("Intercepting invocation to check for valid transaction token.");  
  57.             return handleToken(request, response, handler);  
  58.         }  
  59.         return true;  
  60.     }  
  61.       
  62.     protected boolean handleToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception  
  63.     {  
  64.         synchronized(clock)  
  65.         {  
  66.             if(!TokenHelper.validToken(request))  
  67.             {  
  68.                 System.out.println("未通过验证...");  
  69.                 return handleInvalidToken(request, response, handler);  
  70.             }  
  71.         }  
  72.         System.out.println("通过验证...");  
  73.         return handleValidToken(request, response, handler);  
  74.     }  
  75.       
  76.     /** 
  77.      * 当出现一个非法令牌时调用 
  78.      */  
  79.     protected boolean handleInvalidToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception  
  80.     {  
  81.         Map<String , Object> data = new HashMap<String , Object>();  
  82.         data.put("flag", 0);  
  83.         data.put("msg", "请不要频繁操作!");  
  84.         writeMessageUtf8(response, data);  
  85.         return false;  
  86.     }  
  87.       
  88.     /** 
  89.      * 当发现一个合法令牌时调用. 
  90.      */  
  91.     protected boolean handleValidToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception  
  92.     {  
  93.         return true;  
  94.     }  
  95.       
  96.     private void writeMessageUtf8(HttpServletResponse response, Map<String , Object> json) throws IOException  
  97.     {  
  98.         try  
  99.         {  
  100.             response.setCharacterEncoding("UTF-8");  
  101.             response.getWriter().print(JsonUtil.toJson(json));  
  102.         }  
  103.         finally  
  104.         {  
  105.             response.getWriter().close();  
  106.         }  
  107.     }  
  108.       
  109. }  


TokenHelper.java

  1. package com.xxx.www.common.utils;  
  2.   
  3. import java.math.BigInteger;  
  4. import java.util.Map;  
  5. import java.util.Random;  
  6. import javax.servlet.http.HttpServletRequest;  
  7. import org.apache.log4j.Logger;  
  8. import com.xxx.cache.redis.IRedisCacheClient;  
  9.   
  10. /** 
  11.  * TokenHelper 
  12.  *  
  13.  */  
  14. public class TokenHelper  
  15. {  
  16.       
  17.     /** 
  18.      * 保存token值的默认命名空间 
  19.      */  
  20.     public static final String TOKEN_NAMESPACE = "xxx.tokens";  
  21.       
  22.     /** 
  23.      * 持有token名称的字段名 
  24.      */  
  25.     public static final String TOKEN_NAME_FIELD = "xxx.token.name";  
  26.     private static final Logger LOG = Logger.getLogger(TokenHelper.class);  
  27.     private static final Random RANDOM = new Random();  
  28.       
  29.     private static IRedisCacheClient redisCacheClient;// 缓存调用,代替session,支持分布式  
  30.       
  31.     public static void setRedisCacheClient(IRedisCacheClient redisCacheClient)  
  32.     {  
  33.         TokenHelper.redisCacheClient = redisCacheClient;  
  34.     }  
  35.       
  36.     /** 
  37.      * 使用随机字串作为token名字保存token 
  38.      *  
  39.      * @param request 
  40.      * @return token 
  41.      */  
  42.     public static String setToken(HttpServletRequest request)  
  43.     {  
  44.         return setToken(request, generateGUID());  
  45.     }  
  46.       
  47.     /** 
  48.      * 使用给定的字串作为token名字保存token 
  49.      *  
  50.      * @param request 
  51.      * @param tokenName 
  52.      * @return token 
  53.      */  
  54.     private static String setToken(HttpServletRequest request, String tokenName)  
  55.     {  
  56.         String token = generateGUID();  
  57.         setCacheToken(request, tokenName, token);  
  58.         return token;  
  59.     }  
  60.       
  61.     /** 
  62.      * 保存一个给定名字和值的token 
  63.      *  
  64.      * @param request 
  65.      * @param tokenName 
  66.      * @param token 
  67.      */  
  68.     private static void setCacheToken(HttpServletRequest request, String tokenName, String token)  
  69.     {  
  70.         try  
  71.         {  
  72.             String tokenName0 = buildTokenCacheAttributeName(tokenName);  
  73.             redisCacheClient.listLpush(tokenName0, token);  
  74.             request.setAttribute(TOKEN_NAME_FIELD, tokenName);  
  75.             request.setAttribute(tokenName, token);  
  76.         }  
  77.         catch(IllegalStateException e)  
  78.         {  
  79.             String msg = "Error creating HttpSession due response is commited to client. You can use the CreateSessionInterceptor or create the HttpSession from your action before the result is rendered to the client: " + e.getMessage();  
  80.             LOG.error(msg, e);  
  81.             throw new IllegalArgumentException(msg);  
  82.         }  
  83.     }  
  84.       
  85.     /** 
  86.      * 构建一个基于token名字的带有命名空间为前缀的token名字 
  87.      *  
  88.      * @param tokenName 
  89.      * @return the name space prefixed session token name 
  90.      */  
  91.     public static String buildTokenCacheAttributeName(String tokenName)  
  92.     {  
  93.         return TOKEN_NAMESPACE + "." + tokenName;  
  94.     }  
  95.       
  96.     /** 
  97.      * 从请求域中获取给定token名字的token值 
  98.      *  
  99.      * @param tokenName 
  100.      * @return the token String or null, if the token could not be found 
  101.      */  
  102.     public static String getToken(HttpServletRequest request, String tokenName)  
  103.     {  
  104.         if(tokenName == null)  
  105.         {  
  106.             return null;  
  107.         }  
  108.         Map params = request.getParameterMap();  
  109.         String[] tokens = (String[]) (String[]) params.get(tokenName);  
  110.         String token;  
  111.         if((tokens == null) || (tokens.length < 1))  
  112.         {  
  113.             LOG.warn("Could not find token mapped to token name " + tokenName);  
  114.             return null;  
  115.         }  
  116.           
  117.         token = tokens[0];  
  118.         return token;  
  119.     }  
  120.       
  121.     /** 
  122.      * 从请求参数中获取token名字 
  123.      *  
  124.      * @return the token name found in the params, or null if it could not be found 
  125.      */  
  126.     public static String getTokenName(HttpServletRequest request)  
  127.     {  
  128.         Map params = request.getParameterMap();  
  129.           
  130.         if(!params.containsKey(TOKEN_NAME_FIELD))  
  131.         {  
  132.             LOG.warn("Could not find token name in params.");  
  133.             return null;  
  134.         }  
  135.           
  136.         String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);  
  137.         String tokenName;  
  138.           
  139.         if((tokenNames == null) || (tokenNames.length < 1))  
  140.         {  
  141.             LOG.warn("Got a null or empty token name.");  
  142.             return null;  
  143.         }  
  144.           
  145.         tokenName = tokenNames[0];  
  146.           
  147.         return tokenName;  
  148.     }  
  149.       
  150.     /** 
  151.      * 验证当前请求参数中的token是否合法,如果合法的token出现就会删除它,它不会再次成功合法的token 
  152.      *  
  153.      * @return 验证结果 
  154.      */  
  155.     public static boolean validToken(HttpServletRequest request)  
  156.     {  
  157.         String tokenName = getTokenName(request);  
  158.           
  159.         if(tokenName == null)  
  160.         {  
  161.             LOG.debug("no token name found -> Invalid token ");  
  162.             return false;  
  163.         }  
  164.           
  165.         String token = getToken(request, tokenName);  
  166.           
  167.         if(token == null)  
  168.         {  
  169.             if(LOG.isDebugEnabled())  
  170.             {  
  171.                 LOG.debug("no token found for token name " + tokenName + " -> Invalid token ");  
  172.             }  
  173.             return false;  
  174.         }  
  175.           
  176.         String tokenCacheName = buildTokenCacheAttributeName(tokenName);  
  177.         String cacheToken = redisCacheClient.listLpop(tokenCacheName);  
  178.           
  179.         if(!token.equals(cacheToken))  
  180.         {  
  181.             LOG.warn("xxx.internal.invalid.token Form token " + token + " does not match the session token " + cacheToken + ".");  
  182.             return false;  
  183.         }  
  184.           
  185.         // remove the token so it won't be used again  
  186.           
  187.         return true;  
  188.     }  
  189.       
  190.     public static String generateGUID()  
  191.     {  
  192.         return new BigInteger(165, RANDOM).toString(36).toUpperCase();  
  193.     }  
  194.       
  195. }  


spring-mvc.xml

  1. <!-- token拦截器-->  
  2.     <bean id="tokenInterceptor" class="com.xxx.www.common.interceptor.TokenInterceptor"></bean>      
  3.     <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">      
  4.         <property name="interceptors">      
  5.             <list>      
  6.                 <ref bean="tokenInterceptor"/>      
  7.             </list>  
  8.         </property>      
  9.     </bean>  


input.jsp 在form中加如下内容:

  1. <input type="hidden" name="<%=request.getAttribute("xxx.token.name") %>" value="<%=token %>"/>  
  2.   
  3. <input type="hidden" name="xxx.token.name" value="<%=request.getAttribute("xxx.token.name") %>"/>  


当前这里也可以用类似于struts2的自定义标签来做。

另:公司域名做了隐藏,用xxx替换了。

 

http://blog.csdn.net/mylovepan/article/details/38894941

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [springmvc token] 推荐:

springmvc下的基于token的防重复提交

- - zzm
现在的网站在注册步骤中,由于后台要处理大量信息,造成响应变慢(测试机器性能差也是造成变慢的一个因素),在前端页面提交信息之前,等待后端响应,此时如果用户. 再点一次提交按钮,后台会保存多份用户信息. 为解决此问题,借鉴了struts2的token思路,在springmvc下实现token. 在springmvc配置文件中加入拦截器的配置,拦截两类请求,一类是到页面的,一类是提交表单的.

SpringMVC传参

- - 企业架构 - ITeye博客
Spring MVC 的请求参数获取的几种方法. 通过@PathVariabl注解获取路径中传递参数. 用@ModelAttribute注解获取POST请求的FORM表单数据. 直接用HttpServletRequest获取. 用注解@RequestParam绑定请求参数a到变量a. 当请求参数a不存在时会有异常发生,可以通过设置属性required=false解决,.

SpringMVC+ajaxfileupload上传

- - CSDN博客互联网推荐文章
看这篇的文章之前最好看一下上篇文章这样可以更好的理解. 整个项目的基本配置和上面差不多. 不同的是在webRoot文件夹下的js中引入jQuery.js 和ajaxfileupload.js. 如何没有这个两个js文件可以到各自的官网下载. DemoController.java   跳转到upload.jsp.

SpringMVC 注解配置

- - CSDN博客互联网推荐文章
在Spring项目开发中呢,最好是搞明白原理,其次装上Spring为eclipse开发的插件,这样会大大提高开发效率,而且减少了大量信息的记忆负担. SpringIDE插件,可自行到eclipse插件库中进行下载,还有其他Spring相关的plugin可以自行研究下. 当装好这个插件之后呢,可以根据向导创建一个简单的SpringMVC项目,大量的基本信息都可以自动生成,当然了是建立在明白原理的基础上,熟练了之后再去使用插件.

springmvc框架配置

- - CSDN博客编程语言推荐文章
. . .

RSA的SecureID token数据被偷了?

- ripwu - 张志强的网络日志
博客 » 记事本 » 密码学 ». WSJ报道:RSA承认其数据被偷,4000万SecureID token需要被更新. 中国银行银行密钥用的就是RSA生产,就是下图这玩意儿,手里有这玩意儿的同学们要小心了(当然,如果你的账户里的钱没有6位数以上,也不用太担心,毕竟网银的安全性不全依赖于这个设备):.

什么是 JWT -- JSON WEB TOKEN - 简书

- -
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(. (RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景. JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密.

struts1,struts2,springMVC终极对比

- - CSDN博客Web前端推荐文章
         最近做项目用到了struts2,之前一直是用struts1和springMVC. 感觉到了struts2从很大程度上和这两个还是有很大区别的,所以今天搜集了些资料,给他们做一下对比.          Struts1官方已经停止更新,现在用的也比较少,这里主要讲一下struts2和struts1比较都有哪些不同和进步.

springmvc文件上传下载

- - ITeye博客
在网上搜索的代码 参考整理了一份. commons-fileupload.jar与commons-io-1.4.jar二个文件. 1、表单属性为: enctype="multipart/form-data". 2、springmvc配置.

SpringMVC 拦截器 筛选

- - ITeye博客
 如果只配置拦截类似于*.do格式的url,则对静态资源的访问是没有问题的,但是如果配置拦截了所有的请求(如我们上面配置的“/”),就会造成js文件、css文件、图片文件等静态资源无法访问. 一般Web应用服务器默认的Servlet名称是"default",所以这里我们激活Tomcat的defaultServlet来处理静态文件.