Spring MVC接口防数据篡改和重复提交
本文实例为大家分享了SpringMVC接口防数据篡改和重复提交的具体代码,供大家参考,具体内容如下
一、自定义一个注解,此注解可以使用在方法上或类上
- 使用在方法上,表示此方法需要数据校验
- 使用在类上,表示此类下的所有方法需要数据校验
- 此注解对无参数方法不起作用
importorg.springframework.stereotype.Component; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public@interfaceDataValidate{ }
二、自定义拦截,拦截前端所有请求
1、检查此接口调用的方法或方法所在的类是否使用了DataValidate注解,若没有使用,表示此接口不需要校验数据;
2、若使用了注解,再检查此方法有没有入参,若没有入参,不需要校验数据,否在需要校验;
3、把前端传来的所有参数(除了签名参数)按照参数升序生成一个json字符串(使用TreeMap方式自动排序);
4、把生成的json字符串通过MD5加密的结果和前端传的签名值对比,若不相等,表示此数据被篡改过,否在没有被篡改过;
5、数据是否被篡改校验完毕,若前端传了用户唯一标示(token),表示需要校验数据是否重复提交;
6、若签名和上次提交的数据的签名相等,表示是重复提交数据,若不相等,把签名保存下来,表示数据不是重复提交。
importjava.security.MessageDigest; importjava.util.Map; importjava.util.Set; importjava.util.TreeMap; importjava.util.concurrent.ConcurrentHashMap; importjavax.annotation.PreDestroy; importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importorg.springframework.beans.factory.annotation.Value; importorg.springframework.core.MethodParameter; importorg.springframework.web.method.HandlerMethod; importorg.springframework.web.servlet.handler.HandlerInterceptorAdapter; importcom.alibaba.fastjson.JSON; /** *防数据被篡改和重复提交 */ publicclassDataValidateInterceptorextendsHandlerInterceptorAdapterimplementsRunnable{ publicstaticMapuserToken=newConcurrentHashMap<>(); //过期时间 privatestaticlongEXPIRED_TIME=3600000; privatestaticStringTOKEN_NAME="token"; privatestaticStringSIGN_NAME="sign"; privatevolatilebooleanshutDown; publicDataValidateInterceptor(@Value("${data_interceptor.expired_time}")StringexpiredTime, @Value("${data_interceptor.token_name}")StringtokenName, @Value("${data_interceptor.sign_name}")StringsignName){ 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 publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler) throwsException{ if(validate(request,response,handler)){ /** *实现返回提示数据 */ response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write("参数验证失败!"); returnfalse; } returntrue; } privatebooleanvalidate(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){ if(handlerinstanceofHandlerMethod){ Class>clazz=((HandlerMethod)handler).getBeanType(); DataValidatedataValidate=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){ //方法没有入参不需要校验 returnfalse; } //需要校验 Stringsign=request.getParameter(SIGN_NAME); Map params=request.getParameterMap(); Set paramNames=params.keySet(); Map paramsMap=newTreeMap<>(); for(StringparamName:paramNames){ if(paramName.equals(SIGN_NAME)){ continue; } paramsMap.put(paramName,request.getParameter(paramName)); } StringparamString=JSON.toJSONString(paramsMap).replaceAll("",""); StringMD5Sign=MD5.getMD5(paramString); if(!sign.equals(MD5Sign)){ //数据被篡改 returntrue; } Stringtoken=request.getParameter(TOKEN_NAME); if(token!=null){ if(userToken.containsKey(token)){ TokenValuetokenValue=userToken.get(token); if(tokenValue.getValue().equals(sign)){ //数据已经提交过 returntrue; }else{ tokenValue.setValue(sign); } }else{ userToken.put(token,newTokenValue(sign)); } } } } returnfalse; } @Override publicvoidrun(){ try{ while(!shutDown){ synchronized(this){ wait(EXPIRED_TIME); Set keys=userToken.keySet(); for(Stringkey:keys){ if((userToken.get(key).getExpiredTime()+EXPIRED_TIME)<=System.currentTimeMillis()){ userToken.remove(key); } } } } }catch(Exceptione){ e.printStackTrace(); } } @PreDestroy publicvoidcustDestroy(){ shutDown=true; synchronized(this){ notifyAll(); } } privatestaticclassMD5{ /** *向getMD5方法传入一个你需要转换的原始字符串,将返回字符串的MD5码 * *@paramcode原始字符串 *@return返回字符串的MD5码 */ privatestaticStringgetMD5(Stringcode){ try{ MessageDigestmessageDigest=MessageDigest.getInstance("MD5"); byte[]bytes=code.getBytes(); byte[]results=messageDigest.digest(bytes); StringBuilderstringBuilder=newStringBuilder(); for(byteresult:results){ //将byte数组转化为16进制字符存入stringbuilder中 stringBuilder.append(String.format("%02x",result)); } returnstringBuilder.toString(); }catch(Exceptione){ e.printStackTrace(); return""; } } } } publicclassTokenValue{ privateStringvalue; privatelongexpiredTime; publicTokenValue(Stringvalue){ this.value=value; this.expiredTime=System.currentTimeMillis(); } publicStringgetValue(){ returnvalue; } publicvoidsetValue(Stringvalue){ this.value=value; this.expiredTime=System.currentTimeMillis(); } publiclonggetExpiredTime(){ returnexpiredTime; } }
三、使用
后端使用:
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"}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。