保护多租户、多数据库 REST API

信息安全 oauth 休息 hmac json
2021-08-30 21:47:37

我希望提高通过 SSL 访问的现有 REST API 的安全性。Web 服务是多租户的,因此每个租户都有一个分配的 TenantId。

我面临的问题可以概括为:

  1. 如何确定租户?
  2. 如何确定客户端是否合法?
  3. 基于 HTTP cookie 的安全性是否适合该任务,还是我应该考虑基于令牌?

目前

目前,我们手动向每个客户端发出(即进程外 - 通过电话等)API 密钥,它们作为 HTTP 标头包含在每个请求中。此 API 密钥映射到 TenantId。当客户端向 REST API 发送登录请求时,我们会确定 TenantId,这反过来又允许我们检查正确租户数据库中的用户名/密码。一旦成功,我们就会发出一个有时间限制的 HTTP cookie。然后,该 cookie 将用于后续的 REST 请求。在内部,该 cookie 链接到会话,并且会话包含用户配置文件信息以减少数据库负载。

我们知道这有一些固有的安全风险。API 密钥可以很容易地从反汇编的客户端源代码中提取。我们还打算将我们自己的客户端构建为 JavaScript 中的 SPA,任何人都可以阅读。虽然我们可以撤销/更改密钥,但我正在寻找更安全的标准化实现。

选择

据我了解,我可以使用基于令牌的cookie 替代方案。HMAC 是 Web 应用程序的常见身份验证机制,但这需要在客户端和服务器中存储共享密钥。这个秘密似乎和上面的 API 密钥有同样的问题;因为它可以被泄露。

我还阅读了一些关于 JWT 的内容,它似乎扩展了 HMAC 概念,因为服务器可以将用户“会话”数据保存在令牌中,从而减少了对用户/配置文件信息的数据库调用次数。JWT 令牌用作用户名/密码登录成功的结果。因此这里没有共享秘密问题对吗?

我还阅读了一些关于 OAuth2 的内容,特别是Resource Owner Password Credentials Grant我什至不确定 OAuth2 是否能解决我的问题。

确定租户

第一步是确定租户。除了使用 API 密钥映射到 TenantId 之外,我还可以:

  1. 在登录请求中请求 TenantId
  2. 使用子域映射映射到 TenantId

验证应用程序调用者

其次,我需要确定是否允许给定的客户端应用程序访问 REST API。在这方面,我完全没有好主意。这就是 OAuth2 来救援的地方吗?

我能想到解决这个问题的唯一方法是在用户登录后向调用应用程序发出一个临时过期的 API 密钥。虽然它不能保证应用程序不是恶意的,但它确实降低了攻击者的风险谁还获得了对用户凭据的访问权限。

我当前的 HTTP cookie 解决方案是否可以接受?我是否需要改用基于令牌的身份验证机制?

我欢迎您提出任何建议。我正在浏览大量信息,并试图弄清楚这一切。任何指导都会很高兴收到!

3个回答

您应该查看 oauth 2.0 (RFC 6749),它有许多可以满足您要求的流程。

首先,您需要知道客户是公开的还是机密的。如果客户端是 Web 浏览器或下载到移动设备的应用程序,因为无法维护机密,那么像您一样发布客户端 API 密钥是没有意义的。如果它们是服务器,那么 api 密钥很好。所以这是你的首要考虑。rfc 的第 2.1 节处理客户端类型,这将确定哪些流程适用。

您需要将客户端服务的标识与客户端所代表的用户分开。您的后端数据库应该有用户与租户的关联,并且如果您想将范围限定为单个租户,则可能有 api 密钥与用户或用户+租户。我已经实现了用户可以参与多个租户的服务,因此这可能适用于您的案例,也可能不适用于您的案例。我已成功使用资源所有者密码凭据授予请求,以便用户可以从 Web 浏览器提供他们的用户名和密码。听起来您使用了带外方法来授权客户端,因此不需要用户凭据。但是,如果客户端是 Web 浏览器或移动应用程序,则您不能依赖客户端身份验证,并且必须从用户那里获得授权,这意味着提供用户凭据。

无论您使用哪种流程,最终都会得到如下响应:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"example",
    "expires_in":3600,
    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    "example_parameter":"example_value"
} 

返回的访问令牌是客户端在其请求中使用的令牌,以便它可以向服务器证明其请求已获得授权。

Authorization 标头有两个选项,“bearer”或“mac”。

Authorization: Bearer xxxxxx

如果您的客户端使用 TLS,那么不记名令牌是前进的简单方法。有关详细信息,请参阅 RFC 的第 7.1 节。

最后,由于访问令牌只是临时的,一旦访问令牌接近或超过其到期时间,您的客户端可以使用刷新令牌来请求新的访问令牌。是否要使用刷新令牌取决于您。客户需要安全地存储它们,至少与他们用于 api 密钥的保护级别相同。如果访问生命周期足够且不需要用户身份验证,则在您的系统中,服务器-服务器交互可能不需要刷新令牌。如果需要用户身份验证,那么如果客户端存储提供了足够的保护级别,那么刷新令牌可能会很有用。

我认为就如何识别租户而言,您目前所拥有的一切都很好——每个客户端都需要某种标识符,API 密钥与任何标识符一样好。鉴于它都是在 SSL 上运行的,你真的不需要担心它会被暴露(至少在传输过程中)。

您似乎关心API 密钥的客户端存储,但最终这不是您的工作。客户有责任确保其私人信息的安全(确保您的 PIN 安全不是您银行的工作)。您的工作是保护服务器和您的客户端免受滥用。一种方法是能够撤销/阻止 API 密钥和/或会话。

cookie 与 HMAC 的争论可能没有你想象的那么重要。问问自己,如果您从 cookie 切换到令牌,会发生什么变化?HMAC 肯定更安全,因为它已签名,但是,您可以轻松地加密 cookie 的内容。最终,它仍然会给您带来与 cookie 一样的存储难题。需要关注的重要部分是,如果有人设法访问它,他们会造成多大的损害?这会让您思考 A. cookie/令牌需要多么安全,以及 B. 如果服务器/客户端落入恶意手中,您如何保护它。

如果您还需要处理客户端授权,那么您可能应该查看 OAuth,因为这正是它的设计目的。

另一种方法是使用公私钥。

  1. 客户端应用程序(例如 angularjs)将拥有服务器的公钥(例如 web api)

  2. 使用服务器的公钥,客户端需要加密用户的凭证(例如用户名、散列密码等)。生成的令牌将被发送到服务器;通过标题或查询

  3. 然后服务器将解密令牌,获取用户名和哈希密码。然后从数据库中获取持久的用户名+哈希密码。身份验证是通过检查两个散列密码是否匹配(一个来自令牌,一个来自数据库)。

在此替代方案中,用户的密码不会以纯文本形式发送。它在发送之前被加密/散列。客户端可以将该令牌存储到本地存储(浏览器)或每次调用服务器时生成新令牌。