java接口签名框架

1 接口签名的必要性

       在为第三方系统提供接口的时候,肯定要考虑接口数据的安全问题,比如数据是否被篡改数据是否已经过时数据是否可以重复提交等问题。其中我认为最终要的还是数据是否被篡改

1.1 什么是重放攻击

      重放攻击,web漏洞中称会话重放漏洞,又称重播攻击、回放攻击。

      指的是先截取主机A发送给主机B的报文,入侵者把A请求B的报文原封不动地再发送一次,两次...n次,使主机B误以为入侵者就是主机A,然后进入到正常逻辑中并返回响应。如果是付款接口,或者购买接口就会造成损失,因此需要采用防重放的机制来做请求验证,如请求参数上加上时间戳(timestamp)和随机数(nonce)。

1.2 HTTPS数据加密是否可以防止重放攻击

      不可以,加密可以有效防止明文数据被监听,但是却防止不了重放攻击。

1.3 开发中的appId、appKey、appSecret到底是什么

1.3.1 appId:应用的唯一标识

      用来标识你的开发者账号的,即:用户id,可以在数据库添加索引,方便快速查找,app_key和app_secret是一对出现的账号, 同一个appId可以对应多个appKey + appSecret,这样平台就可以分配你不一样的权限, 比如app_key1 + app_secect1只有只读权限,但是app_key2 + app_secret2有读写权限… 这样你就可以把对应的权限放给不同的开发者;其中权限的配置都是直接跟app_key做关联的,app_key也需要添加数据库检索,方便快速查找。

1.3.2 appKey:公匙(相当于账号)

      公开的,调用服务所需要的密钥。是用户的身份认证标识,用于调用平台可用服务,可以简单理解成是账号。

1.3.3 appSecret:私匙(相当于密码)

      签名的密钥,是跟appKey配套使用的,可以简单理解成是密码。

1.3.4 token:令牌(过期失效)

1.3.5 使用方法

      向第三方服务器请求授权时,带上AppKey和AppSecret(需存在服务器端)。

      第三方服务器验证appKey和appSecret在数据库、缓存中有没有记录;如果有,生成一串唯一的字符串(token令牌),返回给服务器,服务器再返回给客户端,后续客户端每次请求都需要带上token令牌。

1.3.6 为什么要有appKey+appSecret这种成对出现的机制

      因为要加密, 通常用在首次验证(类似登录场景), 用appKey(标记要申请的权限有哪些)+appSecret(密码,表示你真的拥有这个权限)来申请一个token, 就是我们经常用到的accessToken(通常拥有失效时间),后续的每次请求都需要提供accessToken表明验证权限通过。

1.3.7 权限划分

      现在有了统一的appId,此时如果针对同一个业务要划分不同的权限,比如同一功能,某些场景需要只读权限,某些场景需要读写权限。这样提供一个appId和对应的秘钥appSecret就没办法满足需求。此时就需要根据权限进行账号分配,通常使用appKey和appSecret。

      由于appKey和appSecret是成对出现的账号,同一个appId可以对应多个 appKey + appSecret,这样平台就为不同的appKey + appSecret对分配不一样的权限。

1.3.8 简化的场景

      第一种场景:通常用于开放性接口,像地图api,会省去app_id和app_key,此时相当于三者相等,合而为一appId = appKey = appSecret。这种模式下,带上app_id的目的仅仅是统计某一个用户调用接口的次数而已。

      第二种场景:当每一个用户有且仅有一套权限配置可以去掉appKey,直接将app_id = app_key,每个用户分配一个appId + appSecret就够了。

      也可以采用签名(signature)的方式:当调用方向服务提供方法发起请求时,带上(appKey、时间戳timeStamp、随机数nonce、签名sign)签名sign可以使用 (appSecret + 时间戳 + 随机数)使用sha1、md5生成,服务提供方收到后,生成本地签名和收到的签名比对,如果一致,校验成功。

签名流程

3 签名规则

      分配appId(开发者标识)和appSecret(密钥),给不同的调用方。

      可以直接通过平台线上申请,也可以线下直接颁发。appId是全局唯一的,每个appId将对应一个客户,密钥appSecret需要高度保密。

      加入timeStamp(时间戳),以服务端当前时间为准,单位为ms,5分钟内数据有效。间戳的目的就是为了减轻DOS攻击。防止请求被拦截后一直尝试请求接口。服务器端设置时间戳阀值,如果服务器时间 减 请求时间戳超过阀值,表示签名超时,接口调用失败。

      加入临时流水号nonce,至少为10位,有效期内防重复提交。

      随机值nonce主要是为了增加签名sign的多变性,也可以保护接口的幂等性,相邻的两次请求nonce不允许重复,如果重复则认为是重复提交,接口调用失败。

