本文介紹了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