在Spring中重复的表单提交
在Spring中遇到(Duplicate form submission)问题时,出现的原因是用户重复提交表单。为了解决这个问题,可以在表单提交成功后进行重定向操作。在返回ModelAndView时,确保View是一个RedirectView。从用户的角度来看,他们提交表单后会被重定向到另一个URL进行"GET"操作,这样就不会重复提交了。
需要注意的是,当使用重定向视图(Redirect View)时,模型属性会作为参数暴露在URL中。因此,你可能希望尽可能保持属性的简洁性。通常我会显示一个页面给用户,页面中并不包含任何唯一信息,只是一个"确认"消息。
代码示例:
@Controller public class FormController { @RequestMapping(value = "/submitForm", method = RequestMethod.POST) public ModelAndView submitForm(@ModelAttribute("form") Form form) { // 处理表单提交逻辑 // ... // 返回重定向视图 return new ModelAndView(new RedirectView("/confirm")); } @RequestMapping(value = "/confirm", method = RequestMethod.GET) public String showConfirmPage() { return "confirm"; } }
在上述示例中,表单提交后将被重定向到"/confirm"这个URL,显示一个"确认"页面给用户。这样即使用户重复提交,也只会重复访问这个确认页面,而不会重复执行表单提交逻辑。
在Spring框架中,存在一个问题称为“重复表单提交(Duplicate form submission)”。这个问题的出现原因是用户在提交表单时,可能会多次点击提交按钮或者刷新页面,导致表单数据被重复提交到后端服务器。
解决这个问题的方法是使用Spring提供的同步器令牌(synchronizer token)。同步器令牌是一种在表单中嵌入的隐藏字段,用于标识表单的唯一性。当用户提交表单时,后端服务器会验证同步器令牌的有效性,如果表单已经被提交过一次,后端服务器会拒绝再次提交。
以下是解决方法的具体步骤(代码示例可参考链接):
1. 在表单中添加同步器令牌字段:
<form action="/submit" method="post"> <input type="hidden" name="token" value="${token}" /> // 其他表单字段 </form>
2. 在后端控制器中生成和验证同步器令牌:
@Controller public class FormController { @Autowired private TokenService tokenService; @GetMapping("/form") public String showForm(Model model) { String token = tokenService.generateToken(); model.addAttribute("token", token); return "form"; } @PostMapping("/submit") public String submitForm(@RequestParam("token") String token) { if (!tokenService.isValidToken(token)) { // 处理重复提交的逻辑 } // 处理表单提交的逻辑 return "success"; } }
3. 在TokenService中实现生成和验证令牌的逻辑:
@Service public class TokenService { private Settokens = new HashSet<>(); public String generateToken() { String token = UUID.randomUUID().toString(); tokens.add(token); return token; } public boolean isValidToken(String token) { if (tokens.contains(token)) { tokens.remove(token); return true; } return false; } }
通过使用同步器令牌,可以防止用户重复提交表单,提高系统的安全性和稳定性。请点击此处了解更多关于Spring MVC同步器令牌的详细信息。
在Spring中避免重复提交有多种方法,可以结合使用:
- 使用JavaScript在点击后的几毫秒内
disable
按钮。这将避免因为不耐烦的用户多次点击按钮而导致多次提交。 - 提交后发送重定向,也被称为Post-Redirect-Get (PRG)模式。这将避免因为用户在结果页面按下F5并忽略浏览器的重复提交警告,或者在浏览器的前进/后退按钮上来回导航并忽略同样的警告而导致多次提交。
- 在页面请求时生成一个唯一的令牌,并将其放在会话范围和表单的隐藏字段中。在处理过程中,检查令牌是否存在,然后立即从会话中删除并继续处理。如果令牌不存在,则阻止处理。这将避免前面提到的问题。
在Spring中,可以使用RedirectView
来实现PRG模式(如第2点所述)。其他两个点需要自行实现。
你好,我应用了PRG模式,它运行良好。但是我需要在GET方法中传递一条消息,比如"用户ID 1001创建成功"。为此,我在控制器类中定义了一个变量,在POST方法中设置其值,在GET方法中获取其值。这将为并发用户创建问题。我该如何避免这个问题?
:将其存储在会话或Cookie中。
不,这会占用更多的处理时间。与其我们手动编码,框架应该提供像Struts2.0默认提供的令牌功能。
对于如此好的答案点赞+1
仅供参考,Struts使用了第三种解决方案(dev.anyframejava.org/docs.en/anyframe/plugin/optional/struts/…)
关于与C点相关的另一种类似方法,生成一个唯一令牌,并将其作为表单的隐藏字段。现在在Web过滤器中,在finally块中将其放入会话中并删除。同时,如果从请求中获取到相同的令牌,并且会话中存在该令牌,则拒绝。唯一的区别是我们不需要提前存储令牌,而是在需要时存储,比如在POST请求中。