API接口安全性安全
# 不同场景下接口的暴露方式
网站形式(WEB、H5)
- chrome浏览器
- burp suite抓包工具
- Wireshark抓包工具
APP
- APP反编译工具
- APP抓包演示Fiddler
微信小程序
- 微信小程序抓包
- 微信小程序反编译
# 常见危害
- 获取敏感数据
- 篡改数据
- 重复提交
- 恶意调用
- 爬虫爬取
- 黑路由器流量包抓取敏感信息
# 常见攻击方式
- 抓包工具接口重放重复提交:如下单、退款
- 抓包工具拦截调用篡改数据:如通过修改用户id获取不同用户信息
- 黑路由器抓取敏感信息
https可以提高接口安全性,但并不能完全保护数据安全,如果用户下载了伪造的证书依旧是能对https加密后的数据进行解密曝光的。
# 不同场景下的安全策略
- 预防路由器抓包
- HTTPS:https可以提高接口安全性,但并不能完全保护数据安全,如果用户下载了伪造的证书依旧是能对https加密后的数据进行解密曝光的。
- 数据加密
- 预防请求重放
- 加密、验签、时间戳
- 幂等性
- 预防请求篡改
- 加密、验签
- 预防未授权方调用
- JWT认证
- 预防恶意爬取
- 网关限流、黑名单...
# HTTPS协议原理

- 客户端请求服务器,携带自身支持的加密算法和哈希算法
- 服务器接收确认后统一与该客户端交互使用的加密及哈希算法
- 服务器发放数字证书(含公钥)给客户端
- 客户端与本地的根证书进行验证,合法则随机生成秘钥R
- 客户端用公钥加密秘钥R发送给服务器
- 服务器通过私钥解密获取到秘钥R
-- 到这里客户端和服务器就完成了加密方式的统一,在之后都会通过R加密传输数据进行交互 - 服务器使用R加密响应数据返回给客户端
- 客户端使用R解密响应数据进行逻辑处理
# JWT身份验证

