SpringMVC接口怎么防数据篡改和重复提交
创始人
2024-12-22 08:22:10
0次

一、自定义一个注解,此注解可以使用在方法上或类上

使用在方法上,表示此方法需要数据校验  使用在类上,表示此类下的所有方法需要数据校验  此注解对无参数方法不起作用

import org.springframework.stereotype.Component; @Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @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 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 params = request.getParameterMap(); Set paramNames = params.keySet(); Map 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 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"}

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...