数据接口的安全方案
2022-07-15 09:48:04 阿炯

在日常开发中如何保证接口数据的安全性呢?接口数据安全的保证过程主要体现在这几个方面:一个就是数据传输过程中的安全,还有就是数据到达服务端,如何识别数据,最后一点就是数据存储的安全性。这里跟大家聊聊保证接口数据安全的10个方案。


1.数据加密,防止报文明文传输。

数据在网络传输过程中很容易被抓包。如果使用的是http协议,因为其是明文传输的,用户的数据就很容易被别人获取。所以需要对数据加密。

1.1 数据如何加密呢?
常见的实现方式,就是对关键字段加密。比如需要登录的接口可以对密码加密。一般用什么加密算法呢?简单点可以使用对称加密算法(如AES)来加解密,或者哈希算法处理(如MD5)。

什么是对称加密:加密和解密使用相同密钥的加密算法。


非对称加密:非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。

更安全的做法,就是用非对称加密算法(如RSA或者SM2),公钥加密,私钥解密。


如果想对所有字段都加密的话,一般都推荐使用https协议。https其实就是在http和tcp之间添加一层加密层SSL或TLS。

1.2 是否还记得https的原理呢,面试也经常问的,如下:


客户端发起Https请求,连接到服务器的443端口。
服务器必须要有一套数字证书(证书内容有公钥、证书颁发机构、失效日期等)。
服务器将自己的数字证书发送给客户端(公钥在证书里面,私钥由服务器持有)。
客户端收到数字证书之后,会验证证书的合法性。如果证书验证通过,就会生成一个随机的对称密钥,用证书的公钥加密。
客户端将公钥加密后的密钥发送到服务器。
服务器接收到客户端发来的密文密钥之后,用自己之前保留的私钥对其进行非对称解密,解密之后就得到客户端的密钥,然后用客户端密钥对返回数据进行对称加密,如此传输的数据都是密文了。
服务器将加密后的密文返回到客户端。
客户端收到后,用自己的密钥对其进行对称解密,得到服务器返回的数据。

日常业务的数据传输加密这块的话,用https就可以了,如果安全性要求较高的,比如登陆注册这些,需要传输密码的,密码就可以使用RSA等非对称加密算法,对密码加密。如果业务的安全性要求很高,可以模拟https这个流程,对报文,再做一次加解密。

2. 数据加签验签

数据报文加签验签,是保证数据传输安全的常用手段,它可以保证数据在传输过程中不被篡改。如企业转账系统就用了加签验签。

2.1 什么是加签验签呢?

数据加签:用Hash算法(如MD5,或者SHA-256)把原始请求参数生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名sign(这个过程就是加签)。通常来说呢,请求方会把数字签名和报文原文一并发送给接收方。


验签:接收方拿到原始报文和数字签名(sign)后,用同一个Hash算法(比如都用MD5)从报文中生成摘要A。再用对方提供的公钥对数字签名进行解密,得到摘要B,对比A和B是否相同,就可以得知报文有没有被篡改过。


其实加签,个人的理解的话,就是把请求参数,按照一定规则,利用hash算法+加密算法生成一个唯一标签sign。验签的话,就是把请求参数按照相同的规则处理,再用相同的hash算法,和对应的密钥解密处理,以对比这个签名是否一致。

再举个例子,将所有非空参数(包含一个包AccessKey,唯一的开发者标识)按照升序,然后再拼接个SecretKey(这个仅作本地加密使用,不参与网络传输,它只是用作签名里面的),得到一个stringSignTemp的值,最后用MD5运算,得到sign。

服务端收到报文后,会校验,只有拥有合法的身份AccessKey和签名Sign正确才放行。这样就解决了身份验证和参数篡改问题,如果请求参数被劫持,由于劫持者获取不到SecretKey(仅作本地加密使用,不参与网络传输),就无法伪造合法的请求了。

2.2 有了https等加密数据,为什么还需要加签验签

有人可能有疑问,加签验签主要是防止数据在传输过程中被篡改,那如果都用了https下协议加密数据了,为什么还会被篡改呢,为什么还需要加签验签呢?

数据在传输过程中被加密了,理论上,即使被抓包,数据也不会被篡改。但是https不是绝对安全的。还有一个点:https加密的部分只是在外网,然后有很多服务是内网相互跳转的,加签也可以在这里保证不被中间人篡改,所以一般转账类安全性要求高的接口开发,都需要加签验签。另可参考《密钥散列消息认证码-HMAC》。