JWT身份验证流程:
- 用户发送登录请求至认证服务
- 认证用户合法后会发放JWT令牌给用户
- 用户带上令牌请求其他服务
- 服务验证令牌,合法则返回数据给用户
# JWT组成
jwt是由三部分组成,分别是header payload signature这三部分
Header:
jwt的头部承载着两部分信息。一是声明类型,这里是jwt,二是声明加密的算法,通常就使用HMAC和SHA256
{
"typ": "jwt",
"alg": "HS256"
}
2
3
4
对它的头部进行base64加密。就构成了jwt的第一部分
ewogICAgICd0eXAnOiAnand0JywKICAgICAnYWxnJzogJ0hTMjU2Jwp9
Payload:
这一部分就是存放有效信息的地方。如用户id
参考:
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
2
3
4
5
6
7
示例:
{
"userId": "用户id",
"name": "张三"
}
2
3
4
※理论上这个部分可以存放任何的信息,所以不推荐存放敏感信息
同样对payload这一部分进行base64加密。
ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAiWGlMaXR0ZXIiLAogICJhZG1pbiI6IHRydWUKfQ==
signature:
这一部分是对前两部分的签名,防止数据被纂改。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
2
3
4
简单说就是用一个算法,如头部的描述的算法,对base64加密的前两部分用"."拼起来,再用一个所有服务约定好的secret作为key进行加密。
默认采用的签名算法是HMAC SHA256.产生签名后,这三部分就用点(.)将这三部分连接起来,就组成了完整的jwt。
最后再解释一下application server如何认证用户发来的JWT是否合法:
首先application server 和认证必须要有个约定,例如双方同时知道加密用的secret(这里假设用的就是简单的对称加密算法),那么在applicaition收到这个JWT是,就可以利用JWT前两段(别忘了JWT是个三段的拼成的字符串哦)数据作为输入,用同一套hash算法和同一个secret自己计算一个签名值,然后把计算出来的签名值和收到的JWT第三段比较,如果相同则认证通过,如果不相同,则认证不通过。就这么简单,当然,上面是假设了这个hash算法是对称加密算法,其实如果用非对称加密算法也是可以的,比方说我就用非对称的算法,那么对应的key就是一对,而非一个,那么一对公钥+私钥可以这样分配:私钥由认证服务保存,公钥由application server保存,application server验证的时候,用公钥解密收到的signature,这样就得到了header和payload的拼接值,用这个拼接值跟前两段比较,相同就验证通过。
※需要注意的是:非对称性加密算法,如RSA,公钥可以解密私钥和公钥,但是私钥只能解密公钥
用私钥加密公钥解密的好处就在于安全性会更高,如果公钥加密私钥解密,也就是认证服务用公钥加密,而认证服务往往是最容易被盯上的,由于没有身份认证,大多的请求都会到达认证服务,就有公钥泄漏的风险,若黑客拿到了公钥,那么就可以伪造令牌去请求其他application server,而application server存放的私钥刚好就能解密公钥,认证就能通过。但反过来,如果黑客拿到的是私钥,用私钥加密,而application server存放的私钥无法解密,就能够预防伪造令牌。
# JWT如何实现logout
一种是设置expire time,这种可以称为"被动logout",即超过了一定时间自动失效,但是如果等不及,就是想主动logout怎么办呢?由于jwt的这种"无状态"的天然属性,理论上是没有办法直接主动logout的,但是有一个间接的方法,就是可以在服务器后台存一个"黑名单",这个黑名单专门用来存尚未过期但又想主动标明失效的的token,然后登录状态检查的时候多做一步黑名单检查即可。
# JWT如何实现续签
一般验签的时候是通过token查询redis,如果能拿到userId或其他相关信息时,则说明token未过期,续签则可以判断redis这个token key是否即将过期,如还差两分钟时,重新设置key的过期时间。
# JWT特点
- JWT本身不保证数据安全,默认是不加密的,但也可以加密。生成原始token之后再用密钥加密即可。
- JWT无论加不加密,都不建议写入私密数据。
- JWT不仅可以用于认证,也可以用于交换信息,因为本质是JSON,利用得当可以降低服务器数据库压力。
- JWT最大的缺点是:由于服务器不保存session状态,因此无法在使用过程中废止某个token,或者更改token的权限。也就是说,一旦JWT签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑,如token黑名单。
- JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少滥用,JWT的有效期应该设得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议传输。
# 敏感数据加密策略
参考支付宝支付接口,请求接口需要带上timestamp,token,sign三个请头
- timestamp:顾名思义时间戳
- token:身份认证令牌
- sign:对timestamp和token进行json转换,然后使用MD5加密(可以加点盐)或者SHA1加密,全部大写。
# 合法校验具体实现逻辑
API请求客户端想服务器端第一次发送用用户认证信息(用户名和密码),服务器端请求到改请求后,验证用户信息是否正确。如果正确:则返回一个唯一不重复的字符串(一般为UUID),然后在Redis(任意缓存服务器)中维护Token—>Uid的用户信息关系,以便其他API对token的校验。如果错误:则返回错误码。
服务器设计一个url请求拦截规则:
- 判断请求头中是否包含timestamp,token,sign,如果不含有返回错误码。
- 判断服务器接到请求的时间和参数中的时间戳是否相差很长一段时间(时间自定义如1分钟),如果超过则说明该url已经过期(如果url被盗,他改变了时间戳,但是会导致sign签名不相等)。
- 根据用户请求的url参数,服务器端按照同样的规则生成sign签名,对比签名看是否相等,相等则放行。(url签名也无法100%保证其安全,也可以通过公钥AES对数据和url加密,但这样如果无法确保公钥丢失,所以签名只是很大程度上保证安全)。
- 判断token是否有效,根据请求过来的token,查询redis缓存中的uid,如果获取不到这说明该token已过期。
- 此url拦截只需对获取身份认证的url放行(如登陆url),剩余所有的url都需拦截。
# 幂等性原则
场景:
一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
策略:
- 全局唯一业务单号(先查询再操作)
- 锁、分布式锁
- 状态机
- 消息队列去重
# 代码实现(持续更新...)
import java.io.Serializable;
public class JwtPayload implements Serializable{
private String jti; //jwt token id
private String tid; //companyId
private String cid; //appId
private String iss; //token使用方
private String sub; //token主题 格式 tid:cid:uid
private Long exp; //过期时间 毫秒
private Long iat; //创建时间 毫秒
private String uid; //user id
//省略getter setter
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23