沧州三亚菏泽经济预测自然
投稿投诉
自然科学
知识物理
化学生物
地理解释
预测理解
本质社会
人类现象
行为研究
经济政治
心理结构
关系指导
人文遗产
菏泽德阳
山西湖州
宝鸡上海
茂名内江
三亚信阳
长春北海
西安安徽
黄石烟台
沧州湛江
肇庆鹤壁
六安韶关
成都钦州

SpringBoot如何保证接口安全?老鸟们都是这么玩的!

12月16日 生死族投稿
  为什么要保证接口安全
  对于互联网来说,只要你系统的接口暴露在外网,就避免不了接口安全问题。如果你的接口在外网裸奔,只要让黑客知道接口的地址和参数就可以调用,那简直就是灾难。
  举个例子:你的网站用户注册的时候,需要填写手机号,发送手机验证码,如果这个发送验证码的接口没有经过特殊安全处理,那这个短信接口早就被人盗刷不知道浪费多少钱了。
  那如何保证接口安全呢?
  一般来说,暴露在外网的api接口需要做到防篡改和防重放才能称之为安全的接口。防篡改
  我们知道http是一种无状态的协议,服务端并不知道客户端发送的请求是否合法,也并不知道请求中的参数是否正确。
  举个例子,现在有个充值的接口,调用后可以给用户增加对应的余额。http:localhostapiuserrecharge?userid1001amount10
  如果非法用户通过抓包获取到接口参数后,修改userid或amount的值就可以实现给任意账户添加余额的目的。如何解决
  采用https协议可以将传输的明文进行加密,但是黑客仍然可以截获传输的数据包,进一步伪造请求进行重放攻击。如果黑客使用特殊手段让请求方设备使用了伪造的证书进行通信,那么https加密的内容也会被解密。
  一般的做法有2种:采用https方式把接口的数据进行加密传输,即便是被黑客破解,黑客也花费大量的时间和精力去破解。接口后台对接口的请求参数进行验证,防止被黑客篡改;
  步骤1:客户端使用约定好的秘钥对传输的参数进行加密,得到签名值sign1,并且将签名值也放入请求的参数中,发送请求给服务端步骤2:服务端接收到客户端的请求,然后使用约定好的秘钥对请求的参数再次进行签名,得到签名值sign2。步骤3:服务端比对sign1和sign2的值,如果不一致,就认定为被篡改,非法请求。防重放
  防重放也叫防复用。简单来说就是我获取到这个请求的信息之后什么也不改,,直接拿着接口的参数去重复请求这个充值的接口。此时我的请求是合法的,因为所有参数都是跟合法请求一模一样的。重放攻击会造成两种后果:针对插入数据库接口:重放攻击,会出现大量重复数据,甚至垃圾数据会把数据库撑爆。针对查询的接口:黑客一般是重点攻击慢查询接口,例如一个慢查询接口1s,只要黑客发起重放攻击,就必然造成系统被拖垮,数据库查询被阻塞死。
  对于重放攻击一般有两种做法:基于timestamp的方案
  每次HTTP请求,都需要加上timestamp参数,然后把timestamp和其他参数一起进行数字签名。因为一次正常的HTTP请求,从发出到达服务器一般都不会超过60s,所以服务器收到HTTP请求之后,首先判断时间戳参数与当前时间比较,是否超过了60s,如果超过了则认为是非法请求。
  一般情况下,黑客从抓包重放请求耗时远远超过了60s,所以此时请求中的timestamp参数已经失效了。如果黑客修改timestamp参数为当前的时间戳,则sign1参数对应的数字签名就会失效,因为黑客不知道签名秘钥,没有办法生成新的数字签名。
  但是这种方式的漏洞也是显而易见,如果在60s之内进行重放攻击,那就没办法了,所以这种方式不能保证请求仅一次有效。
  老鸟们一般会采取下面这种方案,既可以解决接口重放问题,又可以解决一次请求有效的问题。基于noncetimestamp的方案
  nonce的意思是仅一次有效的随机字符串,要求每次请求时该参数要保证不同。实际使用用户信息时间戳随机数等信息做个哈希之后,作为nonce参数。
  此时服务端的处理流程如下:去redis中查找是否有key为nonce:{nonce}的string如果没有,则创建这个key,把这个key失效的时间和验证timestamp失效的时间一致,比如是60s。如果有,说明这个key在60s内已经被使用了,那么这个请求就可以判断为重放请求。
  这种方案nonce和timestamp参数都作为签名的一部分传到后端,基于timestamp方案可以让黑客只能在60s内进行重放攻击,加上nonce随机数以后可以保证接口只能被调用一次,可以很好的解决重放攻击问题。代码实现
  接下来以SpringBoot项目为例看看如何实现接口的防篡改和防重放功能。
  1、构建请求头对象DataBuilderpublicclassRequestHeader{privateSprivateLprivateS}
  2、工具类从HttpServletRequest获取请求参数Slf4jUtilityClasspublicclassHttpDataUtil{post请求处理:获取Body参数,转换为SortedMapparamrequestpublicSortedMapString,StringgetBodyParams(finalHttpServletRequestrequest)throwsIOException{byte〔〕requestBodyStreamUtils。copyToByteArray(request。getInputStream());StringbodynewString(requestBody);returnJsonUtil。json2Object(body,SortedMap。class);}get请求处理:将URL请求参数转换成SortedMappublicstaticSortedMapString,StringgetUrlParams(HttpServletRequestrequest){SSortedMapString,StringresultnewTreeMap();if(StringUtils。isEmpty(request。getQueryString())){}try{paramURLDecoder。decode(request。getQueryString(),utf8);}catch(UnsupportedEncodingExceptione){e。printStackTrace();}String〔〕paramsparam。split();for(Strings:params){String〔〕arrays。split();result。put(array〔0〕,array〔1〕);}}}
  这里的参数放入SortedMap中对其进行字典排序,前端构建签名时同样需要对参数进行字典排序。
  3、签名验证工具类Slf4jUtilityClasspublicclassSignUtil{验证签名验证算法:把timestampJsonUtil。object2Json(SortedMap)合成字符串,然后MD5SneakyThrowspublicbooleanverifySign(SortedMapString,Stringmap,RequestHeaderrequestHeader){StringparamsrequestHeader。getNonce()requestHeader。getTimestamp()JsonUtil。object2Json(map);returnverifySign(params,requestHeader);}验证签名publicbooleanverifySign(Stringparams,RequestHeaderrequestHeader){log。debug(客户端签名:{},requestHeader。getSign());if(StringUtils。isEmpty(params)){}log。info(客户端上传内容:{},params);StringparamsSignDigestUtils。md5DigestAsHex(params。getBytes())。toUpperCase();log。info(客户端上传内容加密后的签名结果:{},paramsSign);returnrequestHeader。getSign()。equals(paramsSign);}}
  4、HttpServletRequest包装类publicclassSignRequestWrapperextendsHttpServletRequestWrapper{用于将流保存下来privatebyte〔〕requestBpublicSignRequestWrapper(HttpServletRequestrequest)throwsIOException{super(request);requestBodyStreamUtils。copyToByteArray(request。getInputStream());}OverridepublicServletInputStreamgetInputStream()throwsIOException{finalByteArrayInputStreambaisnewByteArrayInputStream(requestBody);returnnewServletInputStream(){OverridepublicbooleanisFinished(){}OverridepublicbooleanisReady(){}OverridepublicvoidsetReadListener(ReadListenerreadListener){}Overridepublicintread()throwsIOException{returnbais。read();}};}OverridepublicBufferedReadergetReader()throwsIOException{returnnewBufferedReader(newInputStreamReader(getInputStream()));}}
  防篡改和防重放我们会通过SpringBootFilter来实现,而编写的filter过滤器需要读取request数据流,但是request数据流只能读取一次,需要自己实现HttpServletRequestWrapper对数据流包装,目的是将request流保存下来。
  5、创建过滤器实现安全校验ConfigurationpublicclassSignFilterConfiguration{Value({sign。maxTime})privateStringsignMaxTfilter中的初始化参数privateMapString,StringinitParametersMapnewHashMap();BeanpublicFilterRegistrationBeancontextFilterRegistrationBean(){initParametersMap。put(signMaxTime,signMaxTime);FilterRegistrationBeanregistrationnewFilterRegistrationBean();registration。setFilter(signFilter());registration。setInitParameters(initParametersMap);registration。addUrlPatterns(sign);registration。setName(SignFilter);设置过滤器被调用的顺序registration。setOrder(1);}BeanpublicFiltersignFilter(){returnnewSignFilter();}}Slf4jpublicclassSignFilterimplementsFilter{ResourceprivateRedisUtilredisU从fitler配置中获取sign过期时间privateLongsignMaxTprivatestaticfinalStringNONCEKEYOverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{HttpServletRequesthttpRequest(HttpServletRequest)servletRHttpServletResponsehttpResponse(HttpServletResponse)servletRlog。info(过滤URL:{},httpRequest。getRequestURI());HttpServletRequestWrapperrequestWrappernewSignRequestWrapper(httpRequest);构建请求头RequestHeaderrequestHeaderRequestHeader。builder()。nonce(httpRequest。getHeader(xNonce))。timestamp(Long。parseLong(httpRequest。getHeader(XTime)))。sign(httpRequest。getHeader(XSign))。build();验证请求头是否存在if(StringUtils。isEmpty(requestHeader。getSign())ObjectUtils。isEmpty(requestHeader。getTimestamp())StringUtils。isEmpty(requestHeader。getNonce())){responseFail(httpResponse,ReturnCode。ILLEGALHEADER);}1。重放验证判断timestamp时间戳与当前时间是否操过60s(过期时间根据业务情况设置),如果超过了就提示签名过期。longnowSystem。currentTimeMillis()1000;if(nowrequestHeader。getTimestamp()signMaxTime){responseFail(httpResponse,ReturnCode。REPLAYERROR);}2。判断noncebooleannonceExistsredisUtil。hasKey(NONCEKEYrequestHeader。getNonce());if(nonceExists){请求重复responseFail(httpResponse,ReturnCode。REPLAYERROR);}else{redisUtil。set(NONCEKEYrequestHeader。getNonce(),requestHeader。getNonce(),signMaxTime);}SortedMapString,StringparamMswitch(httpRequest。getMethod()){caseGET:paramMapHttpDataUtil。getUrlParams(requestWrapper);acceptSignUtil。verifySign(paramMap,requestHeader);casePOST:paramMapHttpDataUtil。getBodyParams(requestWrapper);acceptSignUtil。verifySign(paramMap,requestHeader);default:}if(accept){filterChain。doFilter(requestWrapper,servletResponse);}else{responseFail(httpResponse,ReturnCode。ARGUMENTERROR);}}privatevoidresponseFail(HttpServletResponsehttpResponse,ReturnCodereturnCode){ResultDataObjectresultDataResultData。fail(returnCode。getCode(),returnCode。getMessage());WebUtils。writeJson(httpResponse,resultData);}Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{StringsignTimefilterConfig。getInitParameter(signMaxTime);signMaxTimeLong。parseLong(signTime);}}
  6、Redis工具类ComponentpublicclassRedisUtil{ResourceprivateRedisTemplateString,ObjectredisT判断key是否存在paramkey键returntrue存在false不存在publicbooleanhasKey(Stringkey){try{returnBoolean。TRUE。equals(redisTemplate。hasKey(key));}catch(Exceptione){e。printStackTrace();}}普通缓存放入并设置时间paramkey键paramvalue值paramtime时间(秒)time要大于0如果time小于等于0将设置无限期returntrue成功false失败publicbooleanset(Stringkey,Objectvalue,longtime){try{if(time0){redisTemplate。opsForValue()。set(key,value,time,TimeUnit。SECONDS);}else{set(key,value);}}catch(Exceptione){e。printStackTrace();}}普通缓存放入paramkey键paramvalue值returntrue成功false失败publicbooleanset(Stringkey,Objectvalue){try{redisTemplate。opsForValue()。set(key,value);}catch(Exceptione){e。printStackTrace();}}}
  作者:飘渺Jam
  链接:https:juejin。cnpost7195355046065176631