针对查询接口,流水号只用于日志落地,便于后期日志核查。

针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求。

通过在接口签名请求参数加上时间戳(timeStamp)+ 随机数(nonce)可以防止 “重放攻击”。

3.1 时间戳(timeStamp)

      以服务端当前时间为准,服务端要求客户端发过来的时间戳,必须是最近60秒内(假设值,自己定义)的。这样,即使这个请求即使被截取了,也只能在60s内进行重放攻击。

3.2 随机数(nonce)

      但是,即使设置了时间戳,攻击者还有60s的攻击时间呢!

      所以我们需要在客户端请求中再加上一个随机数(中间黑客不可能自己修改随机数,因为有参数签名的校验呢),服务端会对一分钟内请求的随机数进行检查,如果有两个相同的,基本可以判定为重放攻击。

      因为正常情况下,在短时间内(比如60s)连续生成两个相同nonce的情况几乎为0。

服务端“第一次”在接收到这个nonce的时候做下面行为:

1)去redis中查找是否有key为nonce:{ nonce}的数据

2)如果没有,则创建这个key,把这个key失效的时间和验证timestamp失效的时间一致,比如是60s。

3)如果有,说明这个key在60s内已经被使用了,那么这个请求就可以判断为重放请求。

      加入签名字段sign,获取调用方传递的签名信息。

      通过在接口签名请求参数加上appId + sign解决身份验证和防止“参数篡改”。

1)请求携带参数appId和Sign,只有拥有合法的身份appId和正确的签名Sign才能放行。这样就解决了身份验证和参数篡改问题。

2)即使请求参数被劫持,由于获取不到appSecret(仅作本地加密使用,不参与网络传输),也无法伪造合法的请求。

签名的生成

4.1 签名signature字段生成规则

      鉴权参数 = 请求头签名参数(appId,timeStamp,nonce) + 请求URL地址(调用方请求接口完整url地址) + 请求Request参数(针对是Get请求时) + 请求Body(非针对是Get请求时 ,如Post请求)。

      先将鉴权参数以key-value的格式存储,并以key值正序排序,进行拼接。如: key1value1key2value2;最后将上面拼接的字符串在拼接应用密钥appSecret,如: key1value1key2value2 + appSecret;将最终拼接成的字符串转成utf-8的字节数组,后然后做Md5不可逆加密 Md5(key1value1key2value2 + appSecret) 得到的字符串作为签名signature。

4.2 请求参数描述

4.2.1 请求头

      请求头="appId=xxxx&nonce=xxxx&tamp=xxxx&sign=xxx"

      请求头中的4个参数是必须要传的,否则直接报异常。

4.2.2 请求URL

      请求该接口的完整地址。

4.2.3 请求数据

请求数据的拼接规则

Path:按照path中的顺序将所有value进行拼接。

URL 路径参数指的是通过在 URL 的斜杠后面传递的参数。比如我们要访问id为2的project, 则可以访问/project/2这个URL。

Query:按照key的字典顺序排序,将所有key=value进行拼接。

查询字符串参数(query string)和 路径参数类似,你也可以通过查询字符串的形式传递id。查询字符串就是在url中通过?号后面加参数。比如/project/?id=2这种形式。

Form:按照key的字典顺序排序,将所有key=value进行拼接。

表示为表单请求时携带的数据。

Body:

表示是一个raw数据请求(纯字符串格式),比如json的方式传递。

Json: 按照key的字典顺序排序,将所有key=value进行拼接

(例如{“a”:“a”,“c”:“c”,“b”:{“e”:“e”}} => a=a_b=e=e_c=c)

String: 整个字符串作为一个拼接。

分别对应 SpringMvc提供的获取参数的注解:

@RequestParam:处理(前端)Content-Type为 application/x-www-form-urlencoded或者form-data编码的内容

@PathVariable:模板变量,一般用于get请求,即 XXX/{XXXid}

@RequestBody:常用来处理Content-Type为application/json, application/xml码的内容,前端规定的是raw方式。

如果存在多种数据形式,同种数据内按照上面描述的四种规则进行拼接,拼接好后,不同的数据格式则按照path、query、form、body的顺序进行二次拼接,得到所有数据最终的拼接值。

接口签名的实现

5.1 实现步骤

      基本原理其实也比较简单,就是自定义过滤器或者拦截器,对每个请求进行拦截处理,在服务端取到调用方的参数后按同样的签名规则进行匹配。

5.1.1 整体流程如下