3.token授权认证机制

日常开发中的网站或者APP,都是需要用户登录的。那么如果是非登录接口,是如何确保安全,如何确认用户身份的呢?可以使用token授权机制。

3.1 token的授权认证方案

token的授权认证方案:用户在客户端输入用户名和密码,点击登录后,服务器会校验密码成功,会给客户端返回一个唯一值token,并将token以键值对的形式存放在缓存(一般是Redis)中。后续客户端对需要授权模块的所有操作都要带上这个token,服务器端接收到请求后,先进行token验证,如果token存在,才表明是合法请求。Token登录授权流程图如下:


用户输入用户名和密码,发起登录请求;
服务端校验密码,如果校验通过,生成一个全局唯一的token;
将token存储在redis中,其中key是token,value是userId或者是用户信息,设置一个过期时间;
把这个token返回给客户端;
用户发起其他业务请求时,需要带上这个token;
后台服务会统一拦截接口请求,进行token有效性校验,并从中获取用户信息,供后续业务逻辑使用。如果token不存在,说明请求无效。

3.2 如何保证token的安全,token被劫持呢?

如何保证token的安全呢?
如果拿到token,是不是就可以调用服务器端的任何接口?可以从这几个方面出发考虑:
token设置合理的有效期;
使用https协议;
token可以再次加密;
如果访问的是敏感信息,单纯加token是不够的,通常会再配置白名单。

说到token,大家很可能会想起JWT,即(JSON Web Token),其实它也是token的一种。

4. 时间戳timestamp超时机制

数据是很容易抓包的,假设使用了https和加签,即使中间人抓到了数据报文,它也看不到真实数据。但是有些不法者,他根本不关心真实的数据,而是直接拿到抓取的数据包,进行恶意请求(比如DOS攻击)。

可以引入时间戳超时机制,来保证接口安全。就是:用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后,解密,验签通过后,与服务器当前时间进行比对,如果时间差大于一定时间 (比如3分钟),则认为该请求无效。

5.timestamp+nonce方案防止重放攻击

时间戳超时机制也是有漏洞的,如果是在时间差内,黑客进行的重放攻击,那就不好使了。可以使用timestamp+nonce方案。

nonce指唯一的随机字符串,用来标识每个被签名的请求。可以将每次请求的nonce参数存储到一个“set集合”中,或者可以json格式存储到数据库或缓存中。每次处理HTTP请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。然而对服务器来说,永久保存nonce的代价是非常大的。可以结合timestamp来优化。因为timstamp参数对于超过3min的请求,都认为非法请求,所以我们只需要存储3min的nonce参数的“集合”即可。

6. 限流机制

如果用户本来就是就是真实用户,恶意频繁调用接口来搞垮你的系统呢?这种情况就需要接入限流了。常用的限流算法有令牌桶和漏桶算法。

可以使用Guava的RateLimiter单机版限流,也可以使用Redis分布式限流,还可以使用开源组件sentinel限流,比如一分钟可以接受多少次请求。

7. 黑名单机制

如果发现了真实用户恶意请求,可以搞个黑名单机制把该用户拉黑。一般情况,会有些竞争对手,或者不坏好意的用户。为了保证安全,一般的业务系统需要有个黑名单机制。对于黑名单发起的请求,直接返回错误码好了。

8.白名单机制

有了黑名单机制,也可以搞个白名单机制。以前负责的企业转账系统,如果有外面的商户要接入系统时,是需要提前申请网络白名单的。那时候运维会申请个IP网络白名单,只有白名单里面的请求,才可以访问转账系统。

9.数据脱敏掩码

对于密码,或者手机号、身份证这些敏感信息,一般都需要脱敏掩码再展示的,如果是密码,还需要加密再保存到数据库。

对于手机号、身份证信息这些,日常开发中,在日志排查时,看到的都应该是掩码的。目的就是尽量不泄漏这些用户信息,虽然能看日志的只是开发和运维,但是还是需要防一下,做掩码处理。

对于密码保存到数据库,肯定不能直接明文保存。最简单的也需要MD5处理一下再保存,Spring Security中的 BCryptPasswordEncoder也可以,它的底层是采用SHA-256 +随机盐+密钥对密码进行加密,而SHA和MD系列是一样的,都是hash摘要类的算法。

