0%

三方应用接入与OAuth的思考

最近需要为系统添加三方应用接入的功能,对接入的三方应用允许获取用户的部分信息——但显然,不能将用户的账号、密码暴露给三方应用。这个过程中产生了一些思考,在此简单记录下。如果要一句话总结的话,就是:多学知识,学习前人的成果。

三方应用接入需要解决的问题

三方应用接入关键的是服务端能够识别请求来源,判断是否是已授权的三方应用,所以这首先是一个身份识别的问题。

第二个隐含的问题是系统权限设计的问题:三方应用使用用户个人数据,是否需要用户的授权。通常这是必须的,但这次系统中面向的是内部管理应用子系统的接入授权问题,认为内部数据流转不需要此流程。

身份识别那么可以使用公钥私钥——但是这样,对于服务端就需要保存所有三方应用的公钥。那么也可以按照这种思路,使用一种更轻的方式:为每个合法的三方应用分配一个ID和secret密钥,三方应用请求时使用secret对内容进行签名,服务端验证签名,则可以识别请求来源的合法性。

基于这一思路,可以设计出一种方式:

  • 服务端为三方应用分配一个可公开的ID、一个不可公开的secret
  • 三方应用使用secret对ID签名得到sign,携带自身ID与sign向服务端请求一个随机数(面向管理端子系统,默认用户同意授权),这一随机数和三方应用ID一一对应、在一定时间内有效
  • 三方应用使用secret对随机数及时间戳签名,携带签名、时间戳和自身ID发起请求
  • 服务端校验根据三方应用ID、随机数验证签名,并比对入参时间戳限制请求发起时间,从而确认请求来自合法的三方应用

这里身份认证的可信来自于签名算法的单向和secret的私密——但也并不是非常完善的,尤其是与OAuth协议的流程对比之后。不过,稍进一步分析之后也几乎可以得到结论是,不使用https(或者类似https的非对称、对称组合)的情况下,怎么做也不安全

OAuth协议的流程

不难发现,前文设计的流程与OAuth授权流程的基本思路是一致的——所以由此也可以看出OAuth确实是非常简明的。

OAuth中最常用的是授权码模式,最典型的应用也是最常见的例子:github的OAuth app(这里有一个接入代码示例)、微信小程序请求微信登录,都是基于OAuth的授权码模式

这里再来看一下集成github OAuth的流程:

  1. 登录一个oauth app,分配到client_id(appId),client_secret
  2. 用户在三方应用请求登录时,携带client_id、client_secret、redirect_uri(用户允许登录后的重定向页面)重定向到github授权页面
  3. 如果用户在github授权页面允许授权,则由github重定向回应用指定的redirect_uri,并返回一个参数code(作用类似与前文的随机数)
  4. 三方应用服务端收到github的回调后,携带返回的code和client_id、client_secret向github请求一个token
  5. 如果token申请成功,则三方应用后续直接使用github返回的token请求github接口

了解了这一流程,回顾前文自己的设计,最大的差别是github的OAtuh流程中code随机数只用于token申请,之后的请求都是携带token进行认证的,优势是很明显的:

  • 前文流程中每次请求对code签名作为凭证,如果code超时失效则需要重新走一遍申请code流程,但是为了降低code泄露带来的风险,code的有效期不能设置的过长
  • 在前文流程中每次请求都需要进行签名认证,而OAuth流程中对三方应用的请求只是管控了入口,后续只需要对token进行验证处理

在此之外不难发现另一个差别。翻看github、微信小程序之类OAuth流程的实现,会发现在应用申请token这一步,都是直接在请求参数中携带了应用的secret————而不是前文设计的流程中一样使用secret签名,真正的secret不进行传输。这里本身OAuth协议的开放性很高,并没有什么严格的要求,需要在实现时根据需求自行决定。

为什么github、微信选择了传输secret呢?其实可以这样理解:如果不传输secret,而是由三方应用使用secret做签名,那么认证服务端为了验签,就必须保存一份应用secret的明文(虽然可能是经过对称加密的),这是一个风险点。

而在请求中携带secret,看似恐怖,其实有https的保护,安全性并不是问题————当然,需要客户端保存好secret

为什么说不使用https怎么做也不安全?

回过头来看自己设计的流程:

  1. 服务端为三方应用分配一个可公开的ID、一个不可公开的secret

需要传输、保存的过程可靠才可以认为安全

  1. 三方应用使用secret对ID签名得到sign,携带自身ID与sign向服务端请求一个随机数(面向管理端子系统,默认用户同意授权),这一随机数和三方应用ID一一对应、在一定时间内有效

不安全,使用secret对ID签名得到sign,但为了服务端识别出应用又需要在请求中携带ID,那么每次请求都是同样的sign,一旦暴露就可以任意做重放攻击

  1. 三方应用使用secret对随机数签名,携带签名和自身ID发起请求

不安全,请求参数可能泄漏

  1. 服务端校验根据三方应用ID、随机数验证签名,从而确认请求来自合法的三方应用

不安全,步骤3中的请求参数泄漏后,同样参数的请求每次的sign相同,可以任意做重放攻击

即使使用申请token的模式、申请token的过程做到可信,携带token的请求如果不使用https,也无安全性可言;即使请求使用https,在客户端存储的token也有暴露的风险,只能从有效期、token状态、绑定终端等做补救。

但是再进一步:做到了以上手段,认证服务端对应用客户端的识别基本可靠了,但是,应用客户端怎么确认收到的响应是来自真实的认证服务端呢?