RESTful api接口安全优雅设计

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RESTful api接口安全优雅设计相关的知识,希望对你有一定的参考价值。

参考技术A                                                                                                                                                                                                                                                  -- 陈万洲

在项目中,需要为APP撰写API。刚开始接触的时候,并没有考虑太多,就想提供URL,APP端通过该URL进行查询、创建、更新等操作即可。但再对相关规范进行了解后,才发现,API的设计并没有那么简单,远远不是URL的问题,而是一个通信协议的整体架构

请求模式也可以说是动作、数据传输方式,通常我们在web中的form有GET、POST两种,而在HTTP中,存在下发这几种。

常见的请求参数

比如在数据过多, 需要对数据进行分页请求的时候, 我们应该统一 API 请求参数. 常见的有这些.

API的开发直接关系了APP是否可以正常使用,如果原本运行正常的API,突然改动,那么之前使用这个API的APP可能无法正常运行。APP是不可能强迫用户主动升级的,因此,通过API版本来解决这个问题。也就是说,API的多个版本是同时运行的,而且都要保证可以正常使用。

按照RESTful的规范,不同的版本也应该用相同的API URL,通过header信息来判断版本,再调用不同版本的程序进行处理。但是这明显会给开发带来巨大的成本。

解决办法有以下几种:

接口的数据一般都采用JSON格式进行传输,不过,需要注意的是,JSON的值只有六种数据类型:

所以,传输的数据类型不能超过这六种数据类型。以前,我们曾经试过传输Date类型,它会转为类似于"2016年1月7日 09时17分42秒 GMT+08:00"这样的字符串,这在转换时会产生问题,不同的解析库解析方式可能不同,有的可能会转乱,有的可能直接异常了。要避免出错,必须做特殊处理,自己手动去做解析。为了根除这种问题,最好的解决方案是用毫秒数或者字符串表示日期。

服务器返回的数据结构,一般为:

不同错误需要定义不同的返回码,属于客户端的错误和服务端的错误也要区分,比如1XX表示客户端的错误,2XX表示服务端的错误。这里举几个例子:

错误信息一般有两种用途:一是客户端开发人员调试时看具体是什么错误;二是作为App错误提示直接展示给用户看。主要还是作为App错误提示,直接展示给用户看的。所以,大部分都是简短的提示信息。

data字段只在请求成功时才会有数据返回的。数据类型限定为对象或数组,当请求需要的数据为单个对象时则传回对象,当请求需要的数据是列表时,则为某个对象的数组。这里需要注意的就是,不要将data传入字符串或数字,即使请求需要的数据只有一个,比如token,那返回的data应该为:

首先,使用https可以在数据包被抓取时多一层加密。我们现在的APP使用环境大部分都是在路由器WIFI环境下,一旦路由器被入侵,那么黑客可以非常容易的抓取到用户通过路由器传输的数据,如果使用http未经加密,那么黑客可以很轻松的获取用户的信息,甚至是账户信息。

其次,即使使用https,也要在API数据传输设计时,正确的采用加密。例如直接将token信息放在URL中的做法,即使你使用了https,黑客抓不到你具体传输的数据,但是可以抓到你请求的URL啊!(查了资料了,https用GET方式请求,也仅能抓到域名字符部分,不能抓到请求的数据,但是URL可以在浏览器或特殊客户端工具中直接看到。多谢下面的朋友指正错误)因此,使用https进行请求时,要采用POST、PUT或者HEAD的方式传输必要的数据。

现在,大部分App的接口都采用RESTful架构,RESTFul最重要的一个设计原则就是,客户端与服务器的交互在请求之间是无状态的,也就是说,当涉及到用户状态时,每次请求都要带上身份验证信息。实现上,大部分都采用token的认证方式,一般流程是:

然而,此种验证方式存在一个安全性问题:当登录接口被劫持时,黑客就获取到了用户密码和token,后续则可以对该用户做任何事情了。用户只有修改密码才能夺回控制权。

如何优化呢?第一种解决方案是采用HTTPS。HTTPS在HTTP的基础上添加了SSL安全协议,自动对数据进行了压缩加密,在一定程序可以防止监听、防止劫持、防止重发,安全性可以提高很多。不过,SSL也不是绝对安全的,也存在被劫持的可能。另外,服务器对HTTPS的配置相对有点复杂,还需要到CA申请证书,而且一般还是收费的。而且,HTTPS效率也比较低。一般,只有安全要求比较高的系统才会采用HTTPS,比如银行。而大部分对安全要求没那么高的App还是采用HTTP的方式。

我们也给每个端分配一个appKey,比如androidios、微信三端,每个端分别分配一个appKey和一个密钥。没有传appKey的请求将报错,传错了appKey的请求也将报错。这样,安全性方面又加多了一层防御,同时也方便对不同端做一些不同的处理策略。

另外,现在越来越多App取消了密码登录,而采用手机号+短信验证码的登录方式,我在当前的项目中也采用了这种登录方式。这种登录方式有几种好处:

不需要注册,不需要修改密码,也不需要因为忘记密码而重置密码的操作了;

用户不再需要记住密码了,也不怕密码泄露的问题了;

相对于密码登录其安全性明显提高了。

显式用户和隐式用户,我不知道这两个词用的是否确切。 

显式用户指的是,APP程序中有用户系统,一个username、password正确的合法用户,称之为显式的用户,

通常显式用户都需要注册,登录以后能完成一些个人相关的操作。

隐式用户指的是,APP程序本身就没有用户系统,或者一个在没有登录的情况下,使用我们APP的用户。

在这种情况下,可以通过客户端生成的UDID来标识一个用户。

有了用户信息,我们就能够了解不同用户的使用习惯,而不仅仅是全体用户的一个整体的统计信息,

有了这些个体的信息之后,就可以做一些用户分群、个性化推荐之类的事情。

如果是SAAS版本,还需要区分不同商户的用户

接口文档有时候是项目初期就定下来的,前后端开发人员按照接口规范开发,

有的是接口开发完成后写的。

接口文档要清晰、明了,包含多少个接口,每个接口的地址、参数、请求方式、数据交换格式、返回值等都要写清楚。

接口测试程序,有条件的话,也可以提供,方便前后端的调试。

如果是springMVC开发的话,可以用swagger,后端只要加几个注释发一个url给前端就可以了,轻松又高效。

在做PC端网站的时候,我们都会给我们的网站加上个统计功能,要么自己写统计系统,要么使用第三方的比如GA、百度等。

移动端接口API则需要我们自己实现统计功能,

这时就需要我们尽可能多的收集客户端的信息,除了传统的IP、User-Agent之外,还应该收集一些移动相关的信息,

比如

手机操作系统,是android还是ios,都是什么版本,

用户使用的网络状况,是2G、3G、4G还是WIFI

客户端APP是什么版本信息。

这样,有助于我们更好的了解我们用户的使用情况。

安全优雅的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接口安全优雅设计的主要内容,如果未能解决你的问题,请参考以下文章

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

SpringBoot定义优雅全局统一Restful API 响应框架三

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

RESTful API 规范

Java高并发秒杀API之web层

瞧瞧别人家的API接口,那叫一个优雅