如何设计一个安全的对外接口,老司机总结了这几点

news2024/11/27 8:36:18

博主之前做过恒丰银行代收付系统(相当于支付接口),包括现在的oltpapi交易接口和虚拟业务的对外提供数据接口。总之,当你做了很多项目写了很多代码的时候,就需要回过头来,多总结总结,这样你会看到更多之前写代码的时候看不到的东西,也能更明白为什么要这样做。

做接口需要考虑的问题

什么是接口

接口无非就是客户端请求你的接口地址,并传入一堆该接口定义好的参数,通过接口自身的逻辑处理,返回接口约定好的数据以及相应的数据格式。

接口怎么开发

接口由于本身的性质,由于和合作方对接数据,所以有以下几点需要在开发的时候注意:

1.定义接口入参:写好接口文档

2.定义接口返回数据类型:一般都需要封装成一定格式,确定返回json还是xml报文等

编辑

添加图片注释,不超过 140 字(可选)

见如下返回数据定义格式:

 
 

package com.caiex.vb.model; import java.io.Serializable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "Result", propOrder = { "resultCode", "resultMsg" }) public class Result implements Serializable { private static final long serialVersionUID = 10L; protected int resultCode; protected String resultMsg; public int getResultCode() { return this.resultCode; } public void setResultCode(int value) { this.resultCode = value; } public String getResultMsg() { return this.resultMsg; } public void setResultMsg(String value) { this.resultMsg = value; } }

 

