AI聊天无敏感词:技术原理与应用实践
为 RESTful API 构建身份验证和授权的步骤
身份验证和授权
构建任何 RESTful API 的挑战之一是制定深思熟虑的身份验证和授权策略。身份验证、安全性和日志记录等交叉问题始终具有挑战性,并涉及许多利益相关者。
为了明确定义,通常会一起讨论两个单独的行动:
验证:
涉及验证此人的身份。这可能涉及检查用户名/密码或检查令牌是否已签名且未过期。身份验证并不表示此人可以访问特定资源。
授权:
涉及检查用户通过定义的角色或声明有权访问或修改的资源。例如,经过身份验证的用户有权读取数据库,但不允许修改数据库。这同样适用于您的 API。也许大多数用户可以访问某些资源或端点,但特殊的管理员用户拥有特权访问权限。
当然,这些定义通常是一起实现且相互依赖的,所以当我们提到 auth 时,我们指的是整个系统。
定义实际的 token
创建身份验证策略时,首先要考虑的事情之一是要使用哪种类型的令牌。有多种方法,但最常见的两种是:
1. JWT 令牌(JSON Web 令牌)
JWT 令牌实际上是一个完整的 JSON 对象,它经过 base64 编码,然后使用对称共享密钥或公钥/私钥对进行签名。不同之处在于,如果您有一个需要验证令牌是否已签名的消费者,但不应允许该消费者创建令牌,则可以向该消费者提供公钥,该公钥不能创建令牌但仍可以验证它们。我将在另一篇文章中介绍详细信息,但如果您还记得密码学课程,非对称加密和签名算法会创建一对数学相关的密钥。
JWT 可以包含以下信息,包括主题或 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声明(或可以存储在 Db 中的 JWT Id)。假设您想要使令牌失效的令牌数量比应用程序中的用户数量少得多,那么这种方法可能很容易扩展。您甚至可以在与 API 代码相同的进程中本地缓存它,从而消除对数据库服务器可靠性的依赖,从而实现多个 9。
另一方面,如果您的企业应用包含许多角色,例如管理员、项目所有者、服务帐户经理,并且您希望效果立即生效,那么开发可能会很棘手。特别是,想象一下管理员正在修改其他人的授权角色(例如他/她的直接报告)的情况。因此,修改后的用户甚至不知道他/她的角色已经更改,而无需刷新 JWT。
第二个缺点是,随着字段的增加,令牌可能会变大。在无状态应用中,几乎每个请求都会发送令牌,因此可能会影响数据流量大小。例如,我们之前提到的企业应用可能有许多角色,这可能会增加令牌中存储内容的臃肿和复杂性。想想设计支持 AWS 或 Azure Web 门户的 API。您已为每个用户设定了针对每个资源的权限。例如,因此允许一个用户查看 S3 帐户,但不能修改 EC2 实例。这种复杂性可能超出了 JWT 的处理能力。
在移动应用中,智能手机用户担心客户端延迟和数据使用情况,JWT 可能会在每个请求中添加过多的有效负载。
2. 不透明代币
既然我们讨论了 JWT 的优点和缺点,让我们来探索第二个选项,即不透明令牌。不透明令牌的字面意思就是它的名字。不透明令牌不是将用户身份和声明存储在令牌中,而是只是一个引用包含数据的数据库条目的主键。像 Redis 这样的快速键值存储非常适合利用内存哈希表进行 O(1) 有效负载查找。由于角色是直接从数据库读取的,因此可以更改角色,并且只要更改通过后端传播,用户就会看到新角色。
当然,维护 K/V 存储和身份验证服务器会增加复杂性。根据您的架构,每个服务都必须与身份验证服务器握手以获取声明或角色。
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 与本地存储?”每种方式都有好处和安全风险。
曲奇饼
Cookies 的优点在于它们具有某些标志,可以设置这些标志来强制执行安全检查,例如 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 请求等。有办法解决这个问题,但基本原则是您的 Web 服务器需要识别请求是来自在浏览器中运行的真实网站还是来自其他人。一种方法是使用隐藏的防伪造令牌。一种方法是生成并存储一个特殊的随机密钥在 cookie 中,该密钥也需要与 POST 表单数据一起发送。请记住,只有您的真实网站可以访问cookie,而黑客网站由于同源策略而无法访问。然后,您的服务器可以验证 cookie 的令牌是否与表单数据中的令牌匹配。还有其他选项可以保护 XSRF。
本地存储
本地存储的一个安全风险是 Javascript 可能遭受跨脚本攻击 (XSS)。早期,XSS 是由于未转义用户输入而导致的,但现在您的现代 Web 应用程序可能导入了大量 JS 库,从分析和归因跟踪到广告和小型 UI 元素。本地存储对您的网站域是全局的。因此,您网站上的任何 javascript(无论是否是第三方库)都可以访问相同的本地存储。您的应用程序内没有沙盒。例如,您的分析库从与您自己的应用程序代码相同的本地存储中读取和写入。虽然 GA 可能没问题,但您是否审核过您添加的快速 UI 元素?过去,即使网站本身通过 HTTPS 保护,如果 javascript 以纯文本形式进行 AJAX 调用也会引起担忧。现在浏览器开始强制检查混合内容,这种担忧比以前少了。如果浏览器较旧或未强制执行,仍需注意这一点。
本地存储的第二个缺点是您无法跨多个子域访问它。如果您有单独的博客子域或电子邮件子域,这些网站将无法读取本地存储。如果您不打算跨多个域登录,这可能没问题(想想 mail.google.com 和 google.com)。
文章来源:Steps to building authentication and authorization for RESTful APIs