防止表单重复提交是个老生常谈的问题,有些框架层面已经有实现,比如Struts2中的token,但Spring MVC中并未找到相应的功能,只能自己实现。
网上搜索“Spring MVC防止重复提交”,会有一大推的案例实现,但多数都存在以下几个问题或者不便:
- 防止重复提交页面需要添加隐藏域,类似<input type=”hidden” name=”token” value=”${token}”>,如果页面很多,会是一个体力活;
- 如果后台服务时分布式的,放入session中的值如何在另一台服务器获取;
- 多服务器多实例部署后,多个请求同时验证,有同时通过验证的可能,比如A服务器和B服务器都去Redis中验证是否存在token,同时通过验证后再保存操作,也会出现重复提交的问题。
针对以上问题,我们先进行分析一下:
- token机制肯定是服务器端产生的并且需要传递给客户端,然后客户端提交请求时带着该token。那有什么机制能保证不改客户端的代码,访问服务器时自动带上token,答案是利用cookie机制;当服务器产生token后,放入cookie,该token自动保存到客户端的cookie,客户端提交请求时,会默认带着所有的cookie信息。
- 针对该问题还是有好多解决方法,方法一:我们可以把token存入到一个公共地方,比如Redis,memcached中(需要考虑多用户同时生成token问题,放入规则可以userId+token);方法二:使用插件做到session共享,这样多个服务器都能获取同样的session。
- 第三个问题是典型的分布式锁问题,同一时刻不能2个线程去校验,否则会出现同时验证通过的问题。
以下是代码实现,基础框架参照的网上示例,session使用了插件做了session共享;分布式锁,由于使用的是memcached,所以使用memcached的add方式实现分布式锁(参照http://timyang.net/programming/memcache-mutex/)。
实现步骤:
- 新建注解
/** * <p> * 防止重复提交注解,用于方法上<br/> * 在新建页面方法上,设置needSaveToken()为true,此时拦截器会在Session中保存一个token, * 同时需要在新建的页面中添加 * <input type="hidden" name="token" value="${token}"> * <br/> * 保存方法需要验证重复提交的,设置needRemoveToken为true * 此时会在拦截器中验证是否重复提交 * </p> */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface AvoidDuplicateSubmit { boolean needSaveToken() default false; boolean needRemoveToken() default false; }
2.定义切面(处理逻辑)
@Aspect @Component public class AvoidDuplicateSubmitAspect { @Autowired(required=false) private SessionServiceImpl sessionService; private static ICacheService<?> cacheService = CacheFactory.getCache(); private static IMemcachedCache memcachedCache = null; static { if(cacheService instanceof RemoteCacheServiceImpl) { memcachedCache = ((RemoteCacheServiceImpl)cacheService).getCache(); } } @Before("@annotation(sec)") public void execute(JoinPoint jp,AvoidDuplicateSubmit sec) { //JoinPoint会获取到注解所在方法的参数 Object[] args = jp.getArgs(); //使用该注解的方法参数第一个必须是HttpServletRequest,第二个必须是HttpServletResponse HttpServletRequest request = (HttpServletRequest) args[0]; HttpServletResponse response = (HttpServletResponse) args[1]; boolean needSaveSession = sec.needSaveToken(); if (needSaveSession) { String uuid = UUID.randomUUID().toString(); request.getSession(false).setAttribute("token", uuid); CookieUtil.addCookie(response, "token", uuid, 0); } boolean needRemoveSession = sec.needRemoveToken(); if (needRemoveSession) { String serverToken = (String) request.getSession(false).getAttribute("token"); Cookie c = CookieUtil.getCookieByName(request, "token"); String clientToken = c.getValue(); if (isRepeatSubmit(serverToken, clientToken)) { throw new ValidateException("请勿重复提交!"); } //校验通过后从session中删除token request.getSession(false).removeAttribute("token"); if(null != memcachedCache) { //删除memcached锁 memcachedCache.delete(serverToken); } } } private boolean isRepeatSubmit(String serverToken, String clientToken) { if (serverToken == null) { return true; } Calendar cal = Calendar.getInstance(); cal.add(Calendar.MINUTE, 1); //memcached add 失败,即没有获取到锁,返回true if(null != memcachedCache && !memcachedCache.add(serverToken, 1, cal.getTime())) { return true; } if (clientToken == null) { return true; } if (!serverToken.equals(clientToken)) { return true; } return false; } }
3.在spring dispatcher-servlet.xml中开启该注解
<aop:aspectj-autoproxy proxy-target-class="true"> <aop:include name="avoidDuplicateSubmitAspect"/> </aop:aspectj-autoproxy> <bean id="avoidDuplicateSubmitAspect" class="com.xuehuifei.annotation.submit.AvoidDuplicateSubmitAspect"></bean>
4.接口层使用
//页面展现接口,添加生成token注解 @RequestMapping(value = "/{userId}", method = RequestMethod.GET) @AvoidDuplicateSubmit(needSaveToken=true) public @ResponseBody ResultObject list(HttpServletRequest request, HttpServletResponse response, @PathVariable Long userId) { ResultObject obj = new ResultObject(); return obj; } //提交请求接口,添加删除token注解 @RequestMapping(method=RequestMethod.POST) @AvoidDuplicateSubmit(needRemoveToken=true) public @ResponseBody ResultObject save(HttpServletRequest request, HttpServletResponse response, HostModel hostModel) { ResultObject obj = new ResultObject(); return obj; }
ps:cookie保存token机制还设计到客户端禁用cookie的问题,该文章忽略这种情况。
相关推荐
在Spring MVC框架中,防止重复提交是一个重要的议题,特别是在处理敏感数据或执行不可逆操作时。重复提交可能会导致数据不一致性和系统混乱。"Token-SpringMVC"是一种常见的解决方案,它利用令牌(Token)机制来确保...
国际化(i18n)则涉及如何支持多种语言,而防止重复提交是Web开发中的重要问题,Struts2提供了一种解决方案。 2. **Hibernate**:Hibernate是一个对象关系映射(ORM)框架,简化了数据库操作。`day57_hibernate_多...
Spring MVC是一个强大且广泛使用的Java框架,常用于构建高效、可维护的Web...同时,为了应对跨站脚本(XSS)和跨站请求伪造(CSRF)等安全问题,还需要遵循最佳实践,如使用Spring Security等框架来增强应用的安全性。
3. **使用Token防止表单重复提交**: 表单重复提交可能导致数据不一致,尤其是在并发环境中。Token机制是一种常见的解决方案。学生需要在服务端生成一个唯一的Token,并将其存储在会话或Cookie中,同时将Token发送...
- **MVC模式**:许多JSP项目会使用如Spring MVC这样的框架,将视图、模型和控制器分离,提高代码可维护性。 - **数据库连接池**:例如Apache的DBCP或C3P0,用于高效管理数据库连接。 - **标签库**:如JSTL(Java...
- 使用框架:例如Spring MVC中,可以利用`@Valid`注解和自定义Validator类进行数据校验。 3. **常见验证场景**: - 非空验证:确保字段不为空。 - 长度验证:限制输入的字符长度。 - 数据类型验证:检查数字、...
**目标:** Java技术栈不断更新,需要持续学习新技术和最佳实践。 1. **关注技术动态:** - **阅读技术博客:** 如JavaWorld、InfoQ等。 - **参加技术会议:** 如JavaOne、JFokus等。 - **订阅技术邮件列表:** ...
这个项目是北大青鸟教学体系的一部分,旨在帮助学员深入理解Java Web开发中的核心技术和最佳实践,提升实际项目开发能力。通过参与这样的项目,学生不仅能掌握编程技能,还能锻炼到项目管理、需求分析以及团队合作等...
5. **安全实践**:遵循最佳实践,如使用预编译的SQL语句防止SQL注入攻击。 除了用户登录和注册,新闻发表系统还可能包括其他功能,如: 1. **新闻分类管理**:创建、编辑和删除新闻类别,便于用户筛选和查找信息。...
七、最佳实践与注意事项 1. **代码规范**:遵循良好的编码和设计规范,使代码易于理解和维护。 2. **错误处理**:正确处理异常,提供友好的错误信息,提高用户体验。 3. **性能优化**:合理使用缓存,避免重复...
10. **最佳实践**:在实际项目中,除了使用Kaptcha生成验证码,还应考虑定期更换验证码样式,防止被破解。同时,结合其他安全措施,如限流、IP黑名单等,增强网站的安全性。 总的来说,Kaptcha是Java Web开发中一个...
系统应遵循安全最佳实践,如防止SQL注入、XSS攻击,以及使用HTTPS协议保障数据传输的安全。 这个基于Vue的电影在线预订与管理系统,不仅涵盖了前端与后端的交互,还涉及到数据库设计、用户管理、资源调度等多个方面...
5. **最佳实践** - 保持验证规则的模块化和复用性,避免冗余代码。 - 使用异常处理机制处理验证失败的情况,提供清晰的错误信息反馈给用户。 - 考虑性能影响,优化动态加载验证规则的过程。 - 对于敏感数据,...
总结来说,JSP火车订票系统的开发涵盖了前端页面展示、后端业务逻辑处理、数据库操作、安全防护、性能优化等多个方面,涉及了丰富的Java EE技术和最佳实践。通过这样的系统,我们可以为用户提供一个安全、高效的火车...
Java面试题集合通常涵盖了许多核心概念和技术,是...以上是Java面试中常见的一些问题及其答案,这些知识点涵盖了Java基础、Web开发、框架、数据库和最佳实践等多个方面。理解和掌握这些内容对于准备Java面试至关重要。
然而,由于历史遗留问题,Struts2曾被发现一些严重的安全漏洞,因此及时更新框架版本和遵循最佳实践至关重要。 总的来说,"struts2参考文档"是开发者学习和理解Struts2框架的重要资源,它涵盖了框架的各个方面,...
这份手册详细列出了Java开发中应遵循的最佳实践、编程规约和技术规范,旨在打造高效、可维护且易读的代码。以下是对该手册核心内容的详细解读: 1. **基本规约** - **命名规约**:包括类名、方法名、变量名等的...
10. **最佳实践** - 数据库设计遵循范式原则,提高数据的规范性和减少数据冗余。 - 使用预编译的`PreparedStatement`提高SQL执行效率,同时防止SQL注入。 - 使用合适的索引优化查询性能。 - 定期备份数据库,...
15.3 使用令牌防止表单重复提交 313 15.3.1 使用s:token/表单标签 313 15.3.2 令牌拦截器规则的例外 314 15.4 自动显示等待页面 316 15.5 完成CRUD操作的一个动作 317 15.5.1 CRUD 317 15.5.2 拦截器和接口 318 ...