投诉 评论 转载

招6242人!江苏省2023年省市事业单位统一公开招聘启动现代快报讯(记者徐苏宁)在找工作的小伙伴看过来!江苏省2023年省属事业单位统一公开招聘启动了!2月23日,中共江苏省委组织部、江苏省人力资源和社会保障厅召开新闻发布会介绍了相……红网夜读人生最美妙的风景,竟是用声音传递温暖,欢迎收听红网夜读,我是田萌,田野的田,萌芽的萌。钱钟书曾说过:洗一个澡,看一朵花,吃一顿饭,即使你觉得快活,并非全因为澡洗得干净,花开得好,或者菜合你口味……北北换房梦去年出台房产税新政之后,我就动了换房的念头。心想一来别浪费了政策;二来现在的住处地段欠佳,电梯厅的墙面已斑驳,走进大厅就觉老旧。加之我朋友卖了旧屋打新,新房和二手房价格倒挂,一……深入建构新闻评论公共性与交往性的理论著作评新闻评论与公共性理胡沈明(江西师范大学新闻与传播学院副院长、教授、博士生导师)苏蕾的新著《新闻评论与公共性理论建构研究》出版了,本书脱胎于其博士论文,不过此时距离她博士毕业已愈12年。12……王莽究竟做了什么?会被现代人戏称穿越者首先介绍一下王莽的生平和如何改汉为新的。王莽生于汉初元四年(公元前45年)。王莽出生的时候,王氏因为王政君的原因已经成为首屈一指的外戚家族。等到汉元帝过世,汉成帝继位,王……外卖小哥超时接到投诉,民警一把抢过手机近日,浙江嘉兴外卖小哥周家其在送餐途中遇到一男子欲轻生他冲上前将对方紧紧抱住坚持到警方到来因为订单超时,他接到投诉电话民警一把抢过手机帮忙解……全球最大墓地600万人共葬此地,随便一挖都是古墓公元661年1月26日,伊斯兰教第四任哈里发阿里伊本艾比塔利卜,在库法的公开活动中遭遇毒刀刺杀,最终伤势过重身亡,弥留时叮嘱需秘密安葬。阿里的儿子将父亲遗体放在骆驼背上,……SpringBoot如何保证接口安全?老鸟们都是这么玩的!为什么要保证接口安全对于互联网来说,只要你系统的接口暴露在外网,就避免不了接口安全问题。如果你的接口在外网裸奔,只要让黑客知道接口的地址和参数就可以调用,那简直就是灾难。……经典老照片1958年,大跃进摄影巨擘布列松和中国有着不解之缘。他的摄影集《从一个中国到另一个中国》,还有后来出版的法国黑皮书《中国》都是在西方极为畅销的经典。透过这些珍贵的照片,不但让西方人了解了中国,也……鸣锣开市!时间京选好物市集开市了19:0023:00抖音搜索时间京选时间京选好物市集鸣锣开市第一步扫码关注时间京选主页爆品好物平面设计以视觉作为沟通和表现的方式透过多……与普京不同,泽连斯基更换国防部长,直接原因与战场局势无关很多人就问乌克兰换将对他的战事会不会有影响?首先我要告诉大家,乌克兰真的换将了,但是为什么换将?是反腐的一个举措。我们知道,泽连斯基在战争开始以后一直使用的是现任的几个头……中海信托拟4亿转让国联期货,两大买家有望接手!券商系期货公司一个多月前就将国联期货39股权挂牌转让的中海信托,仍然没有找到下家。记者从上海产权交易所获悉,该笔股权转让底价为4。11亿元,较评估价溢价约1。7,首个披露期自2022年……
纸上得来终觉浅!每次外国发生战争国内总会陨落评论界的将星推广无论你春天穿什么,有它都会加分!清风不计一身不争一时各方热议网络安全保险目前我国共有30余家保险公司备案了77款你只知诸葛亮以奇制胜,曹操奸雄一世,可知二人同样重视子女教育北向资金单日净卖出33。75亿元,减仓电气设备机械设备化工星云法师的教育事业一直赔钱也要办学校浅析1830年革命中的君主制,探讨从拉斐特到比利时宪法苏联建设的三大工程,俄罗斯至今受益Google后开发的Carbon真的会取代C吗?周王室是天下共主为什么不扩张自己土地?吉林扶余,一个在县县级市市辖区之间不断切换身份的城市

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找