java后端使用token处理表单重复提交
- 保证接口幂等性,表单重复提交
前台解决方案:
提交后按钮禁用、置灰、页面出现遮罩
后台解决方案: 使用token,每个token只能使用一次
1.在调用接口之前生成对应的Token,存放至redis2.在调用接口时,将生成的令牌放入请求request中
3.接口提交的时候获取对应的令牌,如果能够从redis中获得该令牌(获取后将当前令牌删除),
则继续执行访问的业务逻辑4.接口提交的时候获取对应的令牌,如果获取不到改令牌,则直接返回请勿提交
工程源码:https://github.com/youxiu326/sb_more_submit
自定义注解
ApiToken注解用于将token保存至request,用于页面取token
ApiRepeatSubmit注解用于标明改方法需要验证token才能提交
package com.huarui.util; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 生成token注解
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiToken {
}
ApiToken.java
package com.huarui.util; import com.huarui.common.ConstantUtils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @功能描述 防止重复提交标记注解
*/
@Target(ElementType.METHOD) // 作用到方法上
@Retention(RetentionPolicy.RUNTIME)// 运行时有效
public @interface ApiRepeatSubmit {
ConstantUtils value();
}
ApiRepeatSubmit.java
package com.huarui.common; /**
* 【定义从哪里取Token的枚举类】
* head 即从请求头中取token,即客户端将token放入请求头来请求后端数据
* body 即直接从请求体中取token
*/
public enum ConstantUtils {
BOOD,HEAD
}
ConstantUtils.java
spring.thymeleaf.cache=false spring.redis.host=youxiu326.xin
spring.redis.port=6379
application.properties
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.19.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.huarui</groupId>
<artifactId>sb_more_submit</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sb_more_submit</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties> <dependencies> <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
pom.xml
切面拦截,
package com.huarui.util; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.UUID;
import java.util.concurrent.TimeUnit; /**
* redis工具类
*/
@Component
public class RedisTokenUtils { private long timeout = 2;//过期时间 @Autowired
private RedisTemplate redisTemplate; /**
* 获取Token 并将Token保存至redis
* @return
*/
public String getToken() {
String token = "token_"+ UUID.randomUUID();
redisTemplate.opsForValue().set(token,token,timeout, TimeUnit.MINUTES);
return token;
} /**
* 判断Token是否存在 并且删除Token
* @param tokenKey
* @return
*/
public boolean findToken(String tokenKey){
String token = (String) redisTemplate.opsForValue().get(tokenKey);
if (StringUtils.isEmpty(token)) {
return false;
}
// token 获取成功后 删除对应tokenMapstoken
redisTemplate.delete(tokenKey);
return true;
} }
RedisTokenUtils.java
package com.huarui.aop; import javax.servlet.http.HttpServletRequest;
import com.huarui.common.ConstantUtils;
import com.huarui.util.ApiToken;
import com.huarui.util.ApiRepeatSubmit;
import com.huarui.util.RedisTokenUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.concurrent.TimeUnit; /**
* @功能描述 aop解析注解
*/
@Aspect
@Component
public class NoRepeatSubmitAop { private Log logger = LogFactory.getLog(getClass()); @Autowired
private RedisTokenUtils redisTokenUtils; /**
* 将token放入请求
* @param pjp
* @param nrs
*/
@Before("execution(* com.huarui.controller.*Controller.*(..)) && @annotation(nrs)")
public void before(JoinPoint pjp, ApiToken nrs){
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
request.setAttribute("token", redisTokenUtils.getToken());
} /**
* 拦截带有重复请求的注解的方法
* @param pjp
* @param nrs
* @return
*/
@Around("execution(* com.huarui.controller.*Controller.*(..)) && @annotation(nrs)")
public Object arround(ProceedingJoinPoint pjp, ApiRepeatSubmit nrs) { try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest(); String token = null;
if (nrs.value() == ConstantUtils.BOOD){
//从请求体中取Token
token = (String) request.getAttribute("token");
}else if (nrs.value() == ConstantUtils.HEAD){
//从请求头中取Token
token = request.getHeader("token");
}
if (StringUtils.isEmpty(token)){
return "token 不存在";
}
if (!redisTokenUtils.findToken(token)){
return "请勿重复提交";
}
Object o = pjp.proceed();
return o;
} catch (Throwable e) {
e.printStackTrace();
logger.error("验证重复提交时出现未知异常!");
return "{\"code\":-889,\"message\":\"验证重复提交时出现未知异常!\"}";
} } }
package com.huarui.controller; import com.huarui.common.ConstantUtils;
import com.huarui.util.ApiRepeatSubmit;
import com.huarui.util.ApiToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; @Controller
public class TestController { /**
* 进入页面
* @return
*/
@GetMapping("/")
@ApiToken
public String index(){
return "index";
} /**
* 测试重复提交接口
* 将Token放入请求头中
* @return
*/
@RequestMapping("/test")
@ApiRepeatSubmit(ConstantUtils.HEAD)
public @ResponseBody String test() {
return ("程序逻辑返回");
} }
前端页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<base th:href="${#httpServletRequest.getContextPath()+'/'}">
<meta charset="UTF-8">
<title>测试表单重复功能</title>
</head>
<body> <td colspan="1"><button type="button" onclick="add()">加购</button></td> </body> <script src="/jquery-1.11.3.min.js"></script>
<script th:inline="javascript"> function add(){
//取得token参数
var token = [[${token}]];
console.log("获取到的token:" + token);
$.ajax({
type: 'POST',
url: "/test",
data: {},
headers: {
"token":token,
},
// dataType: "json",
success: function(response){
alert(response);
},
error:function(response){
alert(response);
console.log(response);
}
});
} </script> </html>
工程源码:https://github.com/youxiu326/sb_more_submit
java后端使用token处理表单重复提交的更多相关文章
- PHP简单利用token防止表单重复提交
<?php /* * PHP简单利用token防止表单重复提交 * 此处理方法纯粹是为了给初学者参考 */ session_start(); function set_token() { $_S ...
- PHP生成token防止表单重复提交
.提交按钮置disabled 当用户提交后,立即把按钮置为不可用状态.这种用js来实现. 提交前代码如下: $() { $exec="insert into student (user_ ...
- PHP简单利用token防止表单重复提交(转)
<?php/* * PHP简单利用token防止表单重复提交 */function set_token() { $_SESSION['token'] = md5(microtime(true)) ...
- php通过token验证表单重复提交
PHP防止重复提交表单 2016-11-08 轻松学PHP 我们提交表单的时候,不能忽视的一个限制是防止用户重复提交表单,因为有可能用户连续点击了提交按钮或者是攻击者恶意提交数据,那么我们在提交数据后 ...
- AOP+Token防止表单重复提交
表单重复提交: 由于用户误操作,多次点击表单提交按钮 由于网速等原因造成页面卡顿,用户重复刷新提交页面 避免表单重复提交的方式: 1.页面上的按钮做防重复点击操作 2.在数据库中可以做唯一约束 3.利 ...
- PHP使用token防止表单重复提交的方法
本文实例讲述了PHP使用token防止表单重复提交的方法.分享给大家供大家参考,具体如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2 ...
- token防止表单重复提交
出现表单重复提交的三种情况: 一.服务器响应缓慢,用户多次点击提交按钮. 二.提交成功后刷新页面. 三.提交成功后返回表单页面再次点击提交. package com.jalja.token; impo ...
- java struts2入门学习--防止表单重复提交.OGNL语言学习
一.知识点回顾 防止表单重复提交核心思想: 客户端和服务器端和写一个token,比较两个token的值相同,则非重复提交;不同,则是重复提交. 1.getSession三种方式比较: request. ...
- struts2 自带的 token防止表单重复提交拦截器
在struts2中,我们可以利用struts2自带的token拦截器轻松实现防止表单重复提交功能! 1. 在相应的action配置中增加: <interceptor-ref name=&quo ...
随机推荐
- 【C#表达式树 六】表达式树中创建节点的两种方式
创建表达式树节点的两种方式1.用expression的静态方法MakeBinary|MakeUnary(ExpressionType,参数)的方式创建表达式树节点: BinaryExpression ...
- 小白学python第2问: 为什么只有int,没有long?
为什么只有int,没有long? 在python官网开发者指引里面能找到 PEP 237 -- Unifying Long Integers and Integers,这里说明了为什么要统一 int ...
- MySQL索引下推,原来这么简单!
大家好,我是大彬~ 今天给大家分享MySQL的索引下推. 什么是索引下推 索引条件下推,也叫索引下推,英文全称Index Condition Pushdown,简称ICP. 索引下推是MySQL5.6 ...
- Lua中如何实现类似gdb的断点调试--01最小实现
说到Lua代码调试,最常用的方法应该就是加一堆print进行打印.print大法虽好,但其缺点也是显而易见的.比如效率低下,需要修改原有函数内部代码,在每个需要的地方添加print语句,运行一次只能获 ...
- Qt:QString
0.说明 区别于QByteArray,QString串是Unicode串,每个元素都是QChar 16-bit UTF-16编码(Unicode) :而QByteArray是8-bit串. 0.1.初 ...
- Pandas:各种错误
1.输出为CSV文件时,Permission denied 原因可能是: (1).构建DataFrame时没有写index参数 (2).用Dict构建最开始的数据时,value没有写成List的形式, ...
- PhpStudy代码执行后门
0x00 概述 只需要两个参数 Accept-Encoding: gzip,deflate Accept-Charset: Base64编码(PHP代码) 0x01 利用代码 加群可以下载:87369 ...
- 矩池云上nvidia opencl安装及测试教程
本教程租用的是2080ti,3.7多框架镜像. 添加nvidia-cuda的阿里源 curl -fsSL https://mirrors.aliyun.com/nvidia-cuda/ubuntu18 ...
- PhpStrom 好用的翻译插件
最近php使用laravel框架的比较多,里面的注释都是英文的,有些同学的英语不是很好,不过不用但是侯蜀黍带你一个好用的翻译插件,告别烦恼一了百了 Translation 翻译插件 安装: 打开Fil ...
- 面向对象编程(C++篇3)——析构
目录 1. 概述 2. 详论 2.1. 对象生命周期 2.2. 不一定需要显式析构 2.3. 析构的必要性 3. 总结 1. 概述 类的析构函数执行与构造函数相反的操作,当对象结束其生命周期,程序就会 ...