package com.caiex.vb.model; import java.io.Serializable; public class Response implements Serializable { private static final long serialVersionUID = 2360867989280235575L; private Result result; private Object data; public Result getResult() { if (this.result == null) { this.result = new Result(); } return result; } public void setResult(Result result) { this.result = result; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }

3.确定访问接口的方式,get or post等等,可以根据restful接口定义规则RESTful API:RESTful API

4.定义一套全局统一并通用的返回码,以帮助排查问题;

 
 

public static int NO_AGENT_RATE = 1119; //未找到兑换率 public static int SCHEME_COMMIT_FAIL = 4000; //方案提交失败 public static int SCHEME_CONFIRMATION = 4001; //方案确认中 public static int SCHEME_NOT_EXIST = 4002; //方案不存在 public static int SCHEME_CANCEL= 4005; //方案不存在 //。。。。

5.统一的异常处理:应该每个系统都需要一套统一的异常处理

 
 

package com.caiex.vb.interceptor; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import com.caiex.vb.model.Response; @ControllerAdvice @ResponseBody public class GlobalExceptionHandler { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 所有异常报错 * @param request * @param exception * @return * @throws Exception */ @ExceptionHandler(value=Exception.class) public Response allExceptionHandler(HttpServletRequest request, Exception exception) throws Exception { logger.error("拦截到异常:", exception); Response response = new Response(); response.setData(null); response.getResult().setResultCode(9999); response.getResult().setResultMsg("系统繁忙"); return response; } }

6.拦截器链设置:合作方访问接口的时候,会根据你接口定义好的传参访问你的接口服务器,但是会存在接口参数类型错误或者格式不对,必传参数没传的问题,甚至一些恶意请求,都可以通过拦截器链进行前期拦截,避免造成接口服务的压力。

学习资料:Java进阶视频资源

还有很重要的一点,加签验签也可以在拦截器设置。继承WebMvcConfigurerAdapter实现springboot的拦截器链。实现HandlerInterceptor方法编写业务拦截器。

 
 

package com.caiex.vb.interceptor; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.alibaba.fastjson.JSON; import com.caiex.redis.service.api.RedisApi; import com.caiex.vb.model.Response; import com.caiex.vb.utils.CaiexCheckUtils; @Component public class SignInterceptor extends BaseValidator implements HandlerInterceptor{ private Logger logger = LogManager.getLogger(this.getClass()); @Resource private RedisApi redisApi; public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { // TODO Auto-generated method stub } public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { // TODO Auto-generated method stub } public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { if(isTestIpAddr(arg0)){ return true; } String securityKey = redisApi.hGet("securityKey", arg0.getParameter("agentid")); if(StringUtils.isEmpty(securityKey)){ Response response = new Response(); response.setData(null); response.getResult().setResultCode(8001); response.getResult().setResultMsg("缺少私钥, 渠道号:" + arg0.getParameter("agentid")); logger.error("缺少私钥, 渠道号:" + arg0.getParameter("agentid")); InterceptorResp.printJson(arg1, response); return false; } if(StringUtils.isEmpty(arg0.getParameter("sign")) || !arg0.getParameter("sign").equals(CaiexCheckUtils.getSign(arg0.getParameterMap(), securityKey))){ Response response = new Response(); response.setData(null); response.getResult().setResultCode(3203); response.getResult().setResultMsg("参数签名认证失败"); logger.error("参数签名认证失败:" + JSON.toJSONString(arg0.getParameterMap()) + " securityKey = " + securityKey); InterceptorResp.printJson(arg1, response); return false; }else{ return true; } } }

 

package com.caiex.oltp.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.caiex.oltp.interceptor.APILimitRateValidator; import com.caiex.oltp.interceptor.CommonValidator; import com.caiex.oltp.interceptor.DDSAuthValidator; import com.caiex.oltp.interceptor.QueryPriceParamsValidator; import com.caiex.oltp.interceptor.TradeParamsValidator; @EnableWebMvc @Configuration @ComponentScan public class WebAppConfigurer extends WebMvcConfigurerAdapter { @Bean CommonValidator commonInterceptor() { return new CommonValidator(); } @Bean DDSAuthValidator ddsAuthInterceptor() { return new DDSAuthValidator(); } @Bean QueryPriceParamsValidator queryPriceParamsInterceptor() { return new QueryPriceParamsValidator(); } @Bean TradeParamsValidator tradeParamsInterceptor() { return new TradeParamsValidator(); } @Bean APILimitRateValidator aPILimitRateInterceptor() { return new APILimitRateValidator(); } @Override public void addInterceptors(InterceptorRegistry registry) { //访问速率限制 registry.addInterceptor(aPILimitRateInterceptor()) .addPathPatterns("/*/*"); //.addPathPatterns("/price/getPriceParam"); //参数签名认证 registry.addInterceptor(ddsAuthInterceptor()) .addPathPatterns("/tradeState/*") .addPathPatterns("/recycle/*") .addPathPatterns("/matchInfo/*") .addPathPatterns("/price/tradeTicketParam"); //公共参数检查 registry.addInterceptor(commonInterceptor()) .addPathPatterns("/price/tradeTicketParam") .addPathPatterns("/tradeState/*") .addPathPatterns("/recycle/*"); //询价参数校验 registry.addInterceptor(queryPriceParamsInterceptor()) .addPathPatterns("/price/getPriceParam"); //交易参数检查 registry.addInterceptor(tradeParamsInterceptor()) .addPathPatterns("/price/tradeTicketParam"); super.addInterceptors(registry); } }

7.token令牌和sign数字签名实现数据保密性。

创建令牌(Token)

为保证请求的合法性,我们提供第三方创建令牌接口,某些接口需要通过token验证消息的合法性,以免遭受非法攻击。

token过期时间目前暂时定为1天,由于考虑到合作方往往是分布式环境,多台机器都有可能申请token,为了降低合作方保证token一致性的难度,调用接口创建token成功以后一分钟以内,再次请求token返回的数据是一样的。

获取私钥

获取用于数字签名的私钥,第三方获取的私钥需妥善保存,并定期更新,私钥只参与数字签名,不作为参数传输。

数字签名方式:

参数签名;签名方式:所有值不为null的参数(不包括本参数)均参与数字签名,按照“参数名+参数值+私钥”的格式得到一个字符串,再将这个字符串MD5一次就是这个参数的值。(示例:h15adc39y9ba59abbe56e057e60f883g),所以需要先获取私钥。

验签方式:

将用户的所有非null参数放入定义好排序规则的TreeSet中进行排序,再用StringBuilder按照按照“参数名+参数值+私钥”的格式得到一个字符串(私钥从redis拿),再将这个字符串MD5一次就是这个参数的值。将这个值与用户传来的sign签名对比,相同则通过,否则不通过。

 
 

private String createToken(){ String utk = "Msk!D*"+System.currentTimeMillis()+"UBR&FLP"; logger.info("create token --- "+Md5Util.md5(utk)); return Md5Util.md5(utk); }

8.接口限流

有时候服务器压力真的太大,以防交易接口被挤死,就可以对一些其他不影响主要业务功能并且计算量大的接口做限流处理。RateLimit--使用guava来做接口限流,当接口超过指定的流量时,就不处理该接口的请求。详细可看RateLimit。也可参考其他限流框架。

9.协议加密,http升级成https;

为什么要升级呢,为了保证数据的安全性。当使用https访问时,数据从客户端到服务断,服务端到客户端都加密,即使黑客抓包也看不到传输内容。当然还有其他好处,这里不多讲。但这也是开发接口项目需要注意的一个问题。

如何提高接口的高并发和高可用

接口开发好了,接下来就讨论接口的可用性问题。首先我们要将高并发和高可用区分一下,毕竟高可用是在可用的情况,只是很慢或者效率不高。其实也可以归为一类问题,但是不重要啦,重要的是怎么提高你写的接口的访问速度和性能。

接口的高并发解决方案(其实没有唯一答案,业界针对不同业务也有很多不同的方法)

当访问一个接口获取数据时,发现返回很慢,或者总是超时,如果排除网络的原因,那就是接口服务器压力太大,处理不过来了。在世界杯期间,我们查看后台日志总是connection by reset和borker pipe和一些超时问题。

这时候,你可能遇到了高并发和高可用问题。但是,不管遇到什么问题,都不能臆断和乱改,你得需要找到慢的原因,才能对症下药,乱改可能会导致其他问题的出现。首先,解决高并发问题的三个方向是负载均衡,缓存和集群。

学习资料:Java进阶视频资源

负载均衡

我们使用的是阿里云服务器的负载均衡,后台分布式服务管理,我们运维小哥哥搭建了一套k8s,可以自由在k8s上扩展服务节点,各个服务结点也能随内存的使用自动漂移,不用多说,k8s真的很厉害,感兴趣的同学可以详细去学。那么问题来了,阿里云的负载均衡怎么对应到k8s的负载均衡呢?

这个涉及到了k8s的service暴露的一些特点,简单说就是k8s把所有集群的服务都通过指定的内部负载均衡,在指定的服务器上暴露,然后我们又把这几个服务器接在阿里云负载均衡下,这个涉及的细节和配置很多。当然,除nginx外,还有其他负载均衡解决方案,软件硬件都有,硬件如f5等。

阿里云的nginx负载均衡,我们使用的是加权轮询策略,其实轮询是最低效的方式;

这就是最基本的负载均衡实例,但这不足以满足实际需求;目前Nginx服务器的upstream模块支持6种方式的分配:

负载均衡策略

轮询默认方式weight权重方式ip_hash依据ip分配方式least_conn最少连接方式fair(第三方)响应时间方式url_hash(第三方)依据URL分配方式

集群

首先,通过排查问题,发现是oltpapi接口服务处理请求很慢,大量请求过来,总是超时和中断连接,这时候,我们想着最简单的方法就是加机器,给oltp接口服务多加几台机器。

嗯,一切都很完美,如预期进行,但是加到一定数量,你发现,怎么不起效果,异步响应还是很慢,或者更直观的说,消息队列出现了严重的消息堆积。这时候,你发现出现了新的问题或者瓶颈,这个问题已经不是说加oltp服务器能解决了,那么,就需要去重新定位问题。发现是消息堆积,消息堆积就是生产者过快,导致消费者消费不过来,这时候,你就需要增加消费者的消费数量。给风控系统多加几台机器,让消费者和生产者达到一定平衡。

这里有个误区,你可能以为是rocketmq的broker数量过少,增加broker数量,其实当消费者和生产者保持一样的速度时,消息肯定不对堆积,按照原始的broker数量就足够。但是增加broker也会使得消息得到尽快的处理,提升一定效率。

缓存

当加机器不能解决问题时,或者说没那么多服务器可使用时,那么就要重代码层面解决高并发问题。Redis 是一个高性能的key-value数据库,当获取数据从数据库拿很慢时,就可以存储到redis,从redis取值。

  • 用ConcurrentHashMap缓存对象,并设置过期时间

  • redis缓存数据,结合spring定时任务定时获取不会经常改动的key

  • 提高使用redis的效率:比如使用mGet一次获取多个key

  • ....等

接口高可用问题

高可用问题应该上升到整个服务的架构问题上,就是说在搭建整体系统是就应该考虑到。高可用问题是以单点故障,访问速度慢的问题为主导。见 服务高可用

  • redis主从分布式(redis的单点故障和访问速度的提高和主从备份)

  • 分布式dubbo服务的zookeeper主从集群

  • strom的主从集群

  • ...等

总结

下面对接口开发服务做一些总结:

1.是拉还是推:

当接口作为数据源时,还要考虑数据是让合作方主动过来拉还是数据有变化就推送呢,当然是推的效果更好,但是如何有效的推数据,不推重复数据等都是需要根据实际业务考虑的问题。

2.多台分布式服务器上,怎么保证交易的幂等和订单的唯一性

当接口服务和合作方都处于分布式情况下,就很容易出现一个订单号申请多次交易请求,但是根据幂等性,一张彩票只能交易一次,并且每次不管何时请求,结果都应该一样不会改变。这种情况下,我们怎么保证唯一性呢,我们需要把该订单和订单状态存redis,每次请求时去看是否订单已存在。但可能这次交易不成功,下次这张票还可以继续交易,可以生成新的订单号啊。

redis的setNX是一个很好的解决方案,意思是当存在该key时,返回false,当没有时,该key和value插入成功。用作检查订单是否正在提交,如果是,则阻塞本次请求,避免重复提交 ,可以设置过期时间3s。提交之前锁定订单,防止重复提交。

3.处理时间超过10s,自动返回该订单交易失败

总之,博主发现,在高并发场景下,导致服务崩溃的原因还是redis和数据库,可能是redis读写太慢,或者数据库的一些sql使用不当,或者没建索引导致读写很慢。

总之,这是一条很漫长的路,我们都需要慢慢积累经验和学习前人更优秀的解决办法。

                                           资源获取:

大家点赞、收藏、关注、评论啦 、查看👇🏻👇🏻👇🏻微信公众号获取联系方式👇🏻👇🏻👇🏻

 精彩专栏推荐订阅:下方专栏👇🏻👇🏻👇🏻👇🏻

每天学四小时:Java+Spring+JVM+分布式高并发,架构师指日可待

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/88616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业选择适合的云存储的4个技巧

免费和廉价的个人和小型企业云存储无处不在,因此人们需要选择一种更适合自己的云存储。 而面对越来越多的选择,如何选择更适合自己的云存储产品呢?在以往,大多数人根据所获得的存储量多少而决定,这种方法虽然简单,但无…

C语言作用域与内存布局

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 C语言作用域与内存布局作用域1. 局部变量2. 静态局部变量3. 全局变量4. 静态全局变量5. extern声明全局…

[附源码]Python计算机毕业设计SSM基于RationalRose的教务管理系统开发(程序+LW)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

css flex布局 —— 项目属性 align-self

align-self属性定义 flex 子项单独在侧轴(纵轴)方向上的对齐方式,可覆盖 align-items 属性。 默认值为 auto,表示继承父元素的 align-items 属性,如果没有父元素,则等同于 stretch。 语法 .item {align-…

Web3中文|跑路的NFT项目Frosties,是如何被“绳之以法”?

本文,我们将深入探讨传说中的NFT项目Frosties跑路过程,以及该项目的创始人在私吞超100 万美元后是如何被抓获的。 Frosties NFT跑路过程 Frosties是一个由8888幅作品组成的NFT系列,具有Doodles的轻松、有趣的风格。该系列于2022年1月9日上线…

Talk预告 | 阿里巴巴达摩院算法工程师许贤哲:DAMO-YOLO:兼顾速度与精度的高效目标检测框架

本期为TechBeat人工智能社区第463期线上Talk! 北京时间12月15日(周四)20:00,阿里巴巴达摩院算法工程师——许贤哲的Talk将准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “DAMO-YOLO:兼顾速度与精度的高效目标检测框架”…

mysql索引的使用详解

一、 普通索引 ALTER TABLE 表名 ADD INDEX index_store_id索引名 ( 字段名 ) 二、唯一索引 它与前面的"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。 ALTER TABLE table_name ADD UNIQUE (column)三、 组合索引…

实用干货| 教你一键实现微信小程序转APP

自有APP不但有利于企业品牌推广,同时还更有利于收集用户相关数据,从而帮助企业及时调整发展方向和目标。而从0到1进行自有APP的开发其实是一项耗时耗力的工程,为了节约时间和成本,不少企业考虑直接将其现有微信小程序转换成商用AP…

21--Django-后端开发-Web开发模式、API接口以及Restful规范

一、Web开发模式 前后端混合开发模式 前后端分离 二、API接口 1.定义: 通过网络,规定了前后台信息交互规则的url链接,是前后台信息交互的媒介。 2.特点: 1)url:长得像返回数据的url链接 https://api.map.baidu.com/place/v2/search 2)请求方式:get、post、put、patc…

大一学生《Web前端网课作业》基于HTML+CSS自我介绍网页设计与制作

🎉精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

Usaco Trainning刷怪旅第一层第三题:Friday the Thirteenth

Usaco Training网站 关注我持续更新usaco training题目 Is Friday the 13th really an unusual event? That is, does the 13th of the month land on a Friday less often than on any other day of the week? To answer this question, write a program that will compute …

构造哈夫曼树以及求哈夫曼编码、树的带权路径长度

我们先搞清楚这几个概念 构造哈夫曼树的方法 将每种字符出现的频率先收集起来放在最上方,然后选择两个频率最小的增加到图中,并将他们的和作为他们的父节点,增加到图中,在最上方删除选择的两个节点(4和2)&a…

尚硅谷的项目---尚医通 前端模块 vue-element-admin

我的node版本是16的版本,一开始我使用的老师提供的压缩包 一直出现错误 无法 npm install 其实后来发现是node版本过高的原因,小伙伴们可以讲版本调到跟老师的版本一样 我是嫌麻烦,我就直接从github上下载了一个新的版本 网站是&#xff1…

java中的反射和Class类

动态语言 VS 静态语言: 动态语言是一类在运行时可以改变其结构的语言,例如:新的函数对象甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化,通俗点说就是在运行时,代码可以根据某些条件改变自…

【AI with ML】第 4 章 :使用公共数据集和TensorFlow 数据集

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃 🎁欢迎各位→点赞…

Web前端105天-day43-JSCORE

JSCORE03 目录 前言 一、复习 二、块级作用域 三、声明提升 四、模板字符串 五、箭头函数 六、箭头函数的this 七、数组高阶函数 7.1.every 7.2.some 7.3.filter 7.4.map 7.5.join 八、ajax 九、作业 总结 前言 JSCORE学习开始 一、复习 函数的触发方式 call: 临…

跑步用头戴式耳机好吗、五款最适合跑步用的耳机推荐

无论您是为即将举行的运动进行训练的经验丰富的运动员,还是喜欢慢跑的人,一副好的运动型耳机都可以让您的日常锻炼变得与众不同。我们测试过市面上220多种蓝牙耳机,以下是我们针对跑步和锻炼的最佳耳机选择建议: 1、南卡Runner P…

[附源码]Nodejs计算机毕业设计基于web的学生社团管理系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置: Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术: Express框架 Node.js Vue 等等组成,B/S模式 Vscode管理前后端分…

springboot 创建第一个项目

创建springboot项目的方式有很多&#xff0c;一般通过IDEA直接创建。 参考&#xff1a;创建SpringBoot项目的四种方式 - Linqylin - 博客园 代码结构&#xff1a; 代码示例&#xff1a; 创建项目的时候导入了web依赖。 pom.xml&#xff1a; <?xml version"1.0&qu…

Marvell交换机芯片SMI接口读写协议

Marvell的88E6XXX系列交换机芯片基本都提供SMI&#xff08;Serial Management Interface&#xff09;接口。 SMI接口使用2线串行通信&#xff0c;一个MDC提供时钟&#xff0c;一个MDIO为双向数据引脚。 外部CPU通过这2个引脚访问芯片内部的寄存器。 SMI接口的通信时序如图所示&…