zoukankan      html  css  js  c++  java
  • [译]移动API安全终极指南

    文章主要讲了移动api调用的授权和验证问题,原文链接:The Ultimate Guide to Mobile API Security

    移动API的使用是Stack Overflow和 Stormpath支持频道上经常出现的话题。这是一个已经被解决的问题,但是需要大量必要的知识和充足的理解才能很好地实现。

    这篇文章能够让你了解到有关于在移动设备上安全的调用Restful API的一切内容,无论你是在构建一个需要访问Restful API的移动app,还是正在写Restful API并且打算和移动app开发者进行交互。

    我的目标不仅仅是解释如何确保移动开发者能够安全调用你的Restful API,同时还解释整个凭证从开始到结束的交换过程,以及如何从安全漏洞中恢复等等。

    移动API安全问题

    在我们投身去探究如何确保移动开发者能够安全调用Restful API之前,让我们先讨论一下什么使得移动认证有别于传统的API认证。

    API认证最基本的形式就是一般为我们所知的HTTP Basic Authentication

    对于API服务开发者和使用它的人来说,它的工作方式是非常简单的:

    • 开发者会获取到一个API key(一般是ID和Secret)。这个API key一般像这样:3bb743bbd45d4eb8ae31e16b9f83c9ba:ffb7d6369eb84580ad2e52ca3fc06c9d
    • 开发者有责任将API key存放在一个安全的地方,没有人可以得到它。
    • 开发者发起API请求时,需要把API key放到HTTP Authorization header中,同时带有关键词Basic(也就是用户名和密码都使用base64加密)。下面展示了开发者在调用API时如何指定API key进行认证,使用命令行工具cURL
     $ curl --user 3bb743bbd45d4eb8ae31e16b9f83c9ba:ffb7d6369eb84580ad2e52ca3fc06c9d https://api.example.com/v1/test 
    

    cURL工具可以将获取到的API证书用base64加密,然后创建一个HTTP Authorization hearder,如下所示:Basic M2JiNzQzYmJkNDVkNGViOGFlMzFlMTZiOWY4M2M5YmE6ZmZiN2Q2MzY5ZWI4NDU4MGFkMmU1MmNhM2ZjMDZjOWQ=.。而API服务器将会从HTTP Authorization hearder中获取字符串,然后用base64解密,获得ID和Secret,验证通过后再处理相应的API请求。

    HTTP Basic Authentication虽然简单,但是很好用。开发者在调用API时附带API key可以很好地与API服务器进行验证。

    但是,由于移动app无法安全存储API秘钥,使得HTTP Basic Authentication并不是一个很好的选择。同时,HTTP Basic Authentication要求在每次请求时使用原始的API keys,这就导致了keys会被长期使用,这是不安全的。

    因此在大多数情况下,这种验证方式是不切实际的,因为无法安全的将API keys嵌入到分配给许多用户的移动app中。

    比如说,你将API keys嵌入到你所构建的移动app中,然而一个精明的用户能够对你app进行反向工程,获取到keys,从而滥用你的API服务。

    这就是为什么在不可信的环境中,HTTP Basic Authentication不是一个最好的选择,例如web浏览器和移动app。

    注意:和其他所有认证协议一样,HTTP Basic Authentication必须在SSL之上使用。

    为移动安全引入OAuth2.0

    在此之前你可能听说过OAuth,并且在争论它是什么和它并没有足够的好。那么让我们明确:OAuth2是一个优秀的协议,在不受信任的设备中用于保护API服务的安全。它通过一种我们称之为token的认证方式来对移动用户进行认证。

    下面我们从用户的角度OAuth2 token认证的工作方式。

    1. 用户打开移动app,输入账号和密码。
    2. 移动app发送一个带有用户的用户名和密码的POST请求到API服务器,服务器进行验证,成功后返回一个code。
    3. 移动app通过code向认证服务器发送请求,认证成功后为用户生成一个一段时间后就会过期的access token。
    4. 将access token存储在移动设备本地,就像一个能够访问API服务的API key一样。
    5. 一旦access token过期就不再工作,需要再提示用户输入用户名密码,重复1的步骤。

    之所以说OAuth2在保障APIs安全上是极好的,是因为我们不需要在一个不安全的环境中长期存储API keys,取而代之的是生成一个临时的access tokens。这可以抵御一些潜在的攻击。

    现在,当您的API服务生成您的移动应用程序需要的Oauth2 token时,您当然需要将其存储在您的移动应用程序中。

    但是存在哪里?!

    token存储的位置取决于你所处的开发平台。如果你开发的是一个android app,你会将access tokens存储在SharedPreferences中,如果你的IOS开发者,你将会将access tokens存在Keychain中。

    如果你仍然存在疑问,下面这两个Stack Overflow的posts可能会非常有用,它详细阐述了如何在移动app中存储access token。

    现在你应该对OAuth2有所了解,为什么要使用它,以及它的工作原理。

    Access Tokens

    让我们来讨论一下access tokens。他们到底是什么?是一串随机生成的数字?是uuid?还是别的?
    这是一个很好的问题。
    这里是一个比较短的答案:access token在技术上你任何你想要的字符串:

    • 一个随机数
    • 一个随机字符串
    • 一个uuid
    • 更多

    更详细的解释:

    • 为客户端而生成的。
    • 验证token的所有者是你(使用强的签名)
    • 存在过期时间。
      有了以上的解释,你可能会希望去遵守一定的规范。为了不自己处理这些,你可以直接使用JWT(Json Web Token)。这是一个比较新的规范,允许你生成access token。这个规范(RFC7519)满足一下几点:
    • 可以为客户端生成
    • 可以被创建者验证
    • 在某一具体时间后自动过期
    • 可以容纳可变的JSON信息
    • 无需查询API服务,允许用户本地验证API凭证,从而减少对API调用的次数。

    JWTs看起来像随机生成的字符串,在使用的时候你可以像字符串那样存储他们。这使得取代传统的access token而使用JWTs非常方便,毕竟它们基本上都是相同的,而JWT有更多的优势。

    JWTs总是以加密的方式被验证。他们工作的方式如下:

    • 在API服务器的某处存储一个安全随机字符串。一般是一个比较长的随机字符串(40个字符左右差不多)
    • 当你创建一个新的JWT时,你需要将这个随机串传递个JWT类库来签署token,同时带有一些你想存储的数据,例如用户ID,email等等。
    • token将会被生成,看起来像header.claims.signature——header、claims和signature都是base64加密的字符串。
    • 讲生成的token给用户,一般是API的使用者,例如移动app。

    现在从移动客户端中,你可以看到存储在JWT中的任何东西。so如果我有一个JWT,我可以轻松地查看到里面包含的JSON数据,通常如下所示:

    {
      "user_id": "e3457285-b604-4990-b902-960bcadb0693",
      "scope": "can-read can-write"
    }
    

    当然,这是一个100%虚构的例子,但是你可以从中知道,如果你得到这个JWT的副本,你也可以看到存储在里面的JSON信息。

    JWT标准支持自动的过期标记,因此你也能够验证这个JWT是有效的。无论你使用什么开发语言,只要在使用JWT库,你就能验证JWT的有效性直到过期!

    这意味着,如果你使用JWT访问API服务,则只需验证JWT,就可以知道你的API调用是否可以工作,不需要API调用。

    现在,一旦你有了一个有效的JWT,你也可以在服务器端用它做很酷的事情。

    假设您已经向移动app发布了一个包含以下数据的JWT:

    {
      "user_id": "e3457285-b604-4990-b902-960bcadb0693",
      "scope": "can-read can-write"
    }
    

    假设移动app上有恶意代码能够修改你的JWT,比如说:

    {
      "user_id": "e3457285-b604-4990-b902-960bcadb0693",
      "scope": "can-read can-write can-delete"
    }
    

    如果这个修改过的token被发送到我们的API服务器会发生什么?它会起作用吗?我们的服务器会接受这个修改的JWT吗?

    注意!!
    当你的API服务收到JWT并进行验证时,它会做以下几件事情:

    • 通过使用只有服务器才知道的随机生成的字符串来检查token,确保它没有被篡改。如果JWT完全被修改,这个检查就会失败,你会知道有人正在试图做一些令人讨厌的事情。
    • 同时服务器会检查JWT的过期时间,确保它的合法性。so如果客户端用一个已经过期的token来进行请求,你可以立刻拒绝。

    这是很好的功能,因为它使处理验证/到期/安全更简单。

    在使用JWTs时,你唯一需要记住的就是:不要在JWT中存储敏感信息。

    谨记以上提到的规则,那么在使用JWTs时就不会出错了。

    通常你会存一下两条信息在JWTs中:

    • 用户帐户的某种唯一ID。这样,当您收到此JWT进行身份验证时,您可以从用户数据库中查找此用户。
    • 用户权限。当然如果构建的是所有用户都能访问的简单的API,那么这个可能是不必要的。但是通过这个,用户就不需要通过看API文档来了解自己可以做什么,不可以做什么,只需要根据看自己的JWT就可以。

    以上就是有关于JWT的全部。

    JWT是如何工作的

    在本节中,我们将深入讨论整个流程,包括从开始到结束的整个流程,以及构建安全API服务所需的所有低级技术细节,这些API服务可以从移动设备安全地使用。

    具体的工作流程如下图所示:

    1. User Opens App
    用户打开app,然后下一步。
    2. App Asks for Credentials
    由于我们准备使用OAuth2密码授权类型方案来根据我们的API服务对用户进行身份验证,因此您的应用程序需要询问用户的用户名,电子邮件地址和密码。
    现在几乎所有的移动应用程序都要求这样做,所以用户习惯于输入这些信息。
    3. User Enters their Credentials
    用户输入自己的凭证信息,下一步。
    4. App Sends POST Requests to API Service
    这是最初的OAuth2流程开始的地方。你将要做的是从你的移动应用程序向你的API服务发送一个简单的HTTP POST请求。
    这是一个使用cURL来发送POST请求的实例:

    $ curl --form 'grant_type=password&username=USERNAMEOREMAIL&password=PASSWORD' https://api.example.com/v1/oauth
    

    我们在这里做的是将用户名或电子邮件和密码发送到我们的 API 服务,使用 OAuth2 密码授予类型: (有几种授予类型, 但这是一个我们将谈论这里, 因为它是唯一相关的, 当讨论建立自己的可移动的 API)。

    5. API Server Authenticates the User
    接下来发生的事情是,API服务将去取出传入的用户名或电子邮件和密码数据,并验证用户的凭据。

    这一步不同的开发平台会有不同,但是一般步骤如下:

    1. 通过用户名和密码查询用户的账户信息。
    2. 比对加密的密码信息。
    3. 如果验证成功,进行下一步,否则返回错误信息,认证不合法。

    6. API Server Generates a JWT that the App Stores
    现在服务器已经对应用的OAuth2请求进行了身份验证,需要为该应用生成access token。为此,服务器将使用JWT库来生成有用的access token,然后将其返回给应用程序。

    1. 根据JWT的规则生成access token。
    2. 以JSON的形式返回,如下
    {
      "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJEUExSSTVUTEVNMjFTQzNER0xHUjBJOFpYIiwiaXNzIjoiaHR0cHM6Ly9hcGkuc3Rvcm1wYXRoLmNvbS92MS9hcHBsaWNhdGlvbnMvNWpvQVVKdFZONHNkT3dUVVJEc0VDNSIsImlhdCI6MTQwNjY1OTkxMCwiZXhwIjoxNDA2NjYzNTEwLCJzY29wZSI6IiJ9.ypDMDMMCRCtDhWPMMc9l_Q-O-rj5LATalHYa3droYkY",
      "token_type": "bearer",
      "expires_in": 3600
    }
    

    正如你上面看到的,我们的JSON响应包含3个字段。第一个字段access_token是实际的OAuth2访问令牌,移动应用程序将从这一点开始使用,以便进行已认证的API请求。

    第二个字段token_type只是告诉移动应用程序我们正在提供什么类型的访问令牌 - 在这种情况下,我们提供了一个 OAuth2 Bearer token。稍后我会再谈这个。

    最后,提供的第三个字段是expires_in字段。这基本上是提供的access token有效的秒数。

    在上面的例子中, 我们要说的是, 我们给这个移动应用程序一个access token, 它可以被用来访问我们的私有 API 长达1小时-没有更多。在1小时 (3600 秒) 之后, 此access token将过期, 并且我们使用该access token所做的任何未来 API 调用都将失败.

    在移动应用程序方面,你将检索此 JSON 响应, 解析 API 服务器提供的access token,然后将其存储在本地的安全位置。在 Android 上, 这意味着 SharedPreferences, 在 iOS 上, 这意味着Keychain

    既然已经将access token安全地存储在移动设备上,则可以将其用于向API服务器发出所有后续API请求。

    7. App Makes Authenticated Requests to API Server
    现在要做的就是从您的移动应用程序向您的API服务提出安全的API请求。

    在最后一步中,移动应用获得了OAuth2访问令牌,然后将其存储在本地设备上。

    为了使用此toekn成功发出API请求,您需要创建一个使用此token来标识您的用户的 HTTP Authorization header。

    要做到这一点,你要做的就是将你的access token和Bearer一起插入到HTTP Authorization header中。以下是使用cURL的实例:

    $ curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJEUExSSTVUTEVNMjFTQzNER0xHUjBJOFpYIiwiaXNzIjoiaHR0cHM6Ly9hcGkuc3Rvcm1wYXRoLmNvbS92MS9hcHBsaWNhdGlvbnMvNWpvQVVKdFZONHNkT3dUVVJEc0VDNSIsImlhdCI6MTQwNjY1OTkxMCwiZXhwIjoxNDA2NjYzNTEwLCJzY29wZSI6IiJ9.ypDMDMMCRCtDhWPMMc9l_Q-O-rj5LATalHYa3droYkY" https://api.example.com/v1/test
    

    最终, Authorization header长这样:

    Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJEUExSSTVUTEVNMjFTQzNER0xHUjBJOFpYIiwiaXNzIjoiaHR0cHM6Ly9hcGkuc3Rvcm1wYXRoLmNvbS92MS9hcHBsaWNhdGlvbnMvNWpvQVVKdFZONHNkT3dUVVJEc0VDNSIsImlhdCI6MTQwNjY1OTkxMCwiZXhwIjoxNDA2NjYzNTEwLCJzY29wZSI6IiJ9.ypDMDMMCRCtDhWPMMc9l_Q-O-rj5LATalHYa3droYkY
    

    当你的API服务器收到这个HTTP请求后,会做以下工作:

    1. 检查 HTTP Authorization header的值,是否从Bearer开始。
    2. 获取后面字符串的值,作为token。
    3. 验证这个JWT是否有效,若有效,base64解密后获取用户ID、权限等信息,进行余下的API接口调用。

    (完)

    总结

    这篇文章介绍了当下移动app API调用的认证方法。讲到了一般的HTTP认证方式、OAuth2.0认证以及JWT,对他们的原理都描述的很清楚了,所以翻译出来给大家看。

    唯一觉得有一点会让读者误解的的就是对OAuth2.0中access token和JWT之间的关系的描述。所以作如下梳理:

    • OAuth2.0是一种授权框架,JWT是一种认证协议。
    • 在OAuth2.0中,access token其实就是一个随机串,不同开发语言和不同的安全框架生成的access token均不同。当然也可以像文中提到的使用JWT作为其access token。
    • OAuth2.0通过code,去请求授权服务器时,返回信息如下。返回的数据中包含expires_in,代表过期时间。而JWT生成的token,为点分三段式节后,通过base64解密后,就可以获取到过期时间,而不需要单独返回一个字段。
    {
      "access_token":"2YotnFZFEjr1zCsicMWpAA",
      "token_type":"example",
      "expires_in":3600,
      "example_parameter":"example_value"
    }
    
    • OAuth2.0中的access token完全是随机产生,本身无任何意义,为了进行验证需要将生成的access token和过期时间存数据库。而JWT本身可以携带一些非敏感信息和过期时间,并且本身可以通过base64解密,不需要存数据库。由于这个原因,如上文所说,就将JWT引入OAuth2.0中,作为access token,这样服务器就不需要维护Token存储,资源服务器也不必要求Token检查。
  • 相关阅读:
    417 Pacific Atlantic Water Flow 太平洋大西洋水流
    416 Partition Equal Subset Sum 分割相同子集和
    415 Add Strings 字符串相加
    414 Third Maximum Number 第三大的数
    413 Arithmetic Slices 等差数列划分
    412 Fizz Buzz
    410 Split Array Largest Sum 分割数组的最大值
    409 Longest Palindrome 最长回文串
    day22 collection 模块 (顺便对比queue也学习了一下队列)
    day21 计算器作业
  • 原文地址:https://www.cnblogs.com/Sinte-Beuve/p/7795438.html
Copyright © 2011-2022 走看看