• SpringMVC防止重复提交


    作者:mylovepan

    推荐:GOODDEEP

    问题描述:

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

    实现思路:

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

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

    实现方式:

    TokenInterceptor.java

    package com.xxx.www.common.interceptor;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    import com.xxx.cache.redis.IRedisCacheClient;
    import com.xxx.common.utility.JsonUtil;
    import com.xxx.www.common.utils.TokenHelper;
    
    /**
     * 
     * @see TokenHelper
     */
    public class TokenInterceptor extends HandlerInterceptorAdapter
    {  
        private static Logger log = Logger.getLogger(TokenInterceptor.class);
        private static Map<String , String> viewUrls = new HashMap<String , String>();
        private static Map<String , String> actionUrls = new HashMap<String , String>();
        private Object clock = new Object();    
        @Autowired
        private IRedisCacheClient redisCacheClient;
        static
        {
            viewUrls.put("/user/regc/brandregnamecard/", "GET");
            viewUrls.put("/user/regc/regnamecard/", "GET");     
            actionUrls.put("/user/regc/brandregnamecard/", "POST");
            actionUrls.put("/user/regc/regnamecard/", "POST");
        }
        {
            TokenHelper.setRedisCacheClient(redisCacheClient);
        }    
        /**
         * 拦截方法,添加or验证token
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
        {
            String url = request.getRequestURI();
            String method = request.getMethod();
            if(viewUrls.keySet().contains(url) && ((viewUrls.get(url)) == null || viewUrls.get(url).equals(method)))
            {
                TokenHelper.setToken(request);
                return true;
            }
            else if(actionUrls.keySet().contains(url) && ((actionUrls.get(url)) == null || actionUrls.get(url).equals(method)))
            {
                log.debug("Intercepting invocation to check for valid transaction token.");
                return handleToken(request, response, handler);
            }
            return true;
        }    
        protected boolean handleToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
        {
            synchronized(clock)
            {
                if(!TokenHelper.validToken(request))
                {
                    System.out.println("未通过验证...");
                    return handleInvalidToken(request, response, handler);
                }
            }
            System.out.println("通过验证...");
            return handleValidToken(request, response, handler);
        }
        
        /**
         * 当出现一个非法令牌时调用
         */
        protected boolean handleInvalidToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
        {
            Map<String , Object> data = new HashMap<String , Object>();
            data.put("flag", 0);
            data.put("msg", "请不要频繁操作!");
            writeMessageUtf8(response, data);
            return false;
        }    
        /**
         * 当发现一个合法令牌时调用.
         */
        protected boolean handleValidToken(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
        {
            return true;
        }
        
        private void writeMessageUtf8(HttpServletResponse response, Map<String , Object> json) throws IOException
        {
            try
            {
                response.setCharacterEncoding("UTF-8");
                response.getWriter().print(JsonUtil.toJson(json));
            }
            finally
            {
                response.getWriter().close();
            }
        }
        
    }
    TokenHelper.java
    package com.xxx.www.common.utils;
    import java.math.BigInteger;
    import java.util.Map;
    import java.util.Random;
    import javax.servlet.http.HttpServletRequest;
    import org.apache.log4j.Logger;
    import com.xxx.cache.redis.IRedisCacheClient;
    
    /**
     * TokenHelper
     * 
     */
    public class TokenHelper
    {   
        /**
         * 保存token值的默认命名空间
         */
        public static final String TOKEN_NAMESPACE = "xxx.tokens";    
        /**
         * 持有token名称的字段名
         */
        public static final String TOKEN_NAME_FIELD = "xxx.token.name";
        private static final Logger LOG = Logger.getLogger(TokenHelper.class);
        private static final Random RANDOM = new Random();  
        private static IRedisCacheClient redisCacheClient;// 缓存调用,代替session,支持分布式
        public static void setRedisCacheClient(IRedisCacheClient redisCacheClient)
        {
            TokenHelper.redisCacheClient = redisCacheClient;
        }    
        /**
         * 使用随机字串作为token名字保存token
         * 
         * @param request
         * @return token
         */
        public static String setToken(HttpServletRequest request)
        {
            return setToken(request, generateGUID());
        }    
        /**
         * 使用给定的字串作为token名字保存token
         * 
         * @param request
         * @param tokenName
         * @return token
         */
        private static String setToken(HttpServletRequest request, String tokenName)
        {
            String token = generateGUID();
            setCacheToken(request, tokenName, token);
            return token;
        }    
        /**
         * 保存一个给定名字和值的token
         * 
         * @param request
         * @param tokenName
         * @param token
         */
        private static void setCacheToken(HttpServletRequest request, String tokenName, String token)
        {
            try
            {
                String tokenName0 = buildTokenCacheAttributeName(tokenName);
                redisCacheClient.listLpush(tokenName0, token);
                request.setAttribute(TOKEN_NAME_FIELD, tokenName);
                request.setAttribute(tokenName, token);
            }
            catch(IllegalStateException e)
            {
                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();
                LOG.error(msg, e);
                throw new IllegalArgumentException(msg);
            }
        }    
        /**
         * 构建一个基于token名字的带有命名空间为前缀的token名字
         * 
         * @param tokenName
         * @return the name space prefixed session token name
         */
        public static String buildTokenCacheAttributeName(String tokenName)
        {
            return TOKEN_NAMESPACE + "." + tokenName;
        }
        
        /**
         * 从请求域中获取给定token名字的token值
         * 
         * @param tokenName
         * @return the token String or null, if the token could not be found
         */
        public static String getToken(HttpServletRequest request, String tokenName)
        {
            if(tokenName == null)
            {
                return null;
            }
            Map params = request.getParameterMap();
            String[] tokens = (String[]) (String[]) params.get(tokenName);
            String token;
            if((tokens == null) || (tokens.length < 1))
            {
                LOG.warn("Could not find token mapped to token name " + tokenName);
                return null;
            }
            
            token = tokens[0];
            return token;
        }    
        /**
         * 从请求参数中获取token名字
         * 
         * @return the token name found in the params, or null if it could not be found
         */
        public static String getTokenName(HttpServletRequest request)
        {
            Map params = request.getParameterMap();      
            if(!params.containsKey(TOKEN_NAME_FIELD))
            {
                LOG.warn("Could not find token name in params.");
                return null;
            }        
            String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);
            String tokenName;        
            if((tokenNames == null) || (tokenNames.length < 1))
            {
                LOG.warn("Got a null or empty token name.");
                return null;
            }      
            tokenName = tokenNames[0];   
            return tokenName;
        }    
        /**
         * 验证当前请求参数中的token是否合法,如果合法的token出现就会删除它,它不会再次成功合法的token
         * 
         * @return 验证结果
         */
        public static boolean validToken(HttpServletRequest request)
        {
            String tokenName = getTokenName(request);        
            if(tokenName == null)
            {
                LOG.debug("no token name found -> Invalid token ");
                return false;
            }        
            String token = getToken(request, tokenName);        
            if(token == null)
            {
                if(LOG.isDebugEnabled())
                {
                    LOG.debug("no token found for token name " + tokenName + " -> Invalid token ");
                }
                return false;
            }        
            String tokenCacheName = buildTokenCacheAttributeName(tokenName);
            String cacheToken = redisCacheClient.listLpop(tokenCacheName);        
            if(!token.equals(cacheToken))
            {
                LOG.warn("xxx.internal.invalid.token Form token " + token + " does not match the session token " + cacheToken + ".");
                return false;
            }        
            // remove the token so it won't be used again        
            return true;
        }   
        public static String generateGUID()
        {
            return new BigInteger(165, RANDOM).toString(36).toUpperCase();
        }
        
    }

    spring-mvc.xml

    <!-- token拦截器-->
        <bean id="tokenInterceptor" class="com.xxx.www.common.interceptor.TokenInterceptor"></bean>    
        <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">    
            <property name="interceptors">    
                <list>    
                    <ref bean="tokenInterceptor"/>    
                </list>
            </property>    
        </bean>
    input.jsp 在form中加如下内容:
     <input type="hidden" name="<%=request.getAttribute("xxx.token.name") %>" value="<%=token %>"/>
    <input type="hidden" name="xxx.token.name" value="<%=request.getAttribute("xxx.token.name") %>"/>
    当前这里也可以用类似于struts2的自定义标签来做。
  • 相关阅读:
    C# List<T>中Select List Distinct()去重复
    Spring.Net 简单入门学习
    [ASP.NET MVC]:
    打车题
    Vue------发布订阅模式实现
    Vue----数据响应原理
    小程序自定义导航栏_navigationStyle
    CleanWebpackPlugin最新版本使用问题
    js-事件函数调用简化
    用XHR简单封装一个axios
  • 原文地址:https://www.cnblogs.com/lxl57610/p/7441978.html
Copyright © 2020-2023  润新知