如何设计一个安全的对外接口

前言
    安全措施大体来看主要在两个方面,一方面是如何保证数据在传输过程中的安全性,另一方面是数据已经到达服务器,服务器端如何识别数据,如何不被攻击,下面看具体有哪些措施:

1.数据加密
    我们知道数据在传输过程中是很容易被抓包的,如果直接传输比如通过http协议,那么用户传输的数据可以被任何人获取,所以必须对数据加密,常见的做法对关键字段加密比如用户密码直接通过md5加密;现在主流的做法是使用https协议,在http与tcp之间添加一层加密层(SSL层),这一层负责数据的加密和解密。
    现在主流的加密方式有对称加密和非对称加密。
    对称加密:对称密钥在加密和解密的过程中使用的密钥是相同的,常见的对称加密算法有DES,AES。优点是计算速度快,缺点是数据传送前,发送发和接收方必须商定好密钥,然后使双方都能保存好密钥,如果一方的密钥泄漏,那么加密信息也就不安全了。
    非对称加密:服务端会生成一对密钥,私钥存放在服务器,公钥可以发布给任何人使用。优点:就是比对称加密更加安全,但是加解密的速度比堆成加密慢太多了。广泛使用的是RSA算法。
    两种方式各有优缺点,而https的实现方式正好是结合了两种加密方式,整合了双方的优点,在安全和性能方面比较好。
    对称加密和非对称加密代码实现,JDK提供了相关的工具类可以直接使用,https如何配置使用相对来说复杂一些。
2.数据加签
    数据加签就是由发送者产生一段无法伪造的一段数字串,来保证数据在传输过程中不被篡改;你可能会问数据如果已经通过https加密了,还有必要进行加签吗?数据在传输过程中经过加密,理论上就算被抓包,也无法对数据进行篡改;但是我们要知道加密的部分其实只是在外网,现在很多服务在内网中都需要经过很多服务跳转,所以这里加签可以防止内网中数据被篡改。
    数据签名使用比较多的是md5算法,将需要提交的数据通过某种方式组合成一个字符串,然后通过md5生成一段加密字符串,这段字符串就是数据包的签名,可以看一个简单的例子:

1
2
str:参数1={参数1}&参数2={参数2}&……&参数n={参数n}&key={用户密钥};
MD5.encrypt(str);

    注意最后的用户密钥,服务端和客户端都有一份,这样会更加安全。
3.时间戳机制
    数据是很容易抓包的,但是经过如上的加密、加签处理,就算拿到数据也不能看到真实的数据。但是有不法者不关心真实的数据,而是直接拿到抓取的数据包进行恶意请求,这时候可以使用时间戳机制,在每次请求中加入当前时间,服务器端会拿到当前时间和消息中的时间相减,看看是否在一个固定的时间范围内比如5分钟内。这样恶意请求的数据包是无法更改里面的时间的,所以5分钟后的请求视为非法请求。
    解密后的数据,经过签名认证后,我们拿到数据包中的客户端时间戳字段,然后用服务器当前时间去减客户端时间,看结果是否在一个区间内,伪代码如下:

1
2
3
4
5
6
long interval = 5*60*1000;// 超时时间
long clientTime = request.getParameter("clientTime");
long serverTime = System.currentTimeMills();
if(serverTime - clientTime > interval) {
return new Response("超过处理时长");
}

4.AppId机制
    大部分网站基本都需要用户名和密码才能登陆,并不是谁都能来使用我的网站,这其实也是一种安全机制。对应的对外提供的接口其实也需要这么一种机制,并不是谁都可以调用,需要使用接口的用户需要在后台开通appid,提供给用户相关的密钥,在调用的接口中需要提供appid+密钥,服务端会进行相关的验证。
    生成一个唯一的appid即可,密钥使用字母、数字等特殊字符随机生成即可,生成唯一appid根据实际情况看是否需要全局统一,但是不管是否全局统一,最好让生成的id有如下属性:
        1.趋势递增:这样在保存数据库的时候,使用索引性能更好。
        2.信息安全:尽量不要连续的、容易发现规律的。
    关于全局唯一id生成的方式常见的有类snowflake方式等。
5.限流机制
    本来就是真实的用户,并且开通了appid,但是出现频繁调用接口的情况,这种情况需要给相关appid限流处理。
    常用的限流算法有令牌桶限流、漏桶算法、计数器限流。
    1.令牌桶限流:令牌桶限流的原理是系统以一定速率向桶中放入令牌,填满了就丢弃令牌,请求来时会先从桶中取出令牌,如果能取到令牌,则可以继续完成请求,否则等待或者拒绝服务。令牌桶允许一定程度突发流量,只要有令牌就可以处理,支持一次拿多个令牌。
    2.漏桶限流:漏桶算法的原理是按照固定常量速率流出请求,流入请求速率任意,当请求超过桶的容量时,新的请求等待或者拒绝服务,可以看出漏桶算法可以勉强强制限制数据的传输速度。
    3.计数器限流:计数器是一种比较简单粗暴的算法,主要用来限制总并发数,比如数据库连接池、线程池、秒杀的并发输。计数器限流只要一定时间内的总请求数超过设定的阈值则进行限流。
    具体基于以上算法如何实现,Guava提供了RateLimiter工具类基于令牌桶算法:

1
RateLimiter rateLimiter = RartLimiter.create(5);

    以上代码表示一秒钟只允许处理5个并发请求,以上方式只能在单应用的请求限流,不能进行全局限流。这时候就需要分布式限流,可以基于redis+lua来实现。
6.黑名单机制
    如果此appid进行过很多非法操作,或者说专门有一个中黑系统,经过分析之后直接将此appid列入黑名单,所有请求直接返回错误码。
    我们可以给每个用户设置一个状态比如包括:初始化状态、正常状态、中黑状态、关闭状态等等。或者我们直接通过分布式配置中心,直接保存黑名单列表,每次检查是否在列表中即可。
7.数据合法性校验
    这个可以说是每个系统都会有的处理机制,只有在数据是合法的情况下才会进行数据处理。每个系统都有自己的校验规则,当然也可能有一下常规性的规则,比如身份证验证长度和组成,电话号码长度等等。
    合法性校验包括常规性校验和业务校验。
    常规性校验:包括签名校验、必填校验、长度校验、类型校验、格式校验等。
    业务校验:根据实际业务而定,比如订单金额不能小于0等。