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,MVC,数据篡改,数据提交
0
投稿

猜你喜欢

  • 控制Android LED灯颜色的代码实例

    2022-12-30 02:53:20
  • WPF自定义控件和样式之自定义按钮(Button)

    2022-04-09 14:39:32
  • java如何给对象按照字符串属性进行排序

    2022-05-23 02:17:14
  • 解决mybatis update并非所有字段需要更新问题

    2022-12-09 10:20:55
  • 基于Java实现多线程下载并允许断点续传

    2021-07-19 03:05:52
  • Android极光推送处理message遇到的坑解决

    2022-08-08 00:49:32
  • Java求字符串中出现次数最多的字符串以及出现次数

    2023-06-03 03:45:34
  • MyBatis resultMap id标签的错误使用方式

    2022-02-01 05:25:37
  • Java VisualVM监控远程JVM(详解)

    2022-07-10 15:59:15
  • Java中如何动态创建接口的实现方法

    2023-11-25 15:13:02
  • Android仿ios年龄、生日、性别滚轮效果

    2022-02-22 11:59:03
  • java.util.NoSuchElementException原因及两种解决方法

    2022-02-10 15:18:58
  • Android 如何实现动态申请权限

    2023-07-30 00:51:31
  • Java将对象保存到文件中/从文件中读取对象的方法

    2022-06-18 21:26:42
  • c#多线程网络聊天程序代码分享(服务器端和客户端)

    2022-08-10 00:32:48
  • Java深入讲解异常处理try catch的使用

    2023-11-04 13:00:32
  • Android实现轮播图片效果

    2023-07-08 02:20:24
  • Logback 使用TurboFilter实现日志级别等内容的动态修改操作

    2022-06-10 04:17:53
  • android 自定义Android菜单背景的代码

    2022-06-23 08:54:40
  • List集合多个复杂字段判断去重的案例

    2022-08-01 16:23:28
  • asp之家 软件编程 m.aspxhome.com