Spring MVC接口防数据篡改和重复提交

本文实例为大家分享了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"}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring mvc防止数据重复提交的方法

    本文实例为大家分享了Spring mvc如何防止数据重复提交,供大家参考,具体内容如下 方法分析: 这里使用的防止数据重复提交的方法是使用token,给所有的url加一个拦截器,在拦截器里面用java的UUID生成一个随机的UUID并把这个UUID放到session里面,然后在浏览器做数据提交的时候将此UUID提交到服务器.服务器在接收到此UUID后,检查一下该UUID是否已经被提交,如果已经被提交,则不让逻辑继续执行下去. 源码实现: 注解Token代码: @Target(ElementTyp

  • springmvc接收jquery提交的数组数据代码分享

    这里分享给大家的代码是springmvc接收jquery提交的数组数据的相关内容,具体代码如下: var selectedUsers = $('#users').tagbox('getValues'); if (selectedUsers.length > 0) { $.post(appPath + "/role/users/add/", { 'systemID' : $('#systemID').combobox('getValue'), 'roleID' : roleID,

  • Spring MVC接口防数据篡改和重复提交

    本文实例为大家分享了Spring MVC接口防数据篡改和重复提交的具体代码,供大家参考,具体内容如下 一.自定义一个注解,此注解可以使用在方法上或类上 使用在方法上,表示此方法需要数据校验 使用在类上,表示此类下的所有方法需要数据校验 此注解对无参数方法不起作用 import org.springframework.stereotype.Component; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionP

  • Java Spring MVC获取请求数据详解操作

    目录 1. 获得请求参数 2. 获得基本类型参数 3. 获得POJO类型参数 4. 获得数组类型参数 5. 获得集合类型参数 6. 请求数据乱码问题 7. 参数绑定注解 @requestParam 8. 获得Restful风格的参数 9. 自定义类型转换器 1.定义转换器类实现Converter接口 2.在配置文件中声明转换器 3.在<annotation-driven>中引用转换器 10. 获得Servlet相关API 11. 获得请求头 11.1 @RequestHeader 11.2 @

  • 一个依赖搞定 Spring Boot 接口防盗刷的流程分析

    目录 系统要求​ 工作流程​ 命中规则后 接入使用 注意 配置一览表 kk-anti-reptile 是适用于基于 spring-boot 开发的分布式系统的反爬虫组件. 系统要求​ 基于 spring-boot 开发(spring-boot1.x, spring-boot2.x均可) 需要使用 redis 工作流程​ kk-anti-reptile 使用基于 Servlet 规范的的 Filter 对请求进行过滤,在其内部通过 spring-boot 的扩展点机制,实例化一个 Filter,并

  • 解决spring mvc 返回json数据到ajax报错parseerror问题

    最近使用ajax接收spring mvc传过来的json数据时总是出现parseerror的错误,错误源码如下: 前端: $.ajax({ type: 'POST', url: "groupFunctionEdit", dataType: 'json', contentType: "application/json", data: JSON.stringify(functiondata), success: function(data){ alert('数据加载成功

  • 如何使用新方式编写Spring MVC接口

    1. 前言 通常我们编写 Spring MVC 接口的范式是这样的: @RestController @RequestMapping("/v1/userinfo") public class UserInfoController { @GetMapping("/foo") public String foo() { return "felord.cn"; } } 这种我都写吐了,今天换个口味,使用 Spring 5 新引入的函数式端点(Funct

  • 基于Mock测试Spring MVC接口过程解析

    1. 前言 在Java开发中接触的开发者大多数不太注重对接口的测试,结果在联调对接中出现各种问题.也有的使用Postman等工具进行测试,虽然在使用上没有什么问题,如果接口增加了权限测试起来就比较恶心了.所以建议在单元测试中测试接口,保证在交付前先自测接口的健壮性.今天就来分享一下胖哥在开发中是如何对Spring MVC接口进行测试的. 在开始前请务必确认添加了Spring Boot Test相关的组件,在最新的版本中应该包含以下依赖: <dependency> <groupId>

  • Spring mvc服务端数据校验实现流程详解

    B/S 系统中对http 请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺少的,实际上,几乎所有的系统,凡是涉及到数据校验,都需要在服务端进行二次校验.为什么要在服务端进行二次校验呢?这需要理解客户端校验和服务端校验各自的目的. 客户端校验,我们主要是为了提高用户体验,例如用户输入一个邮箱地址,要校验这个邮箱地址是否合法,没有必要发送到服务端进行校验,直接在前端用 js 进行校验即可.但是大家需要明白的是,前端校验无法代替后端校验

  • spring MVC中接口参数解析的过程详解

    前言 前天工作中遇到了这样一个问题,我在接口的参数封装了一个pojo,这是很常见的,当参数一多,惯性的思维就是封装一个pojo.那么在参数前有很多注解可以添加,比如:@requestParam,@requestBody,@pathvariable等.我的理解是这样的,首先我先申明,我并是没有看过源码,只是凭经验理解.@requestParam试用于get请求,参数在http的header中的URL上,具体放在?后面以key=value的形式存在.@requestBody适用于post请求中参数在

  • Spring Boot如何利用拦截器加缓存完成接口防刷操作

    目录 为什么需要接口防刷 技术解析 主要代码 测试结果 总结 为什么需要接口防刷 为了减缓服务器压力,将服务器资源留待给有价值的请求,防止恶意访问,一般的程序都会有接口防刷设置,接下来介绍一种简单灵活的接口防刷操作 技术解析 主要采用的技术还是拦截+缓存,我们可以通过自定义注解,将需要防刷的接口给标记出来管理,利用缓存统计指定时间区间里,具体的某个ip访问某个接口的频率,如果超过某个阈值,就让他进一会儿小黑屋,到期自动解放 主要代码 前置环境搭建,Spring Boot项目,引入Web和Redi

随机推荐