`
m635674608
  • 浏览: 4924143 次
  • 性别: 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

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics