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博客编程语言推荐文章
. . .

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来处理静态文件.

SpringMVC 消息转换器HttpMessageConverter

- - 企业架构 - ITeye博客
在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制. 还是回到请求-响应,也就是解析请求体,然后返回响应报文这个最基本的Http请求过程中来.

SpringMVC 限流 - CSDN博客

- -
在使用 SpringBoot做接口访问如何做接口的限流,这里我们可以使用google的Guava包来实现,当然我们也可以自己实现限流,Guava中的限流是久经考验的我们没必需重新再去写一个,如果想了解限流原理的同学可以自己查阅一下相关的资料,本文不作过来说明噢. 在项目中引入 Guava相关包.