`
speed
  • 浏览: 8636 次
文章分类
社区版块
存档分类
最新评论

Spring MVC防止重复提交最佳实践

阅读更多

防止表单重复提交是个老生常谈的问题,有些框架层面已经有实现,比如Struts2中的token,但Spring MVC中并未找到相应的功能,只能自己实现。

 

网上搜索“Spring MVC防止重复提交”,会有一大推的案例实现,但多数都存在以下几个问题或者不便:

  1. 防止重复提交页面需要添加隐藏域,类似<input type=”hidden” name=”token” value=”${token}”>,如果页面很多,会是一个体力活;
  2. 如果后台服务时分布式的,放入session中的值如何在另一台服务器获取;
  3. 多服务器多实例部署后,多个请求同时验证,有同时通过验证的可能,比如A服务器和B服务器都去Redis中验证是否存在token,同时通过验证后再保存操作,也会出现重复提交的问题。

针对以上问题,我们先进行分析一下:

  1. token机制肯定是服务器端产生的并且需要传递给客户端,然后客户端提交请求时带着该token。那有什么机制能保证不改客户端的代码,访问服务器时自动带上token,答案是利用cookie机制;当服务器产生token后,放入cookie,该token自动保存到客户端的cookie,客户端提交请求时,会默认带着所有的cookie信息。
  2. 针对该问题还是有好多解决方法,方法一:我们可以把token存入到一个公共地方,比如Redis,memcached中(需要考虑多用户同时生成token问题,放入规则可以userId+token);方法二:使用插件做到session共享,这样多个服务器都能获取同样的session。
  3. 第三个问题是典型的分布式锁问题,同一时刻不能2个线程去校验,否则会出现同时验证通过的问题。

以下是代码实现,基础框架参照的网上示例,session使用了插件做了session共享;分布式锁,由于使用的是memcached,所以使用memcached的add方式实现分布式锁(参照http://timyang.net/programming/memcache-mutex/)。

 

实现步骤:

  1. 新建注解
/**
 * <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的问题,该文章忽略这种情况。

 

http://www.xuehuifei.com/934.html

分享到:
评论

相关推荐

    token-springMVC 防止重复提交

    在Spring MVC框架中,防止重复提交是一个重要的议题,特别是在处理敏感数据或执行不可逆操作时。重复提交可能会导致数据不一致性和系统混乱。"Token-SpringMVC"是一种常见的解决方案,它利用令牌(Token)机制来确保...

    struts2,hibernate,spring,springmvc,mybatis

    国际化(i18n)则涉及如何支持多种语言,而防止重复提交是Web开发中的重要问题,Struts2提供了一种解决方案。 2. **Hibernate**:Hibernate是一个对象关系映射(ORM)框架,简化了数据库操作。`day57_hibernate_多...

    登录注册模块相应的css、js、lib包.rar

    Spring MVC是一个强大且广泛使用的Java框架,常用于构建高效、可维护的Web...同时,为了应对跨站脚本(XSS)和跨站请求伪造(CSRF)等安全问题,还需要遵循最佳实践,如使用Spring Security等框架来增强应用的安全性。

    J2EE企业级项目开发-1期 任务1-8 实训项目单(一).doc

    3. **使用Token防止表单重复提交**: 表单重复提交可能导致数据不一致,尤其是在并发环境中。Token机制是一种常见的解决方案。学生需要在服务端生成一个唯一的Token,并将其存储在会话或Cookie中,同时将Token发送...

    jsp登陆系统

    - **MVC模式**:许多JSP项目会使用如Spring MVC这样的框架,将视图、模型和控制器分离,提高代码可维护性。 - **数据库连接池**:例如Apache的DBCP或C3P0,用于高效管理数据库连接。 - **标签库**:如JSTL(Java...

    jsp验证代码验证代码

    - 使用框架:例如Spring MVC中,可以利用`@Valid`注解和自定义Validator类进行数据校验。 3. **常见验证场景**: - 非空验证:确保字段不为空。 - 长度验证:限制输入的字符长度。 - 数据类型验证:检查数字、...

    java学习路线.docx

    **目标:** Java技术栈不断更新,需要持续学习新技术和最佳实践。 1. **关注技术动态:** - **阅读技术博客:** 如JavaWorld、InfoQ等。 - **参加技术会议:** 如JavaOne、JFokus等。 - **订阅技术邮件列表:** ...

    北大青鸟 投票系统 y2项目

    这个项目是北大青鸟教学体系的一部分,旨在帮助学员深入理解Java Web开发中的核心技术和最佳实践,提升实际项目开发能力。通过参与这样的项目,学生不仅能掌握编程技能,还能锻炼到项目管理、需求分析以及团队合作等...

    新闻发表系统基于jsp

    5. **安全实践**:遵循最佳实践,如使用预编译的SQL语句防止SQL注入攻击。 除了用户登录和注册,新闻发表系统还可能包括其他功能,如: 1. **新闻分类管理**:创建、编辑和删除新闻类别,便于用户筛选和查找信息。...

    struts1.x 常用知识详解

    七、最佳实践与注意事项 1. **代码规范**:遵循良好的编码和设计规范,使代码易于理解和维护。 2. **错误处理**:正确处理异常,提供友好的错误信息,提高用户体验。 3. **性能优化**:合理使用缓存,避免重复...

    Kaptcha.zip

    10. **最佳实践**:在实际项目中,除了使用Kaptcha生成验证码,还应考虑定期更换验证码样式,防止被破解。同时,结合其他安全措施,如限流、IP黑名单等,增强网站的安全性。 总的来说,Kaptcha是Java Web开发中一个...

    基于Vue的电影在线预订与管理系统-后台java代码(ssm)(毕业设计)

    系统应遵循安全最佳实践,如防止SQL注入、XSS攻击,以及使用HTTPS协议保障数据传输的安全。 这个基于Vue的电影在线预订与管理系统,不仅涵盖了前端与后端的交互,还涉及到数据库设计、用户管理、资源调度等多个方面...

    动态验证formbean

    5. **最佳实践** - 保持验证规则的模块化和复用性,避免冗余代码。 - 使用异常处理机制处理验证失败的情况,提供清晰的错误信息反馈给用户。 - 考虑性能影响,优化动态加载验证规则的过程。 - 对于敏感数据,...

    JSP火车订票系统的设计

    总结来说,JSP火车订票系统的开发涵盖了前端页面展示、后端业务逻辑处理、数据库操作、安全防护、性能优化等多个方面,涉及了丰富的Java EE技术和最佳实践。通过这样的系统,我们可以为用户提供一个安全、高效的火车...

    java面试题集合java面试题集合.doc

    Java面试题集合通常涵盖了许多核心概念和技术,是...以上是Java面试中常见的一些问题及其答案,这些知识点涵盖了Java基础、Web开发、框架、数据库和最佳实践等多个方面。理解和掌握这些内容对于准备Java面试至关重要。

    struts2参考文档,非常实用的struts参考技术

    然而,由于历史遗留问题,Struts2曾被发现一些严重的安全漏洞,因此及时更新框架版本和遵循最佳实践至关重要。 总的来说,"struts2参考文档"是开发者学习和理解Struts2框架的重要资源,它涵盖了框架的各个方面,...

    阿里巴巴Java开发手册终极版v1.3.0.zip

    这份手册详细列出了Java开发中应遵循的最佳实践、编程规约和技术规范,旨在打造高效、可维护且易读的代码。以下是对该手册核心内容的详细解读: 1. **基本规约** - **命名规约**:包括类名、方法名、变量名等的...

    基于Myeclipse与MySQL数据库表格的增删改查

    10. **最佳实践** - 数据库设计遵循范式原则,提高数据的规范性和减少数据冗余。 - 使用预编译的`PreparedStatement`提高SQL执行效率,同时防止SQL注入。 - 使用合适的索引优化查询性能。 - 定期备份数据库,...

    Struts2 in action中文版

    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 ...

Global site tag (gtag.js) - Google Analytics