10. 数据参数一些合法性校验。

接口数据的安全性保证,还需要系统有数据合法性校验,简单来说就是参数校验,比如身份证长度,手机号长度,是否是数字等等。


保证接口的安全的技巧再盘点

1、参数校验
保证接口安全的第一步,也是最重要的一步,需要对接口的请求参数做校验。如果把接口请求参数的校验做好了,真的可以拦截大部分的无效请求。

可以按如下步骤做校验:
校验参数是否为空,有些接口中可能会包含多个参数,有些参数允许为空,有些参数不允许为空,我们需要对这些参数做校验,防止接口底层出现异常。
校验参数类型,比如:age是int类型的,用户传入了一个字符串:"123abc",这种情况参数不合法,需要被拦截。
校验参数的长度,特别是对于新增或者修改数据接口,必须要做参数长度的校验,否则超长了数据库会报异常。比如:数据库username字段长度是30,新用户注册时,输入了超过30个字符的名称,需要提示用户名称超长了。虽说前端会校验字段长度,但接口对参数长度的校验也必不可少。
校验枚举值,有些接口参数是枚举,比如:status,数据库中设计的该字段只有1、2、3三个值。如果用户传入了4,则需要提示用户参数错误。
校验数据范围,对于有些金额参数,需要校验数据范围,比如:单笔交易的money必须大于0,小于10000。

可以自写代码,对每个接口的请求参数一一做校验。也可以使用一些第三方的校验框架。

2、统一封装返回值
可能有人认为,对接口返回值统一封装是为了让代码更规范,其实也是处于安全方面的考虑。

假如有这样一种场景:写的某个接口底层的sql,在某种条件下有语法问题。某个用户请求接口之后,在访问数据库时,直接报了sql语法错误,将数据库名、表名、字段名、相关sql语句都打印出来了。

如果接口将这些异常信息直接返回给外网的用户,有些黑客拿着这些信息,将参数做一些调整,拼接一些注入sql,可以对数据库发起攻击。因此,非常有必要对接口的返回值做统一的封装。例如下面这样:
{
    "code":0,
    "message":null,
    "data":[{"id":123,"name":"abc"}]
}

该json返回值中定义了三个字段:
code:表示响应码,0-成功,1-参数为空,2-参数错误,3-签名错误 4-请求超时 5-服务器内部错误等。
message:表示提示信息,如果请求成功,则返回空。如果请求失败,则返回我们专门在代码中处理过,让用户能看懂的错误信息。
data:表示具体的数据,返回的是一个json字段。

对返回值这样封装之后,即使在接口的底层出现了数据库的异常,也不会直接提示用户,给用户提示的是服务器内部错误。

对返回值统一封装的工作,没有必要在业务代码中做,完全可以在放到API网关。所有的API接口都必须经过API网关,API网关捕获该业务异常,然后转换成统一的异常结构返回,这样能统一返回值结构。

3、做转义
在用户自定义输入框,用户可以输入任意内容。

有些地方需要用html的格式显示用户输入的内容,比如文章详情页或者合同详情页,用户可以自定义文案和样式。这些地方如果我们不做处理,可能会遭受XSS(Cross Site Scripting)攻击,也就是跨站脚本攻击。

攻击者可以在输入的内容中,增加脚本,比如:<script>alert("反射型 XSS 攻击")</script>,这样在访问合同详情页时,会弹出一个不需要的窗口,攻击者甚至可以引导用户访问一些恶意的链接。

由此需要对用户输入内容中的一些特殊标签做转义:
&    &amp;
<    &lt;
>    &gt;
"    &quot;
'    &#x27;
/    &#x2F;

可以自定义一个转义注解,打上该注解的字段,表示需要转义。有专门的AOP拦截器,将用户的原始内容,转换成转义后的内容。保存到数据库中是转义之后的内容。除此之外,为了防止SQL注入的情况,也需要将用户输入的参数做SQL语句方面的转义。

4、做权限控制
需要对接口做权限控制,主要包含了下面3种情况。

4.1 校验是否登录
对于用些公共数据,比如:外部分类,所有人都能够看到,不用登录也能看到。

对于这种接口,则不用校验登录。而对于有些查看内部分类的接口,需要用户登录之后,才能访问。这种情况就需要校验登录了。

