[总结]如何设计一个安全的对外API接口?
最近有个项目需要对外提供一个接口,提供公网域名进行访问,而且接口和交易订单有关,所以安全性很重要;这里整理了一下常用的一些安全措施以及具体如何去实现。
1. 安全措施
个人觉得安全措施大体来看主要在两个方面,一方面就是如何保证数据在传输过程中的安全性,另一个方面是数据已经到达服务器端,服务器端如何识别数据,如何不被攻击;下面具体看看都有哪些安全措施。
1-1. 1. 数据加密
我们知道数据在传输过程中是很容易被抓包的,如果直接传输比如通过 http 协议,那么用户传输的数据可以被任何人获取;所以必须对数据加密,常见的做法对关键字段加密比如用户密码直接通过 md5 加密;现在主流的做法是使用 https 协议,在 http 和 tcp 之间添加一层加密层 (SSL 层),这一层负责数据的加密和解密;
1-2. 2. 数据加签
数据加签就是由发送者产生一段无法伪造的一段数字串,来保证数据在传输过程中不被篡改;你可能会问数据如果已经通过 https 加密了,还有必要进行加签吗?数据在传输过程中经过加密,理论上就算被抓包,也无法对数据进行篡改;但是我们要知道加密的部分其实只是在外网,现在很多服务在内网中都需要经过很多服务跳转,所以这里的加签可以防止内网中数据被篡改;
1-3. 3. 时间戳机制
数据是很容易被抓包的,但是经过如上的加密,加签处理,就算拿到数据也不能看到真实的数据;但是有不法者不关心真实的数据,而是直接拿到抓取的数据包进行恶意请求;这时候可以使用时间戳机制,在每次请求中加入当前的时间,服务器端会拿到当前时间和消息中的时间相减,看看是否在一个固定的时间范围内比如 5 分钟内;这样恶意请求的数据包是无法更改里面时间的,所以 5 分钟后就视为非法请求了;
1-4. 4. AppId 机制
大部分网站基本都需要用户名和密码才能登录,并不是谁来能使用我的网站,这其实也是一种安全机制;对应的对外提供的接口其实也需要这么一种机制,并不是谁都可以调用,需要使用接口的用户需要在后台开通 appid,提供给用户相关的密钥;在调用的接口中需要提供 appid + 密钥,服务器端会进行相关的验证;
1-5. 5. 限流机制
本来就是真实的用户,并且开通了 appid,但是出现频繁调用接口的情况;这种情况需要给相关 appid 限流处理,常用的限流算法有令牌桶和漏桶算法;
1-6. 6. 黑名单机制
如果此 appid 进行过很多非法操作,或者说专门有一个中黑系统,经过分析之后直接将此 appid 列入黑名单,所有请求直接返回错误码;
1-7. 7. 数据合法性校验
这个可以说是每个系统都会有的处理机制,只有在数据是合法的情况下才会进行数据处理;每个系统都有自己的验证规则,当然也可能有一些常规性的规则,比如身份证长度和组成,电话号码长度和组成等等;
2. 如何实现
以上大体介绍了一下常用的一些接口安全措施,当然可能还有其他我不知道的方式,希望大家补充,下面看看以上这些方法措施,具体如何实现;
2-1. 1. 数据加密
现在主流的加密方式有对称加密和非对称加密;
对称加密:对称密钥在加密和解密的过程中使用的密钥是相同的,常见的对称加密算法有 DES,AES;优点是计算速度快,缺点是在数据传送前,发送方和接收方必须商定好秘钥,然后使双方都能保存好秘钥,如果一方的秘钥被泄露,那么加密信息也就不安全了;
非对称加密:服务端会生成一对密钥,私钥存放在服务器端,公钥可以发布给任何人使用;优点就是比起对称加密更加安全,但是加解密的速度比对称加密慢太多了;广泛使用的是 RSA 算法;
两种方式各有优缺点,而 https 的实现方式正好是结合了两种加密方式,整合了双方的优点,在安全和性能方面都比较好;
对称加密和非对称加密代码实现,jdk 提供了相关的工具类可以直接使用,此处不过多介绍;
2-2. 2. 数据加签
为开发者分配AccessKey(开发者标识,确保唯一)和SecretKey(用于接口加密,确保不易被穷举,生成算法不易被猜测)
数据签名使用比较多的是 md5 算法,将需要提交的数据按照某种顺讯拼接成字符串stringA,然后通过 md5 生成一段加密字符串,这段加密字符串就是数据包的签名,可以看一个简单的例子。
- 请求参数名的字母升序排列非空请求参数(包含AccessKey),使用URL键值对的格式(即key1=value1&key2=value2…)
- 在stringA最后拼接上Secretkey得到字符串stringSignTemp;
-
对stringSignTemp进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值。
1 2 3 4 5 6 |
str:参数1={参数1}&参数2={参数2}&......&参数n={参数n}&key={用户密钥SecretKey}; sign: MD5.encrypt(str); key1=value1&key2=value2...&AccessKey=${AccessKey}&sign=${sign} |
注意最后的用户密钥SecretKey,客户端和服务端都有一份,这样会更加安全;
请求携带参数AccessKey和Sign,只有拥有合法的身份AccessKey和正确的签名Sign才能放行。这样就解决了身份验证和参数篡改问题,即使请求参数被劫持,由于获取不到SecretKey(仅作本地加密使用,不参与网络传输),无法伪造合法的请求。
2-3. 3. 时间戳机制
解密后的数据,经过签名认证后,我们拿到数据包中的客户端时间戳字段,然后用服务器当前时间去减客户端时间,看结果是否在一个区间内,伪代码如下:
1 2 3 4 5 6 7 |
long interval=5*60*1000;//超时时间 long clientTime=request.getparameter("clientTime"); long serverTime=System.currentTimeMillis(); if(serverTime-clientTime>interval){ return new Response("超过处理时长") } |
2-3-1. timestamp+nonce方案
2-3-1-1. 实现
http://api.test.com/test?name=hello&home=world&work=java
2-3-1-2. 客户端
-
生成当前时间戳timestamp=now和唯一随机字符串nonce=random -
按照请求参数名的字母升序排列非空请求参数(包含AccessKey) stringA="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random";
-
拼接密钥SecretKey stringSignTemp="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random&SecretKey=secret";
-
MD5并转换为大写 sign=MD5(stringSignTemp).toUpperCase();
-
最终请求 http://api.test.com/test?name=hello&home=world&work=java×tamp=now&nonce=nonce&sign=sign;
2-4. 4. AppId 机制
Token&AppKey(APP)
在APP开放API接口的设计中,由于大多数接口涉及到用户的个人信息以及产品的敏感数据,所以要对这些接口进行身份验证,为了安全起见让用户暴露的明文密码次数越少越好,然而客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上身份验证信息。
生成一个唯一的 AppId 即可,密钥使用字母、数字等特殊字符随机生成即可;生成唯一 AppId 根据实际情况看是否需要全局唯一;但是不管是否全局唯一最好让生成的 Id 有如下属性:趋势递增:这样在保存数据库的时候,使用索引性能更好;信息安全:尽量不要连续的,容易发现规律;关于全局唯一 Id 生成的方式常见的有类 snowflake 方式等;
Token身份验证
-
用户登录向服务器提供认证信息(如账号和密码),服务器验证成功后返回Token给客户端; -
客户端将Token保存在本地,后续发起请求时,携带此Token; -
服务器检查Token的有效性,有效则放行,无效(Token错误或过期)则拒绝。 -
安全隐患:Token被劫持,伪造请求和篡改参数。
Token+AppKey签名验证
2-5. 5. 限流机制
API网关能够有效地将客户端接口与后端的API集合相分离,提供集中式的资源,以实现API服务的一致性、可用性和可扩展性。同时,网关也能够充当反向代理,协调所有API调用所需的资源,并在身份验证后,返回适当的结果。在实践中,我们可以通过API管理平台,来处理各种遥测(Telemetry)、速率限制、以及用户认证等标准化功能,以维护内部服务之间的安全性。
常用的限流算法包括:令牌桶限流, 漏桶限流, 计数器限流;
1. 令牌桶限流 令牌桶算法的原理是系统以一定速率向桶中放入令牌,填满了就丢弃令牌;请求来时会先从桶中取出令牌,如果能取到令牌,则可以继续完成请求,否则等待或者拒绝服务;令牌桶允许一定程度突发流量,只要有令牌就可以处理,支持一次拿多个令牌;
2. 漏桶限流 漏桶算法的原理是按照固定常量速率流出请求,流入请求速率任意,当请求数超过桶的容量时,新的请求等待或者拒绝服务;可以看出漏桶算法可以强制限制数据的传输速度;
3. 计数器限流 计数器是一种比较简单粗暴的算法,主要用来限制总并发数,比如数据库连接池、线程池、秒杀的并发数;计数器限流只要一定时间内的总请求数超过设定的阀值则进行限流;具体基于以上算法如何实现,Guava 提供了 RateLimiter 工具类基于基于令牌桶算法:
1 2 |
RateLimiter rateLimiter = RateLimiter.create(5); |
以上代码表示一秒钟只允许处理五个并发请求,以上方式只能用在单应用的请求限流,不能进行全局限流;这个时候就需要分布式限流,可以基于 redis+lua 来实现;
2-6. 6. 黑名单机制
如何为什么中黑我们这边不讨论,我们可以给每个用户设置一个状态比如包括:初始化状态,正常状态,中黑状态,关闭状态等等;或者我们直接通过分布式配置中心,直接保存黑名单列表,每次检查是否在列表中即可;
2-7. 7. 数据合法性校验
合法性校验包括:常规性校验以及业务校验;常规性校验:包括签名校验,必填校验,长度校验,类型校验,格式校验等;业务校验:根据实际业务而定,比如订单金额不能小于 0 等;
3. 总结
本文大致列举了几种常见的安全措施机制包括:数据加密、数据加签、时间戳机制、AppId 机制、限流机制、黑名单机制以及数据合法性校验;当然肯定有其他方式,欢迎补充。
4. 一、token 简介
-
API Token(接口令牌): 用于访问不需要用户登录的接口,如登录、注册、一些基本数据的获取等。获取接口令牌需要拿appId、timestamp和sign来换,sign=加密(timestamp+key) -
USER Token(用户令牌): 用于访问需要用户登录之后的接口,如:获取我的基本信息、保存、修改、删除等操作。获取用户令牌需要拿用户名和密码来换
5. 二、timestamp 简介
5-1. DoS
-
Pingflood: 该攻击在短时间内向目的主机发送大量ping包,造成网络堵塞或主机资源耗尽。 -
Synflood: 该攻击以多个随机的源主机地址向目的主机发送SYN包,而在收到目的主机的SYN ACK后并不回应,这样,目的主机就为这些源主机建立了大量的连接队列,而且由于没有收到ACK一直维护着这些队列,造成了资源的大量消耗而不能向正常请求提供服务。 -
Smurf:该攻击向一个子网的广播地址发一个带有特定请求(如ICMP回应请求)的包,并且将源地址伪装成想要攻击的主机地址。子网上所有主机都回应广播包请求而向被攻击主机发包,使该主机受到攻击。 -
Land-based:攻击者将一个包的源地址和目的地址都设置为目标主机的地址,然后将该包通过IP欺骗的方式发送给被攻击主机,这种包可以造成被攻击主机因试图与自己建立连接而陷入死循环,从而很大程度地降低了系统性能。 -
Ping of Death:根据TCP/IP的规范,一个包的长度最大为65536字节。尽管一个包的长度不能超过65536字节,但是一个包分成的多个片段的叠加却能做到。当一个主机收到了长度大于65536字节的包时,就是受到了Ping of Death攻击,该攻击会造成主机的宕机。 -
Teardrop:IP数据包在网络传递时,数据包可以分成更小的片段。攻击者可以通过发送两段(或者更多)数据包来实现TearDrop攻击。第一个包的偏移量为0,长度为N,第二个包的偏移量小于N。为了合并这些数据段,TCP/IP堆栈会分配超乎寻常的巨大资源,从而造成系统资源的缺乏甚至机器的重新启动。 -
PingSweep:使用ICMP Echo轮询多个主机。
6. 三、sign 简介
7. 四、防止重复提交
8. 五、使用流程
六、示例代码