服务器之家

服务器之家 > 正文

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

时间:2020-12-06 14:58     来源/作者:qq_641041990

本文介绍了springMVC中基于token防止表单重复提交方法,分享给大家,具体如下:

实现思路:

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

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

实现方式:

TokenInterceptor.Java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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

?
1
2
3
4
5
6
7
8
9
<!-- 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中加如下内容:

?
1
2
3
<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的自定义标签来做。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://blog.csdn.net/letter_believe/article/details/76034791

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享
歪歪漫画vip账号共享2020_yy漫画免费账号密码共享 2020-04-07
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址
电视剧《琉璃》全集在线观看 琉璃美人煞1-59集免费观看地址 2020-08-12
最新idea2020注册码永久激活(激活到2100年)
最新idea2020注册码永久激活(激活到2100年) 2020-07-29
返回顶部