可以从当前用户上下文中获取用户信息,校验用户是否登录。如果用户登录了,当前用户上下文中该用户的信息不为空。否则,如果用户没登录,则当前用户上下文中该用户的信息为空。

4.2 接口功能权限控制
对于有些重要的接口,比如订单审核接口,只有拥有订单审核权限的运营账号,才有权限访问该接口。需要对该接口做功能权限控制。可以自定义一个权限注解,在注解上可以添加权限点。在网关层有个拦截器,会根据当前请求的用户的权限,去跟请求的接口的权限做匹配,只有匹配上次允许访问该接口。

4.3 接口数据权限控制
对于有些订单查询接口,普通运营只能查看普通用户的数据。而运营经理可以查看普通用户和vip用户的数据。这种情况需要对该订单查询接口做数据权限控制。不同的角色,能够查看的数据范围不同。可以在查询数据时,在sql语句中动态拼接过滤数据权限的sql。

5、加验证码
对于一些非常重要的接口,在做接口设计的时候,要考虑恶意用户刷接口的情况。

图形验证码比较简单,很容易被一些暴力破解工具破解。因此要给图形验证码增加难道,增加一些干扰项,增加暴力破解工具的难道。但有个问题是:如果图形验证码太复杂了,会对正常用户使用造成一点的困扰,增加了用户注册的成本,让用户注册功能的效果会大打折扣。

仅靠图形验证码,防止用户注册接口被刷,难道太大了。后来又出现了一种移动滑块形式的图形验证方式,安全性更高。此外使用验证码比较多的地方是发手机短信的功能。发手机短信的功能,一般是购买的云服务厂商的短信服务,按次收费,比如:发一条短信0.1元。如果发送短信的接口,不做限制,被用户恶意调用,可能会产生非常昂贵的费用。

6、限流
上一节中提到的发送短信接口,只校验验证码还不够,还需要对用户请求做限流。从页面上的验证码,只能限制当前页面的不能重复发短信,但如果用户刷新了页面,也可以重新发短信。

因此非常有必要在服务端,即:发送短信接口做限制。可以增加一张短信发送表。该表包含:id、短信类型、短信内容、手机号、发送时间等字段。


有用户发送短信请求过来时:
先查询该手机号最近一次发送短信的记录
如果没有发送过,则发送短信。
如果该手机号已经发送过短信,但发送时间跟当前时间比超过了60秒,则重新发送一条新的短信。
如果发送时间跟当前时间比没超过60秒,则直接提示用户操作太频繁,请稍后重试。
这样就能非常有效的防止恶意用户刷短信的行为。

但还是有漏洞。比如:用户知道在60秒以内,是没法重复发短信的。他有个程序,刚好每隔60秒发一条短信。

这样1个手机号在一天内可以发:60*24 = 1440 条短信。如果他有100个手机号,那么一天也可以刷你很多条短信。因此还需要限制每天同一个手机号可以发的短信次数。

其实可以用redis来做。用户发短信之后,在redis中保存一条记录,key是手机号,value是发短信的次数,过期时间是24小时。

这样在发送短信之前,要先查询一下,当天发送短信的次数是否超过10次(假设同一个手机号一天最多允许发10条短信)。

如果超过10次,则直接提示用户操作太频繁,请稍后重试。如果没超过10次,则发送短信,并且把redis中该手机号对应的value值加1。

短信发送接口完整的校验流程如下:



7、加ip白名单
对于有些非常重要的基础性的接口,比如:会员系统的开通会员接口,业务系统可能会调用该接口开通会员。会员系统为了安全性考虑,在设计开通会员接口的时候,可能会加一个ip白名单,对非法的服务器请求进行拦截且可以动态生效。

如果后期ip数量多了的话,可以直接保存到数据库。只有ip在白名单中的那些服务器,才允许调用开通会员接口。这样即使开通会员接口地址和请求参数被泄露了,调用者的ip不在白名单上,请求开通会员接口会直接失败。

除非调用者登录到了某一个白名单ip的对应的服务器,这种情况极少,因为一般运维会设置对访问器访问的防火墙。当然如果用了Fegin这种走内部域名的方式访问接口,可以不用设置ip白名单,内部域名只有在公司的内部服务器之间访问,外面的用户根本访问不了。

但对于一些第三方平台的接口,更多的是通过设置ip白名单的方式保证接口的安全性。

