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);
Mapparams=request.getParameterMap();
SetparamNames=params.keySet();
MapparamsMap=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);
Setkeys=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"}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。