`
m635674608
  • 浏览: 5029125 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

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

 
阅读更多

问题描述:

 

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

 

实现思路:

 

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

 

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

 

 

 

实现方式:

 

TokenInterceptor.java

 

[java] view plain copy在CODE上查看代码片派生到我的代码片
  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

[java] view plain copy在CODE上查看代码片派生到我的代码片
  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

[html] view plain copy在CODE上查看代码片派生到我的代码片
  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中加如下内容:

[html] view plain copy在CODE上查看代码片派生到我的代码片
  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

 

分享到:
评论

相关推荐

    springMVC中基于token防止表单重复提交方法

    SpringMVC中基于Token防止表单重复提交方法 SpringMVC中基于Token防止表单重复提交方法是指通过在SpringMVC配置文件中添加拦截器配置,来拦截页面请求和表单提交请求,以防止表单重复提交。下面是该方法的实现思路...

    token-springMVC 防止重复提交

    总的来说,"Token-SpringMVC"是Spring MVC框架中防止重复提交的一种实用策略,它通过令牌验证确保了请求的唯一性,从而保护了系统的数据一致性。在实际开发中,我们需要根据项目需求和安全级别来选择合适的防止重复...

    springMVC自定义防重复提交

    本篇将详细介绍如何在Spring MVC中自定义实现防重复提交机制,特别是通过使用“token”标签的方式。 首先,理解防重复提交的核心原理:在客户端请求时生成一个唯一的标识(token),并将这个标识存储在服务器端和...

    Token验证表单重复提交

    通过以上步骤,我们就成功地在SSM框架中实现了基于Token的表单重复提交验证。这种方法能够有效地防止由于网络延迟或其他原因导致的多次提交,确保了数据的一致性和安全性。需要注意的是,实际应用中可能还需要考虑...

    SpringMVC精品资源--整合JWT,spring,springMVC,实现基于token验证.zip

    【标题】中的“SpringMVC精品资源--整合JWT,spring,springMVC,实现基于token验证”是一个关于使用Spring框架,特别是SpringMVC,与JSON Web Tokens (JWT)集成的教程或项目。这个主题涉及了现代Web应用中常见的...

    Servlet、Struts、SpringMVC对于表单重复提交的解决方案

    2. **sessionToken机制**:Struts2框架提供了一个拦截器`TokenInterceptor`,它可以自动管理基于Session的令牌,防止重复提交。 3. **.strutsPrepareAndExecute interceptor**:这个拦截器可以处理表单的唯一性,...

    spring-token:整合JWT,spring,springMVC,实现基于token验证

    整合JWT,spring,springMVC,实现基于token验证 因为CSDN博客上 这篇文章好多小伙伴希望我分享一下源码, 我这边就这个功能单独拉出来写了一个小示例供大家分享。 因为这篇博客讲的是基于JWT和spring,springMVC的...

    Redis 与SpringMVC 集成 基于注解方式

    Redis 与SpringMVC 集成 基于注解方式

    基于springmvc实现文件上传下载 基于AOP的日志功能

    基于springmvc实现文件上传下载 基于AOP的日志功能基于springmvc实现文件上传下载 基于AOP的日志功能基于springmvc实现文件上传下载 基于AOP的日志功能基于springmvc实现文件上传下载 基于AOP的日志功能基于...

    基于SpringMVC开发网上书城.rar

    基于SpringMVC开发网上书城: 1、 书本商品的粗略展示 以及详细信息(价格、类别、简介)的展示。 2、 购物车内展示及购物车的增加和删除。 3、 购物车的提交订单以及订单的展示 4、 平台用户的管理,如:注册、登录...

    Spring In Action SpringMVC 提交表单

    《Spring In Action: SpringMVC 提交表单详解》 在Web开发中,SpringMVC作为Spring框架的一部分,是处理HTTP请求和响应的强大工具。它为开发者提供了构建高性能、易于测试的Web应用程序的结构。本篇文章将深入探讨...

    SpringMVC基于代码的配置方式(零配置,无web.xml)

    本文将详细介绍如何在不使用web.xml的情况下,通过Java代码实现SpringMVC的配置。 一、SpringMVC的零配置介绍 传统的SpringMVC配置方式需要在web.xml中声明DispatcherServlet,并配置相关的servlet-mapping。而在...

    Redis 与SpringMVC 基于代码方式 集成

    Redis 与SpringMVC 基于代码方式 集成

    Java基于Spring+SpringMVC+MyBatis实现的学生信息管理系统源码.zip

    Java基于Spring+SpringMVC+MyBatis实现的学生信息管理系统源码,SSM+Vue的学生管理系统。 Java基于Spring+SpringMVC+MyBatis实现的学生信息管理系统源码,SSM+Vue的学生管理系统。 Java基于Spring+SpringMVC+...

    Spring mvc防止数据重复提交的方法

    Spring MVC 防止数据重复提交的方法是使用 Token 机制来实现的,该机制通过在服务器端生成一个随机的 UUID,并将其存储在 Session 中,然后在客户端提交数据时带上该 UUID,服务器端在接收到该 UUID 后,对其进行...

    基于SpringMVC+Tomcat搭建的WEB工程

    在本文中,我们将深入探讨如何基于SpringMVC和Tomcat搭建一个WEB工程,这是一个常见的Web开发实践,尤其适合初学者入门。我们将从编程环境的设置、SpringMVC框架的介绍、Tomcat服务器的使用,以及项目目录结构等方面...

    基于springmvc高并发秒杀系统

    技术描述:基于SpringMVC,Spring,MyBatis实现的高并发秒杀系统。代码设计风格基于RESTful,以c3p0作为连接池,Redis数据库为媒介实现高并发技术。其中,对于相关的DAO,Service操作,均添加了Junit单元测试实例。 ...

    基于注解的springMVC简单的例子

    **基于注解的SpringMVC简介** SpringMVC是Spring框架的一个模块,主要负责处理Web应用的请求和响应。在传统的SpringMVC配置中,我们需要通过XML文件来配置控制器、视图解析器、模型-视图-控制器(MVC)的各个组件。...

    基于Spring+SpringMVC+Mybatis架构的博客系统.zip

    基于Spring+SpringMVC+Mybatis架构的博客系统:博客管理、图表数据、日志分析、访问记录、图库管理、资源管理、友链通知等。良好的页面预加载,无限滚动加载,文章置顶,博主推荐等。提供 用户端+管理端 的整套系统...

Global site tag (gtag.js) - Google Analytics