1)验证请求头参与签名的必传参数;

2)获取请求头参数,Url请求路径 ,请求数据,把这些值放入SortMap中进行排序;

3)对SortMap里面的值进行拼接;

4)对拼接的值进行加密,生成签名sign;

5)把后台生成的签名sign和调用方传入的签名sign进行比较,如果不相同就返回错误;

6 实现代码

7 API接口设计补充建议

7.1 使用POST作为接口请求方式

      一般调用接口最常用的两种方式就是GET和POST。两者的区别也很明显,GET请求会将参数暴露在浏览器URL中,而且对长度也有限制。为了更高的安全性,所有接口都采用POST方式请求。

7.2 客户端IP白名单

      ip白名单是指将接口的访问权限对部分ip进行开放来避免其他ip进行访问攻击。

  • 设置ip白名单缺点就是当你的客户端进行迁移后,就需要重新联系服务提供者添加新的ip白名单。
  • 设置ip白名单的方式很多,除了传统的防火墙之外,spring cloud alibaba提供的组件sentinel也支持白名单设置。
  • 为了降低api的复杂度,推荐使用防火墙规则进行白名单设置。

7.3 单个接口针对ip限流

      限流是为了更好的维护系统稳定性。

      使用redis进行接口调用次数统计,ip+接口地址作为key,访问次数作为value,每次请求value+1,设置过期时长来限制接口的调用频率。

7.4 记录接口请求日志

      记录请求日志,快速定位异常请求位置,排查问题原因。(如:用aop来全局处理接口请求)

7.5 敏感数据脱敏

      在接口调用过程中,可能会涉及到订单号等敏感数据,这类数据通常需要脱敏处理。

      最常用的方式就是加密。加密方式使用安全性比较高的RSA非对称加密。 非对称加密算法有两个密钥,这两个密钥完全不同但又完全匹配。只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。

7.6 幂等性问题

幂等性是指: 任意多次请求的执行结果和一次请求的执行结果所产生的影响相同。

  • 说的直白一点就是查询操作无论查询多少次都不会影响数据本身,因此查询操作本身就是幂等的。
  • 但是新增操作,每执行一次数据库就会发生变化,所以它是非幂等的。
  • 幂等问题的解决有很多思路,这里讲一种比较严谨的。

      提供一个生成随机数的接口,随机数全局唯一。调用接口的时候带入随机数。

      第一次调用,业务处理成功后,将随机数作为key,操作结果作为value,存入redis,同时设置过期时长。

      第二次调用,查询redis,如果key存在,则证明是重复提交,直接返回错误。

7.7 版本控制

      一套成熟的API文档,一旦发布是不允许随意修改接口的。这时候如果想新增或者修改接口,就需要加入版本控制,版本号可以是整数类型,也可以是浮点数类型。

      一般接口地址都会带上版本号,http://ip:port//v1/list , http://ip:port//v2/list

7.8 响应状态码规范

      一套成熟的API,还需要提供简单明了的响应值,根据状态码就可以大概知道问题所在。我们采用http的状态码进行数据封装,例如200表示请求成功,4xx表示客户端错误,5xx表示服务器内部发生错误。

7.9 统一响应数据格式

      为了方便给客户端响应,响应数据会包含三个属性,状态码(code),信息描述(message),响应数据(data)。客户端根据状态码及信息描述可快速知道接口,如果状态码返回成功,再开始处理数据。

7.10 接口文档

      一套成熟的API,还少不了一个优秀的接口文档。接口文档的可读性非常重要,虽然很多程序员都不喜欢写文档,而且不喜欢别人不写文档。为了不增加程序员的压力,推荐使用swagger2或其他接口管理工具,通过简单配置,就可以在开发中测试接口的连通性,上线后也可以生成离线文档用于管理API。

7.11 生成签名sign的详细步骤

      第1步: 将所有参数(注意是所有参数,包括appId,timeStamp,nonce),除去sign本身,以及值是空的参数,按key名升序排序存储。

      第2步: 然后把排序后的参数按 key1value1key2value2…keyXvalueX的方式拼接成一个字符串。(这里的参数和值必须是传输参数的原始值,不能是经过处理的,如不能将"转成”后再拼接)。

      第3步: 把分配给调用方的密钥secret拼接在第2步得到的字符串最后面。

即: key1value1key2value2…keyXvalueX + secret

  • 第4步: 计算第3步字符串的md5值(32位),然后转成大写,最终得到的字符串作为签名sign。

      即: Md5(key1value1key2value2…keyXvalueX + secret) 转大写。

8 案例

API签名验证工具: 🎉 API签名验证工具,接口数据的安全问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值