Spring MVC接口防数据篡改和重复提交
作者:一代键客 发布时间:2023-11-29 15:02:11
本文实例为大家分享了Spring MVC接口防数据篡改和重复提交的具体代码,供大家参考,具体内容如下
一、自定义一个注解,此注解可以使用在方法上或类上
使用在方法上,表示此方法需要数据校验
使用在类上,表示此类下的所有方法需要数据校验
此注解对无参数方法不起作用
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface DataValidate {
}
二、自定义拦截,拦截前端所有请求
1、检查此接口调用的方法或方法所在的类是否使用了DataValidate注解,若没有使用,表示此接口不需要校验数据;
2、若使用了注解,再检查此方法有没有入参,若没有入参,不需要校验数据,否在需要校验;
3、把前端传来的所有参数 (除了签名参数)按照参数升序生成一个json字符串(使用TreeMap方式自动排序);
4、把生成的json字符串通过MD5加密的结果和前端传的签名值对比,若不相等,表示此数据被篡改过,否在没有被篡改过;
5、数据是否被篡改校验完毕,若前端传了用户唯一标示(token),表示需要校验数据是否重复提交;
6、若签名和上次提交的数据的签名相等,表示是重复提交数据,若不相等,把签名保存下来,表示数据不是重复提交。
import java.security.MessageDigest;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.alibaba.fastjson.JSON;
/**
* 防数据被篡改和重复提交
*/
public class DataValidateInterceptor extends HandlerInterceptorAdapter implements Runnable {
public static Map<String, TokenValue> userToken = new ConcurrentHashMap<>();
// 过期时间
private static long EXPIRED_TIME = 3600000;
private static String TOKEN_NAME = "token";
private static String SIGN_NAME = "sign";
private volatile boolean shutDown;
public DataValidateInterceptor(@Value("${data_interceptor.expired_time}") String expiredTime,
@Value("${data_interceptor.token_name}") String tokenName,
@Value("${data_interceptor.sign_name}") String signName) {
if (null != expiredTime && !"".equals(expiredTime)) {
EXPIRED_TIME = Long.parseLong(expiredTime);
}
if (null != tokenName && !"".equals(tokenName)) {
TOKEN_NAME = tokenName;
}
if (null != signName && !"".equals(signName)) {
SIGN_NAME = signName;
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (validate(request, response, handler)) {
/**
* 实现返回提示数据
*/
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("参数验证失败!");
return false;
}
return true;
}
private boolean validate(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler instanceof HandlerMethod) {
Class<?> clazz = ((HandlerMethod) handler).getBeanType();
DataValidate dataValidate = clazz.getAnnotation(DataValidate.class);
if (null == dataValidate) {
dataValidate = ((HandlerMethod) handler).getMethodAnnotation(DataValidate.class);
}
if (dataValidate != null) {
MethodParameter[] methodParameters = ((HandlerMethod) handler).getMethodParameters();
if (null == methodParameters || methodParameters.length <=0) {
// 方法没有入参不需要校验
return false;
}
// 需要校验
String sign = request.getParameter(SIGN_NAME);
Map<String, String[]> params = request.getParameterMap();
Set<String> paramNames = params.keySet();
Map<String, String> paramsMap = new TreeMap<>();
for (String paramName : paramNames) {
if (paramName.equals(SIGN_NAME)) {
continue;
}
paramsMap.put(paramName, request.getParameter(paramName));
}
String paramString = JSON.toJSONString(paramsMap).replaceAll(" ", "");
String MD5Sign = MD5.getMD5(paramString);
if (!sign.equals(MD5Sign)) {
// 数据被篡改
return true;
}
String token = request.getParameter(TOKEN_NAME);
if (token != null) {
if (userToken.containsKey(token)) {
TokenValue tokenValue = userToken.get(token);
if (tokenValue.getValue().equals(sign)) {
// 数据已经提交过
return true;
} else {
tokenValue.setValue(sign);
}
} else {
userToken.put(token, new TokenValue(sign));
}
}
}
}
return false;
}
@Override
public void run() {
try {
while (!shutDown) {
synchronized (this) {
wait(EXPIRED_TIME);
Set<String> keys = userToken.keySet();
for (String key : keys) {
if ((userToken.get(key).getExpiredTime() + EXPIRED_TIME) <= System.currentTimeMillis()) {
userToken.remove(key);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@PreDestroy
public void custDestroy() {
shutDown = true;
synchronized (this) {
notifyAll();
}
}
private static class MD5 {
/**
* 向getMD5方法传入一个你需要转换的原始字符串,将返回字符串的MD5码
*
* @param code 原始字符串
* @return 返回字符串的MD5码
*/
private static String getMD5(String code) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] bytes = code.getBytes();
byte[] results = messageDigest.digest(bytes);
StringBuilder stringBuilder = new StringBuilder();
for (byte result : results) {
// 将byte数组转化为16进制字符存入stringbuilder中
stringBuilder.append(String.format("%02x", result));
}
return stringBuilder.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
}
public class TokenValue {
private String value;
private long expiredTime;
public TokenValue(String value) {
this.value = value;
this.expiredTime = System.currentTimeMillis();
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
this.expiredTime = System.currentTimeMillis();
}
public long getExpiredTime() {
return expiredTime;
}
}
三、使用
后端使用:
1.在需要数据校验的方法或类上使用DataValidate注解(若在类上使用,表示此类下的所有方法需要验证)
2.配置 DataValidateInterceptor *
3.配置前端签名参数,默认是sign
4.配置用户唯一标示参数,默认是token(防止数据重复提交需要)
5.配置用户唯一标示过期时间,默认是1h,单位是ms(防止数据重复提交需要)
前端使用:
1.获取用户唯一标示(防止数据重复提交需要)
2.把需要提交的数据根据参数(包括用户唯一标示)升序排序然后生成一个json字符串(不能有空格),最后把json字符串进行MD5加密生成32位小写加密结果签名
eg:需要提交的数据为{messageType: "userQueryWait", companyCode: "test_app", token:"123213213"},排序后生成json字符串 {"companyCode":"test_app","messageType":"userQueryWait","token":"123213213"}, md5生成签名
3.把签名和需要提交的数据一起传到后台
eg:{messageType: "userQueryWait", companyCode: "test_app", token:"123213213", sign:"719bdb1fb769efb68e40440d1628ed5b"}
来源:https://blog.csdn.net/woshixiazaizhe/article/details/83277297
猜你喜欢
- 本文实例讲述了Spring实战之ResourceLoader接口资源加载用法。分享给大家供大家参考,具体如下:一 代码package lee
- 一、思路1.定义一个toFind变量来传入要查找的元素2.遍历整个顺序表并判定当前下标的元素等不等于toFind3.如果等于就返回一个tru
- 一、问题最近在做代码重构,代码工程采用了Controller/Service/Dao分层架构,Dao层使用了Mybatis-Plus框架。在
- 本文实例为大家分享了android自定义环形对比图的具体代码,供大家参考,具体内容如下1.首先在res/values里创建一个attr.xm
- 本文实例为大家分享了java实现简单石头剪刀布游戏的具体代码,供大家参考,具体内容如下问题描述Alice, Bob和Cindy一起玩猜拳的游
- 一、遇到一个问题1、读取CSV文件package com.guor.demo.charset;import java.io.Buffered
- 实例如下所示:/** * 创建多级目录文件 * * @param path 文件路径 * @throws IOException */pri
- 在 Flutter 中使用图片是最基础能力之一。作为春节开工后的第一篇文章,17 做了精心准备,满满的都是干货!本文介绍如何在 Flutte
- 在导入studio工程的时候,进行sync的时候,提示Error:Configuration with name 'default&
- 一、系统介绍1.开发环境开发工具:Eclipse2021JDK版本:jdk1.8Mysql版本:8.0.132.技术选型Java+Swing
- .c 源程序 ----- 编译 ----- 链接 ---- exe ----运行 -------->程序翻译环境和执行环境翻译环境:源
- 简介mutable(可变)和immutable(不可变)对象是我们在java程序编写的过程中经常会使用到的。可变类型对象就是说,对象在创建之
- JAVA虽然是在C++基础上发展而来,但却对C++的许多缺陷有所改进,其中一个不得不提的就是字符串,我们知道,随着学习的深入,进入MFC时,
- 配置宝塔面板javaweb运行环境详解,若出现404nignx错误也可按此教程进行检查1.准备:(解析成功的域名,本地运行完好的项目,宝塔面
- Map 中ConcurrentHashMap是线程安全的,但不是所有操作都是,例如get()之后再put()就不是了,这时使用merge()
- 一、闭包的定义。有很多不同的人都对闭包过进行了定义,这里收集了一些。# 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引
- Android中有两种主要方式使用Service,通过调用Context的startService方法或调用Context的bindServ
- 字段策略 0:”忽略判断”,1:”非 NULL 判断”),2:”非空判断”问题描述:当字段策略为 0 “忽略判断” 的时候,如果实体和数据库
- OutputDebugString属于windows API的,所以只要是包含了window.h这个头文件后就可以使用了。可以把调
- 1.try-catch异常处理说明Java提供try和catch块来处理异常,try块用于包含可能出错的代码。catch块用于处理try块中