安全优雅的RESTful API签名实现方案(手机端)

Posted hjzgg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安全优雅的RESTful API签名实现方案(手机端)相关的知识,希望对你有一定的参考价值。

安全优雅的RESTful API签名实现方案

1、接口签名的必要性

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

2、项目中签名方案痛点

  • 每个接口有各自的签名方案,不统一,维护成本较高。
  • 没有对消息实体进行签名,无法避免数据被篡改。
  • 无法避免数据重复提交。

3、签名及验证流程

技术图片

4、签名规则

  • 线下分配appid和appsecret,针对不同的调用方分配不同的appid和appsecret。
  • 加入timestamp(时间戳),10分钟内数据有效。
  • 加入流水号nonce(防止重复提交),至少为10位。针对查询接口,流水号只用于日志落地,便于后期日志核查。 针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求。
  • 加入signature,所有数据的签名信息。
    其中appid、timestamp、nonce、signature这四个字段放入请求头中。

5、签名生成

5.1、数据部分

  • Path:按照path中的顺序将所有value进行拼接
  • Query:按照key字典序排序,将所有key=value进行拼接
  • Form:按照key字典序排序,将所有key=value进行拼接
  • Body:
    技术图片
    如果存在多种数据形式,则按照path、query、form、body的顺序进行再拼接,得到所有数据的拼接值。
    上述拼接的值记作 Y。

5.2、请求头部分

X="appid=xxxnonce=xxxtimestamp=xxx"

5.3、生成签名

最终拼接值=XY
最后将最终拼接值按照如下方法进行加密得到签名(signature)。
技术图片

6、签名算法实现

6.1、指定哪些接口或者哪些实体需要签名

技术图片

6.2、指定哪些字段需要签名

技术图片

6.3、签名核心算法(SignatureUtils)

  • toSplice方法首先判断对象是否注有@Signature注解,如果有则获取签名的排序规则(key值字典序排序或者指定order的值进行排序),比如排序规则是Signature.ALPHA_SORT(字典序)会调用alphaSignature方法生成key=value的拼接串;如果对象没有@Signature注解,该对象类型可能是数组、者集合类等,则调用toString方法生成key=value的拼接串。
    技术图片
  • alphaSignature方法通过反射获取到对象的所有Field属性,需要判断两种情况:(1)获取该Field属性对应的Class信息,如果Class信息含有@Signature注解,则调用toSplice方法生成key=value的拼接串;(2)该Field属性含有@SignatureField注解,调用toString方法生成key=value的拼接串。
    技术图片
  • toString方法针对array, collection, simple property, map类型的数据做处理。其中如果对象是java的simple property类型,直接调用对象的toString方法返回value;如果是array、collection、map类型的数据,再调用toSplice方法生成key=value的拼接串。
    技术图片

7、签名校验

7.1、header中参数

技术图片

7.2、签名实体SignatureHeaders, 绑定request中header信息

技术图片

7.3、生成签名实体SignatureHeaders并验证

技术图片
签名验证需要如下几个步骤。

  • 处理header name,通过工具类将header信息绑定到签名实体SignatureHeaders对象上。
  • 验证appid是否合法。
  • 根据appid从配置中心中拿到appsecret。
  • 请求是否已经超时,默认10分钟。
  • 随机串是否合法。
  • 是否允许重复请求。
  • 重新生成签名,验证签名是否一致。

7.4、生成header信息参数拼接

技术图片

7.5、切面拦截控制层方法,生成method中参数的拼接

技术图片
generateAllSplice方法是在控制层切面内执行,可以在方法执行之前获取到已经绑定好的入参。分别对注有@PathVariable、@RequestParam、@RequestBody、@ModelAttribute注解的参数进行参数拼接的处理。其中注@RequestParam注解的参数需要特殊处理一下,分别考虑数组、集合、原始类型这三种情况。

7.6、对最终的拼接结果重新生成签名信息

技术图片

8、客户端使用示例

8.1、生成签名

技术图片

8.2、输出结果

拼接结果: appid=111^_^appsecret=222^_^nonce=c9e778ba668c8f6fedf35634eb493af6304d54392d990262d9e7c1960b475b67^_^timestamp=1538207443910^_^w8rAwcXDxcDKwsM=^_^
签名数据: SignatureHeadersappid=111, appsecret=222, timestamp=1538207443910, nonce=c9e778ba668c8f6fedf35634eb493af6304d54392d990262d9e7c1960b475b67, signature=0a7d0b5e802eb5e52ac0cfcd6311b0faba6e2503a9a8d1e2364b38617877574d
请求数据: [w8rAwcXDxcDKwsM=]

9、思考

上述的签名方案的实现校验逻辑是在控制层的切面内完成的。如果项目用的是springmvc框架,可以放在Filter或者拦截器里吗?很明显是不行的(因为ServletRequest的输入流InputStream 在默认情况只能读取一次)。上述方案需要获取绑定后的参数结果,然后执行签名校验逻辑。在执行控制层方法之前,springmvc已经帮我们完成了绑定的步骤,当然了,在绑定的过程中会解析ServletRequest中参数信息(例如path参数、parameter参数、body参数)。

其实如果我们能在Filter或者拦截器中实现上述方案,那么复杂度将会大大的降低。首先考虑如何让ServletRequest的输入流InputStream可以多次读取,然后通过ServletRequest获取path variable(对应@PathVariable)、parameters(对应@RequestParam)、body(对应@RequestBody)参数,最后整体按照规则进行拼接并生成签名。

优化方案参考:https://www.cnblogs.com/hujunzheng/p/10178584.html

以上是关于安全优雅的RESTful API签名实现方案(手机端)的主要内容,如果未能解决你的问题,请参考以下文章

开放接口/RESTful/Api服务的设计和安全方案

如何设计好的RESTful API之安全性

如何用 flask 优雅的实现 restful api

为啥 RSA 签名通常不用于 RESTful api?

如何使用 ThinkJS 优雅的编写 RESTful API

springboot集成swagger2,构建优雅的Restful API