您的基本结构是正确的,但是使用 OAuth2,您将永远不会永远存储访问令牌。访问令牌通常是一个不透明的字符串,它授予对您的 API 的访问权限,将其存储在 cookie 或本地存储中很好,但是从服务器发出永不过期的令牌是非常不可取的(MITM 攻击可能会永远劫持您的身份) .
为解决此问题,OAuth2 实施通常会在访问令牌旁边分配刷新令牌。刷新令牌通常比访问令牌具有更长的到期时间(在访问令牌的到期时间和我会说的一个月之间的任何时间)。刷新令牌类似于临时用户密码 - 它们不会直接授予对您 API 的任何访问权限,但是用户可以通过调用您的 OAuth2 刷新 api 向您的系统授权,并获取新的访问权限并使用新的到期时间刷新令牌.这使您的应用程序有机会定期重新验证用户声明(可能他们的访问权限/角色已更改,他们需要更新声明)。
JWT 令牌
访问令牌可能是您存储在服务器上的不透明字符串,但我强烈建议您使用 JWT 令牌。与不透明(无意义)令牌相比,JWT 令牌有 2 个主要优点:
1.客户索赔
在您的客户端应用程序授权后,您需要做的第一件事是查找各种内容来构建您的 UI。 JWT 令牌的美妙之处在于,它们将您的所有用户声明(包括您的应用程序自定义用户声明)作为 JSON 对象有效负载存储在一个编码字符串中,可以通过首先在 . 上拆分令牌来在客户端解码,这会破坏将其转换为[ header, payload, sig ] base 64 编码字符串。然后,您可以对负载字符串进行 base 64 解码并通过 JSON.parse 运行它,这将生成您的声明键值对:
const access_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ'
const claims = JSON.parse(atob(access_token.split('.')[1]))
console.info(claims)
这允许您的客户端应用程序从访问令牌中原子地解码用户声明,而不是使用访问令牌去查找有关用户的信息的传统模型。使用 JWT,您将立即知道用户的名字、姓氏、用户 ID 以及您希望粘贴在 JWT 令牌中的任何其他数据。
2。无会话
要使用您的授权 API,您的客户端应用程序将向端点发出请求并发送 'Authorization': 'Bearer access_token'(其中 access_token 是您的访问令牌)。在传统应用程序中,必须在服务器端查找访问令牌以验证服务器是否授予它。关于 JWT 令牌的另一个很棒的一点是,当它们被发布时,有一个服务器端的秘密用于对它们进行签名。当服务器需要验证它们时,它只需使用服务器端的密钥对其进行签名,如果通过,服务器将根据解码令牌的声明授予 API 请求。无需将它们存储在服务器端,从而使您的架构更加简单。服务器无需针对每个授权的 API 请求与数据库进行通信。您将绕过许多问题,例如跨网络场同步访问令牌或完全存储它们(但您仍需要将刷新令牌存储在链接到用户的表中)。
Cookie 与本地存储
人们对 cookie 的一个很常见的误解是应该避免它们,因为它们会让您面临CSRF 攻击,但通常情况并非如此。发送会话 cookie 和基于这些 cookie 水合会话的 Web 表单等系统对 CSRF 攻击是开放的,但如果您正在构建单页面应用程序并且所有安全检查点都在您的 API 层,那么您的端点将检查Authorization您的不记名令牌的标头,而不是 cookie。如果您正在执行服务器端渲染并在那里使用 cookie 值,您应该了解 CSRF 威胁并实施预防方法。如果您使用 cookie,您应该确保它们不设置了 HttpOnly 标志并且确实设置了 Secure 标志以保护您免受 MITM 威胁。
由于您使用的是 node(或至少是 angular),我现在将插入一个我编写的库 - jwt-autorefresh。这个库的要点很简单,给它你的应用程序刷新机制(向你的刷新 api 发出 http 请求并随后将结果存储在 cookie 中的客户端代码)以及你想让令牌刷新之前的秒数它们的到期时间,它将处理您的客户端应用程序上的自动调度刷新。在内部,它会解码您的 JWT 令牌并查看其 exp 声明(到期时间)以确定它需要多长时间才能安排您的刷新。它具有诸如在刷新时间中添加少量抖动的功能,以便所有客户端实例不会同时尝试刷新。