8、校验敏感词
对于某些用户可以自定义内容的接口,还需要对用户输入的内容做敏感词校验。可以做一个审核功能,对用户创建的商品信息做人工审核,如果商品数量太多,这样会浪费很多人力。有个比较好的做法是:对用户自定义的内容,做敏感词校验。

可以调用第三方平台的接口,也可以自己实现一个敏感词校验接口。可以在GitHub上下载一个开源的敏感词库,将那些敏感词导入到数据库中。然后使用hanlp分词器对用户输入的内容,进行分词。对分好的词,去匹配敏感词库中的那些敏感词。如果匹配上了,则说明是敏感词,则验证不通过。如果没有匹配上,则说明非敏感词,则验证通过。

在调用业务接口之前,先触发拦截器,校验打了敏感词校验注解的那些字段,将里面包含的内容作为入参传入敏感词校验接口做校验。当然有时候hanlp分词器会把句子分错词,还需要添加一个敏感词的白名单,白名单中的词不是敏感词。

9、使用https协议
以前很多接口使用的是HTTP(HyperText Transport Protocol,即超文本传输协议)协议,它用于传输客户端和服务器端的数据。虽说HTTP使用很简单也很方便,但却存在以下3个致命问题:
1.使用明文通讯,内容容易被窃听。
2.不验证通讯方的真实身份,容易遭到伪装。
3.无法证明报文的完整性,报文很容易被篡改。

为了解决HTTP协议的这些问题,出现了HTTPS协议。HTTPS协议是在HTTP协议的基础上,添加了加密机制:
SSL:它是Secure Socket Layer的缩写, 表示安全套接层。
TLS:它是Transport Layer Security的缩写,表示传输层安全。

HTTPS = HTTP + 加密 + 认证 + 完整性保护。

为了安全性考虑,接口如果能使用HTTPS协议,尽量少使用HTTP协议。一些大厂的网站提供的接口都是使用的HTTPS协议。不过需要注意的地方是:HTTPS协议需要申请证书,有些额外的费用成本。

10、数据加密
有些信息是用户的核心信息,比如:手机号、邮箱、密码、身份证、银行卡号等,不能别泄露出去。在保存到数据库时,我们要将这些字段,做加密处理。

后面即使这些数据被泄露了,获得数据的人,由于没有密钥,没办法解密。这种情况可以使用对称加密的方式,因为后面系统的有些业务场景,需要把加密的数据解密出来。

为了安全性考虑,需要设置一个用于加密的密钥,这个密钥可以稍微复杂一点,包含一些数字、字母和特殊字符。同样可以通过自定义注解的方式,给需要加密的字段添加该注解,在拦截器中实现加解密的功能。

对于查询操作,需要将加了该注解的字段的数据做解密处理。对于写入操作,要将加了该注解的字段的数据做加密处理。有些页面显示的地方,手机号一般不会显示完整的手机号,中间有一部分用*代替,比如:182***3457。这种情况需要做后端脱敏处理(不能在前端做)。

11、风险控制
有些特殊的接口,比如用户登录接口,需要对该接口做风险控制,尽可能减小被盗号的风险。

用户登录失败之后,需要有地方,比如:Redis记录用户登录失败的次数。如果用户第一次输入账号密码登录时,出现的是一个稍微简单的验证码。

如果用户把账号或密码连续输错3次之后,出现了更复杂的验证码。或者改成使用手机短信验证。

如果用户在一天之内,把账号或密码连续输错10次,则直接锁定该账号。

这样处理是为了防止有人用一些软件,暴力破解账号和密码。

在用户登录成功之后,需要有一张表记录用户的ip、所在城市和登录的设备id。

如果你的账号被盗了。

在盗号者在页面输入账号密码登录,会调用登录接口,此时登录接口中可以根据用户的ip和设备id,做一些风险控制。

接口判断如果用户当前登录的ip、所在城市和设备ip,跟上一次登录成功时记录的相差非常大。

比如:1小时之前,用的ip是100.101.101.101,城市是北京,设备id是1001,而1小时之后,用的ip是200.202.202.101,城市是广州,设备id是2002。

这种情况用户的账号极有可能被盗了。

登录接口做安全性升级,需要校验用户手机验证码才能登录成功。

由于盗号者只有你的账号和密码,没有手机验证码,所以即使被盗号了,也没办法登录成功。