为 RESTful API 构建身份验证和授权的步骤
封面图片由 Robert Björkén 在 Flickr 上提供。
身份验证和授权
构建任何 RESTful API 的挑战之一是制定周密的身份验证和授权策略。身份验证、安全和日志记录等交叉关注点始终具有挑战性,并且涉及众多利益相关者。
为了明确定义,通常会一起讨论两个单独的操作:
验证:
涉及验证用户身份。这可能涉及检查用户名/密码,或检查令牌是否已签名且未过期。身份验证并不意味着此人可以访问特定资源。
授权:
涉及检查用户通过定义的角色或声明有权访问或修改的资源。例如,经过身份验证的用户被授权读取数据库,但无权修改数据库。同样的道理也适用于您的 API。也许大多数用户可以访问某些资源或端点,但特殊的管理员用户拥有特权访问权限。
当然,这些定义通常是一起实现的并且相互依赖,所以当我们提到 auth 时,我们指的是整个系统。
定义实际的令牌
创建身份验证策略时,首先要考虑的是要使用哪种类型的令牌。令牌有很多种,但最常见的两种是:
1. JWT 令牌(JSON Web 令牌)
JWT 令牌实际上是一个完整的 JSON 对象,经过 base64 编码,然后使用对称共享密钥或公钥/私钥对进行签名。区别在于,如果您有一个用户需要验证令牌是否已签名,但不允许该用户创建令牌,则可以将公钥提供给该用户,该用户虽然无法创建令牌,但仍然可以验证令牌。我将另开一篇文章来详细解释,但如果您还记得密码学课程的内容,非对称加密和签名算法会创建一对数学上相关的密钥。
JWT 可以包含以下信息:主体 (subject) 或 user_id、令牌的签发时间以及令牌的过期时间。通过使用密钥签名,您可以确保只有您可以生成令牌,从而防止其被篡改(例如修改 user_id 或令牌的过期时间)。但需要注意的是,虽然 JWT 是签名的,但 JWT 通常不加密(尽管您可以选择对其进行加密)。这意味着任何有权访问令牌的人都可以读取令牌中的任何数据。在令牌中放置标识符(例如 user_id)是一种很好的做法,但不要放置个人身份信息(例如电子邮件或社会安全号码)。您可以使用JWT.io
之类的工具轻松地对 JSON 数据进行 base64 解码和查看。
为什么 JWT 如此优秀?
JWT 的优点之一是无需后端存储即可使用。所有用户身份验证所需的信息都包含在令牌本身中。在分布式微服务领域,它可以轻松地摆脱对集中式身份验证服务器和数据库的依赖。单个微服务只需要一些中间件来处理令牌验证(JWT 库已公开,适用于从 Express 到 JVM MVC 框架的所有框架)以及验证所需的密钥。验证包括检查签名和一些参数,例如声明和令牌的到期时间。JWT 通常是中等生命周期的令牌,其到期日期可能设置为几周到更长。
验证令牌是否正确签名仅需要 CPU 周期,不需要 IO 或网络访问,并且在现代 Web 服务器硬件上非常容易扩展。
如果 JWT 如此出色,为什么不是每个人都使用它们?
JWT 的缺点之一是,如果您需要立即执行操作,则禁止用户或添加/删除角色会有些困难。请记住,JWT 具有预定义的到期日期,可能设置为未来一周。由于令牌存储在客户端,因此即使在数据库中将用户标记为已禁用,也无法直接使令牌失效。相反,您必须等到它过期。这可能会影响您的架构,尤其是在设计可能被一个高级用户饿死的公共 API 或需要禁止欺诈用户的电子商务应用程序时。有一些解决方法,例如,如果您只关心禁止受损的令牌或用户,则可以拥有令牌或 user_id 的黑名单,但这可能会将数据库重新引入您的身份验证框架。推荐的黑名单方法是确保每个令牌都有一个jti声明(或者一个可以存储在数据库中的 JWT ID)。假设您想要失效的令牌数量远小于应用程序中的用户数量,这种方法很容易扩展。您甚至可以将其本地缓存在与 API 代码相同的进程中,从而消除对数据库服务器可靠性(99.9 ...
另一方面,如果您的企业应用包含多个角色,例如管理员、项目负责人、服务帐户经理,并且您希望修改后的效果立即生效,那么开发可能会很棘手。尤其是当管理员正在修改其他人的授权角色(例如其直接下属)时。因此,被修改的用户甚至在不刷新 JWT 的情况下都不知道其角色已更改。
第二个缺点是,随着字段数量的增加,令牌可能会变大。在无状态应用中,几乎每个请求都会发送令牌,因此这可能会影响数据流量的大小。
例如,我们之前提到的企业应用可能包含多个角色,这可能会增加令牌的臃肿程度,并使令牌存储内容的复杂性增加。想象一下设计支持 AWS 或 Azure Web 门户的 API。你为每个用户分配了针对每个资源的权限。例如,允许一个用户查看 S3 帐户,但不能修改 EC2 实例。这种复杂性可能超出了 JWT 的处理能力。
在智能手机用户担心客户端延迟和数据使用的移动应用中,JWT 可能会在每个请求中添加过多的有效负载。
2. 不透明代币
既然我们讨论了 JWT 的优缺点,现在让我们来探讨一下第二种选择:不透明令牌。不透明令牌顾名思义,它不是将用户身份和声明存储在令牌中,而是简单地用一个主键来引用包含数据的数据库条目。像 Redis 这样的快速键值存储系统非常适合利用内存哈希表进行 O(1) 负载查找。由于角色是直接从数据库读取的,因此可以更改角色,并且更改一旦通过后端传播,用户就会立即看到新的角色。
当然,维护键值存储和身份验证服务器会增加复杂性。根据您的架构,每个服务都必须与身份验证服务器握手才能获取声明或角色。
3. 混合
目前尚未讨论的一点是两种方案的混合。您可以通过 JWT 处理身份验证,例如检查用户是否是其声称的身份。另一方面,特定资源的授权不属于 JWT 的一部分。换句话说,JWT 只处理身份验证,而不处理授权。
Cookies、Headers 和 URL 参数
URL 参数
首先,我们绝不建议在 URL 中放置令牌。URL 会被添加到书签、分享给好友、发布到网上等等。
用户很容易复制自己喜欢的页面 URL 并转发给好友。这样一来,好友就具备了
代表该用户登录所需的所有身份验证条件。此外,还存在其他一些问题,例如大多数记录器至少会以纯文本形式记录 URL。
因此,有人打开 Chrome 开发者工具并复制其 Cookie 或 HTTP 标头的可能性要小得多 ;)
因此,我们最终只能选择 Cookies 和 HTTP 标头。
如果您专注于创建 RESTful API,那么 Cookie 实际上只是另一种标头,就像授权标头一样。实际上,您可以将通过授权标头发送的 JWT 包装在 Cookie 中。实际上,许多设计用于供他人使用的纯 RESTful API 只使用标准或自定义授权标头,因为它更加明确。也可以混合使用,例如,Web 应用可以使用 Cookies 与代理后面的 RESTful API 通信。代理会在中继请求时提取 Cookie 并添加相应的标头。真正的原因将在下一节中阐述:
真正的争论:Cookie 与本地存储:
虽然 Cookie 实际上可能只是包装了 JWT 或不透明令牌,但客户端 Web 应用仍然需要将令牌存储在某个地方。大多数人想到的两个选择是 Cookie 和 HTML5 本地存储。因此,有时当人们提到 Cookie 与 HTTP 标头时,他们实际上是在问“Cookie 与本地存储?” 两者各有优缺点,也存在安全风险。
曲奇饼
Cookie 的优点在于它具有某些标志,可以设置这些标志来强制执行安全检查,例如 HTTP Only 和 Secure。通过设置 HTTP Only 和 Secure 标志,任何 JavaScript 代码都无法读取 Cookie,也无法通过 HTTP 以纯文本形式发送 Cookie。因此,Cookie 可以免受本地存储部分所述的XSS攻击。Cookie 还容易受到另一种称为跨站请求伪造 (XSRF 或 CSRF)的攻击。XSRF 意味着其他站点的黑客可以复制您站点上的某些输入表单,并将表单数据 POST 到您自己的站点。虽然黑客无法访问 Cookie 本身,但 Cookie 会随着每次向您的真正域名发出的 HTTP 请求(前提是该 Cookie 有效)一起传输。因此,黑客无需读取 Cookie,只需成功将表单数据 POST 到您的真实站点即可。这是 Cookie 的危险之一。每次请求(包括静态请求、AJAX 请求等)都会发送 Cookie。
有一些方法可以解决这个问题,但基本原则是您的 Web 服务器需要识别请求是来自您在浏览器中运行的真实网站还是其他网站。一种方法是使用隐藏的防伪令牌。一种方法是生成一个特殊的随机密钥并将其存储在 Cookie 中,该密钥也需要与 POST 的表单数据一起发送。请记住,只有您的真实网站可以访问该 Cookie,而黑客网站由于同源策略而无法访问。然后,您的服务器可以验证 Cookie 中的令牌是否与表单数据中的令牌匹配。还有其他方法可以防止 XSRF。
本地存储
本地存储的安全风险在于 JavaScript 可能遭受跨脚本攻击 (XSS)。早期,XSS 是由于未转义用户输入造成的,但现在您的现代 Web 应用可能会导入大量 JS 库,从分析和归因跟踪到广告和小型 UI 元素。本地存储对您的网站域是全局的。因此,您网站上的任何 JavaScript,无论是否是第三方库,都可以访问相同的本地存储。您的应用内没有沙盒。例如,您的分析库与您自己的应用程序代码从相同的本地存储中读取和写入。虽然 GA 可能没问题,但您是否审核过您添加的快速 UI 元素?过去,即使网站本身通过 HTTPS 保护,如果 JavaScript 以纯文本形式进行 AJAX 调用也会引起担忧。现在浏览器开始强制检查混合内容,这种担忧比以前有所减少。如果浏览器较旧或未强制执行,仍然需要注意这一点。
本地存储的第二个缺点是您无法跨多个子域名访问它。如果您有单独的博客子域名或电子邮件子域名,这些网站将无法读取本地存储。如果您不打算跨多个域名登录(例如 mail.google.com 和 google.com),那么这可能没问题。
谢谢
我们感谢那些尝试 Moesif 的精选测试客户,他们为我们提供了宝贵的反馈,使调试变得更加容易。
本文由 Moesif 创始人兼首席执行官Derric Gilling为Moesif 博客撰写。
文章来源:https://dev.to/moesif/steps-to-building-authentication-and-authorization-for-restful-apis-2adl