您的位置:首页 > 其它

防止刷新或后退页面重复提交表单

2017-11-06 17:56 701 查看
刷新或后退页面会引起重复提交表单,为了避免这个头疼的问题发生,有几种方法可以实现。网上已经有很多实现的方式的思路,比如提交后禁用按钮、重定向和令牌,但前两种方式有时候不起作用或者说没有“安全感”,我觉得最好的实现方式应该是通过生成令牌(随机字符串)的方式由后端控制。

简单说下思路:

1.在呈现表单页面的时候,需要一个隐藏字段input,该值是后端随机生成的一个字符串(令牌)。该令牌存放在Session中。

2.页面提交时,同将这个令牌提交,后端接收该令牌,然后从Session中获取令牌,(然后立即移除Session中的该令牌属性,这样移除后可以保证一个提交请求只能对应一个令牌),将二者比较,不存在或不一样则判断为重复提交,此时可将页面定向到一个错误页面。

(下面使用是Java的MVC。)

1.跳转到表单页面

public static final String SESSION_ORDER_TOKEN="SESSION_ORDER_TOKEN";//订单提交令牌,防止重复提交
@Autowired
HttpSession session;
//表单
@RequestMapping(value = "/orderForm.do",method = RequestMethod.POST)
public String orderForm( ModelMap model) {
session.setAttribute(SESSION_ORDER_TOKEN, RandomUtil.generateString(16));//设置一个令牌,防止重复提交
return "views/weixin/order_form";
}


2.表单页面

<form action="weixin/orderPost.do" method="post">
<input type="hidden" name="token" value="${Session.SESSION_ORDER_TOKEN!''}" >
<input ...>
<input ...>
</form>


3.订单提交

//提交订单
@RequestMapping(value = "orderPost.do",method = RequestMethod.POST)
public String orderPost(String user_id,String token, ModelMap model) {
Object obj = session.getAttribute(SESSION_ORDER_TOKEN);//获得令牌
if(obj==null) return "views/weixin/request_timeout";
//移除令牌  无论成功还是失败
session.removeAttribute(SESSION_ORDER_TOKEN);
String mToken = (String) obj;
if(token==null||!mToken.equals(token)) return "views/weixin/request_timeout";
if(user_id==null||!user_id.equals(loginUser.getCustomerId()+"")){
return "redirect:/weixin/index.do";//如果用户标识不一致,则该笔订单无效,重定向到首页
}
try {
//业务逻辑
...
} catch (Exception e) {
e.printStackTrace();
return "views/weixin/order_post_failure";
}
return "views/weixin/order_post_succeed";
}


4.重复提交的错误页面 request_timeout.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Error</title>
<meta name="viewport"
content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">

<link rel="stylesheet"
href="${request.contextPath}/resources/mui/css/mui.min.css">

</head>
<body>
<div style="text-align: center;padding: 3px;">
<p style="font-size:18px;font-family: '微软雅黑';">
<h4>请求过时或请求页面已经失效!
</p>
<p style=" margin-bottom:20px;">您可以
<a href="${request.contextPath}/weixin/index.do">返回首页</a>
或去 <a href="${request.contextPath}/weixin/orderMgr.do">用户中心</a></p>
</div>
</body>
</html>


5.生成随机字符串的工具类RandomUtil.java

package sy.util;

import java.util.Random;

public class RandomUtil {
public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String LETTERCHAR = "abcdefghijkllmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String NUMBERCHAR = "0123456789";

/**
* 返回一个定长的随机字符串(只包含大小写字母、数字)
*
* @param length
*            随机字符串长度
* @return 随机字符串
*/
public static String generateString(int length) {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));
}
return sb.toString();
}

/**
* 返回一个定长的随机纯字母字符串(只包含大小写字母)
*
* @param length
*            随机字符串长度
* @return 随机字符串
*/
public static String generateMixString(int length) {
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(LETTERCHAR.charAt(random.nextInt(LETTERCHAR.length())));
}
return sb.toString();
}

/**
* 返回一个定长的随机纯大写字母字符串(只包含大小写字母)
*
* @param length
*            随机字符串长度
* @return 随机字符串
*/
public static String generateLowerString(int length) {
return generateMixString(length).toLowerCase();
}

/**
* 返回一个定长的随机纯小写字母字符串(只包含大小写字母)
*
* @param length
*            随机字符串长度
* @return 随机字符串
*/
public static String generateUpperString(int length) {
return generateMixString(length).toUpperCase();
}

/**
* 生成一个定长的纯0字符串
*
* @param length
*            字符串长度
* @return 纯0字符串
*/
public static String generateZeroString(int length) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
sb.append('0');
}
return sb.toString();
}

/**
* 根据数字生成一个定长的字符串,长度不够前面补0
*
* @param num
*            数字
* @param fixdlenth
*            字符串长度
* @return 定长的字符串
*/
public static String toFixdLengthString(long num, int fixdlenth) {
StringBuffer sb = new StringBuffer();
String strNum = String.valueOf(num);
if (fixdlenth - strNum.length() >= 0) {
sb.append(generateZeroString(fixdlenth - strNum.length()));
} else {
throw new RuntimeException("将数字" + num + "转化为长度为" + fixdlenth
+ "的字符串发生异常!");
}
sb.append(strNum);
return sb.toString();
}

/**
* 每次生成的len位数都不相同
*
* @param param
* @return 定长的数字
*/
public static int getNotSimple(int[] param, int len) {
Random rand = new Random();
for (int i = param.length; i > 1; i--) {
int index = rand.nextInt(i);
int tmp = param[index];
param[index] = param[i - 1];
param[i - 1] = tmp;
}
int result = 0;
for (int i = 0; i < len; i++) {
result = result * 10 + param[i];
}
return result;
}

public static void main(String[] args) {
System.out.println("返回一个定长的随机字符串(只包含大小写字母、数字):" + generateString(10));
System.out
.println("返回一个定长的随机纯字母字符串(只包含大小写字母):" + generateMixString(10));
System.out.println("返回一个定长的随机纯大写字母字符串(只包含大小写字母):"
+ generateLowerString(10));
System.out.println("返回一个定长的随机纯小写字母字符串(只包含大小写字母):"
+ generateUpperString(10));
System.out.println("生成一个定长的纯0字符串:" + generateZeroString(10));
System.out.println("根据数字生成一个定长的字符串,长度不够前面补0:"
+ toFixdLengthString(123, 10));
int[] in = { 1, 2, 3, 4, 5, 6, 7 };
System.out.println("每次生成的len位数都不相同:" + getNotSimple(in, 3));
}
}


PS:表单提交后,处理请求并将SESSION_ORDER_TOKEN从Session中移除掉了,所以之后无论是刷新还是后退页面再次提交,由于Session中已经没有令牌了,所以会定向到错误提示页面!

《道德经》第三十三章:

知人者智,自知者明。胜人者有力,自胜者强。知足者富,强行者有志,不失其所者久,死而不亡者寿。

译文:能了解、认识别人叫做智慧,能认识、了解自己才算聪明。能战胜别人是有力的,能克制自己的弱点才算刚强。知道满足的人才是富有人。坚持力行、努力不懈的就是有志。不离失本分的人就能长久不衰,身虽死而“道”仍存的,才算真正的长寿。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息