spring boot 学习(七)小工具篇:表单重复提交
注解 + 拦截器:解决表单重复提交
前言
学习 Spring Boot 中,我想将我在项目中添加几个我在 SpringMVC 框架中常用的工具类(主要都是涉及到 Spring AOP 部分知识)。比如,表单重复提交,?秒防刷新,全局异常捕抓类,IP黑名单(防爬虫设置)…………等等。接下来的时间,我尝试将这些框架整合到 Spring Boot 中(尽可能完成),毕竟项目开发中这些工具是非常有用的。
注意,这些工具基本上都是我以前在 github 之类开源平台找到的小工具类,作者的信息什么的许多都忘了。先说声不好意思了。若有相关信息,麻烦提醒一下~
介绍
这里就不详细介绍相应的知识了,主要提及有关涉及到的术语:
拦截器
Spring 拦截器有两种实现方法。一种是继承HandlerInterceptorAdapter,拥有preHandle(业务处理器处理请求之前被调用),postHandle(在业务处理器处理请求执行完成后,生成视图之前执行),afterCompletion(在完全处理完请求后被调用,可用于清理资源等)三个方法。
另一种就是调用 Spring AOP 的方法来实现。而且,我觉得这种方法更加灵活方便,所以我比较经常使用这种方法。AOP( AspectJ— 注解 风格)
AOP 就是 Aspect Oriented Programming(面向方面编程)。
1. 连接点(Joinpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点。
2. 前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
3. 抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知
附上:大神开涛的有关 Spring AOP 博客:http://jinnianshilongnian.iteye.com/blog/1474325
解决问题
什么是表单重复提交?
服务器认为是同一个表单,在短时间内重复(不止一次)提交,或者提交异常。比如,在服务器还没有响应前我们不断点击刷新网页上一个提交按钮,或者通过 ajax 不断对服务器发送请求报文!
防止情况
- 不通过正常路径访问页面表单;
- session 失效情况下提交表单;
- 短时间内不止一次提交表单。
解决方案
一般情况下,是在服务器利用 session 来防止这个问题的。
流程图:
1. 网页点击事件,网页提交发送申请;
2. 服务器收到申请,并产生令牌(Token),并存于 Session 中;
3. 服务器将令牌返回给页面,页面将令牌与表单真正提交给服务器。
这种就是 structs 的令牌方式。还有其他方法,就是重定向方法或设置页面过期(前端部分不太了解),不过还是感觉强制跳转不是特别友好,同时也不够灵活多用。
前期准备
新建一个 spring boot 项目(建议 1.3.X 以上版本)。
加入 aop 依赖,默认设置就行了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
正式开工
- 注解类 Token.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Token {
//生成 Token 标志
boolean save() default false ;
//移除 Token 值
boolean remove() default false ;
}
- 表单异常类 FormRepeatException.java
public class FormRepeatException extends RuntimeException {
public FormRepeatException(String message){ super(message);}
public FormRepeatException(String message, Throwable cause){ super(message, cause);}
}
- 拦截器 TokenContract.java
注意:@Aspect与@Component两个注解!
@Aspect
@Component
public class TokenContract { private static final Logger logger = LoggerFactory.getLogger(TokenContract.class); @Before("within(@org.springframework.stereotype.Controller *) && @annotation(token)")
public void testToken(final JoinPoint joinPoint, Token token){
try {
if (token != null) {
//获取 joinPoint 的全部参数
Object[] args = joinPoint.getArgs();
HttpServletRequest request = null;
HttpServletResponse response = null;
for (int i = 0; i < args.length; i++) {
//获得参数中的 request && response
if (args[i] instanceof HttpServletRequest) {
request = (HttpServletRequest) args[i];
}
if (args[i] instanceof HttpServletResponse) {
response = (HttpServletResponse) args[i];
}
} boolean needSaveSession = token.save();
if (needSaveSession){
String uuid = UUID.randomUUID().toString();
request.getSession().setAttribute( "token" , uuid);
logger.debug("进入表单页面,Token值为:"+uuid);
} boolean needRemoveSession = token.remove();
if (needRemoveSession) {
if (isRepeatSubmit(request)) {
logger.error("表单重复提交");
throw new FormRepeatException("表单重复提交");
}
request.getSession(false).removeAttribute( "token" );
}
} } catch (FormRepeatException e){
throw e;
} catch (Exception e){
logger.error("token 发生异常 : "+e);
}
} private boolean isRepeatSubmit(HttpServletRequest request) throws FormRepeatException {
String serverToken = (String) request.getSession( false ).getAttribute( "token" );
if (serverToken == null ) {
//throw new FormRepeatException("session 为空");
return true;
}
String clinetToken = request.getParameter( "token" );
if (clinetToken == null || clinetToken.equals("")) {
//throw new FormRepeatException("请从正常页面进入!");
return true;
}
if (!serverToken.equals(clinetToken)) {
//throw new FormRepeatException("重复表单提交!");
return true ;
}
logger.debug("校验是否重复提交:表单页面Token值为:"+clinetToken + ",Session中的Token值为:"+serverToken);
return false ;
}
}
Controller类
访问 http://localhost:8080/savetoken 来获得令牌值
访问 http://localhost:8080/removetoken?token=XXX 来提交真正的表单
@Token(save = true)
@RequestMapping("/savetoken")
@ResponseBody
public String getToken(HttpServletRequest request, HttpServletResponse response){
return (String) request.getSession().getAttribute("token");
} @Token(remove = true)
@RequestMapping("/removetoken")
@ResponseBody
public String removeToken(HttpServletRequest request, HttpServletResponse response){
return "success";
}
spring boot 学习(七)小工具篇:表单重复提交的更多相关文章
- spring boot 学习(九)小工具篇:?秒防刷新
注解 + 拦截器:?秒防刷新 小工具篇:工具许多都是我以前在 github 之类开源平台找到的小工具类,作者的信息什么的许多都忘了.先说声不好意思了.若有相关信息,麻烦提醒一下~ 解释 所谓的?秒防刷 ...
- JavaWeb学习总结——使用Session防止表单重复提交
在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...
- Spring Boot (一) 校验表单重复提交
一.前言 在某些情况下,由于网速慢,用户操作有误(连续点击两下提交按钮),页面卡顿等原因,可能会出现表单数据重复提交造成数据库保存多条重复数据. 存在如上问题可以交给前端解决,判断多长时间内不能再次点 ...
- (转)JavaWeb学习总结(十三)——使用Session防止表单重复提交
如何防止表单重复提交 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复 ...
- Struts2第十三篇【防止表单重复提交】
回顾防止表单重复提交 当我们学习Session的时候已经通过Session来编写了一个防止表单重复提交的小程序了,我们来回顾一下我们当时是怎么做的: 在Servlet上生成独一无二的token,保存在 ...
- java web学习总结(十三) -------------------使用Session防止表单重复提交
在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...
- Spring MVC实现防止表单重复提交(转)
Spring MVC拦截器+注解方式实现防止表单重复提交
- [原创]java WEB学习笔记73:Struts2 学习之路-- strut2中防止表单重复提交
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- java web 学习十三(使用session防止表单重复提交)
在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...
随机推荐
- nodejs 导出 exel文件 xlsx
参考: https://www.npmjs.com/package/node-xlsx Building a xlsx import xlsx from 'node-xlsx'; // Or var ...
- 20145324王嘉澜 《网络对抗》进阶实践之 shellcode注入和Return-to-libc攻击深入
Shellcode注入 •Shellcode实际是一段代码,但却作为数据发送给受攻击服务器,将代码存储到对方的堆栈中,并将堆栈的返回地址利用缓冲区溢出,覆盖成为指向 shellcode的地址 •实验参 ...
- Android项目开发三
微博客户端开发 本周学习计划 运用OAuth相关知识,解决上周出现的微博验证问题. 看懂微博客户端登录.用户主页等功能代码. 将程序中存在的问题解决. 实际完成情况 本周继续研究了OAuth相关知识, ...
- HTML标签(持续更新)
HTML的文档结构: 1.<html> 2.<head>:放置HTML文件的信息,如定义CSS样式代码可放置在此标签中 3.<title>:放置网页的标题 4.&l ...
- 分布式系统一致性算法Raft
Raft 算法也是一种少数服从多数的算法,在任何时候一个服务器可以扮演以下角色之一:Leader:负责 Client 交互 和 log 复制,同一时刻系统中最多存在一个Follower:被动响应请求 ...
- DFS回溯-函数递归-xiaoz triangles
题目:小z 的三角形 ★实验任务 三角形的第1 行有n 个由"+"和"-"组成的符号,以后每行符 号比上行少1 个,2 个同号下面是"+", ...
- 编译安装lamp (php)
用户账号及权限管理 用户账号:'user'@'host' user: 用户名 host: 此用户访问mysqld服务时允许通过哪些主机远程创建连接: host类型:IP.网络地址.主机名.通配符(%和 ...
- sliva数据库简介--转载
sliva rRNA数据库(http://www.arb-silva.de/)用来检查和比对RNA序列,既可以针对16S/18S,SSU,也可以针对23S/28S, LSU,包括了Bacteria, ...
- c++ 容器元素遍历打印(for_each)
#include <iostream> // cout #include <algorithm> // for_each #include <vector> // ...
- vue饿了么学习笔记(1)vue-cli开启项目
一.vue-cli介绍 vue-cli是vue的脚手架工具 ----> 帮助写好vue.js基础代码的工具: ① 搭建目录结构 ② 进行本地调试 ③ 进行代码部署 ④ 热加载 ⑤ 进行单元测试 ...