【问题标题】:JWT Token strategy for frontend and backend前端和后端的 JWT Token 策略
【发布时间】:2016-08-13 13:47:38
【问题描述】:

我正在编写一个应用程序,前端在 emberjs 中,后端/服务器端在 nodejs 服务器中。我配置了 emberjs,以便用户可以使用第 3 方 Oauth(谷歌、推特、Facebook)登录/注册。我有一个用 express nodejs 服务器编写的后端,它托管 RESTful API。

我没有连接到 emberjs 的数据库,而且我认为无论如何我都不应该这样做,因为它是严格的客户端代码。我打算使用 JWT 在客户端和服务器端之间进行通信。当用户使用他们的 oauth 凭据登录时,我会从提供者那里得到一个 JSON 对象,其中包含 uid、名称、登录名、access_token 和其他详细信息。

我正在努力选择如何处理用户注册的策略。因为它是 OAuth,所以没有注册过程。所以流程是如果用户不在我的数据库中,请创建它。我不支持电子邮件/密码验证。当用户第一次使用 OAuth 提供者登录时,流程是怎样的? emberjs 是否应该在每次登录时将所有详细信息发送到后端,以便后端可以将新用户添加到数据库?

什么应该是我的 JWT 主体的一部分?我在想 uid 和 provider 提供的访问令牌。我在这里能想到的一个问题是提供商特定的访问令牌可以更改。用户可以从提供商的站点撤销令牌并使用 emberjs 重新注册。

如果它更容易,我愿意在任何其他 javascript 客户端框架中编写前端。

【问题讨论】:

  • 你是在描述如何做到这一点还是所有的代码?请注意,ember 和 node 非常适合这一点,客户端和后端的技术堆栈应该不会对解决方案产生重大影响。
  • 我正在寻找一个关于不同组件在什么阶段应该如何通信的流程。我不需要代码

标签: node.js api rest ember.js jwt


【解决方案1】:

如果我们谈论的不仅是工作,而且是安全的无状态身份验证,您将需要考虑使用 accessrefresh 令牌的适当策略。

  1. 访问令牌是提供对受保护资源的访问权限的令牌。 Expiration 这里可能会在大约 1 小时内安装完毕(取决于您的考虑)。

  2. 刷新令牌是一个特殊令牌,当它过期或用户会话已更新时,应该使用它来生成额外的access token。显然,您需要让它长寿(与access token 相比)并尽可能安全。 Expiration 这里可能会在大约 10 天或更长时间内安装(也取决于您的考虑)。

仅供参考:由于refresh tokens 的寿命很长,为了使它们真正安全,您可能希望将它们存储在数据库中(很少执行刷新令牌请求)。这样,假设即使您的刷新令牌以某种方式被黑客入侵并且有人重新生成了access/refresh 令牌,您当然会失去权限,但是您仍然可以登录系统,因为您知道登录/通过(如果您稍后将使用它们)或仅通过任何社交网络登录。


在哪里存储这些令牌?

基本有2个常见的地方:

  1. HTML5 网络存储 (localStorage/sessionStorage)

很好,但同时也有足够的风险。存储可通过同一域上的 javascript 代码访问。这意味着如果你有XSS,你的令牌可能会被黑客入侵。因此,通过选择这种方法,您必须小心并编码/转义所有不受信任的数据。即使你这样做了,我也很确定你使用了一些 3rd-party 客户端模块,并且不能保证它们中的任何一个都有一些恶意代码。

此外,Web Storage 在传输过程中不强制执行任何安全标准。所以你需要确保 JWT 是通过 HTTPS 发送的,而不是通过 HTTP 发送的。

  1. Cookie

带有特定 HttpOnly 选项的 cookie 无法通过 javascript 访问,并且不受 XSS 影响。您还可以设置 Secure cookie 标志以保证 cookie 仅通过 HTTPS 发送。 但是,cookie 容易受到不同类型的攻击:跨站点请求伪造 (CSRF)。 在这种情况下,CSRF 可以通过使用某种同步令牌模式来防止。在AngularJSSecurity Considerations 部分有很好的实现。

您可能想要关注的article

为了说明它的一般工作原理:


关于 JWT 本身的几句话:

为了清楚起见,来自 Auth0 的家伙 JWT Debugger 真的很酷。 有 2 种(有时 3 种)常见的声明类型:publicprivate(和 reserved)。

JWT 正文的示例(有效负载,可以是任何你想要的):

{     
  name: "Dave Doe",
  isAdmin: true,
  providerToken: '...' // should be verified then separately
}

有关JWT 结构的更多信息,您将找到here

【讨论】:

    【解决方案2】:

    回答您提出的两个具体问题:

    当用户使用 OAuth 提供程序登录时,流程是怎样的? 第一次? emberjs 是否应该将所有详细信息发送到后端 每次登录以便后端可以向数据库添加新用户?

    当用户通过 oauth 注册或登录并且您的客户端收到新的访问令牌时,我会将其 upsert(更新或插入)到您的用户表(或集合)中您从 oauth 提供程序 API 检索到的有关用户的任何新信息或更新信息。我建议将其直接存储在每个用户记录中,以确保访问令牌和相关的个人资料信息自动更改。一般来说,我通常会将其组合成某种中间件,当出现新令牌时,该中间件会自动执行这些步骤。

    什么应该是我的 JWT 主体的一部分?我在想 uid 和 provider 提供的访问令牌。我在这里能想到的一个问题是提供者 特定的访问令牌可以更改。用户可以从 提供者的网站并使用 emberjs 再次注册。

    JWT 正文通常由用户声明组成。我个人认为将提供者访问令牌存储在 JWT 令牌的主体中几乎没有什么好处,因为它对您的客户端应用程序几乎没有好处(除非您正在从客户端对其 API 进行大量直接 API 调用,我更喜欢这样做这些调用服务器端并将我的应用程序客户端发送回符合我自己的界面的规范化声明集)。通过编写自己的声明界面,您将不必解决来自客户端应用程序的多个提供商的各种差异。这方面的一个示例是将在其 API 中以不同名称命名的 Twitter 和 Facebook 特定字段与您存储在用户配置文件表中的公共字段合并,然后将您的本地配置文件字段作为声明嵌入到您的 JWT 正文中,以由您的客户端应用程序解释.这样做还有一个额外的好处,就是您不会在未加密的 JWT 令牌中保留任何将来可能泄漏的数据。

    无论您是否将 oauth 提供者提供的访问令牌存储在 JWT 令牌正文中,每次配置文件数据更改时,您都需要授予新的 JWT 令牌(您可以设置一种机制来绕过颁发新的 JWT 令牌,如果没有发生配置文件更新,之前的令牌仍然有效)。

    除了您在 JWT 令牌正文中存储为声明的任何配置文件字段之外,我总是会定义标准 JWT token body fields 的:

    {
        iss: "https://YOUR_NAMESPACE",
        sub: "{connection}|{user_id}",
        aud: "YOUR_CLIENT_ID",
        exp: 1372674336,
        iat: 1372638336
    }
    

    【讨论】:

      【解决方案3】:

      对于任何 OAuth 工作流程,您绝对应该使用 passportjs 库。您还应该阅读完整的文档。这很容易理解,但我犯了第一次没有阅读整本书的错误并且很挣扎。它包含具有 300 多个提供者和颁发令牌的 OAuth 身份验证。

      不过,如果您想手动操作或想要基本了解,我会使用以下流程:

      1. 前端有一个登录页面,列出了使用 Google/Facebook 等登录,其中实现了 OAuth。

      2. 成功的 OAuth 会产生一个 uid、登录名、access_token 等(JSON 对象)

      3. 您将 JSON 对象发布到 Node.js 应用程序中的 /login/ 路由。 (是的,无论是新用户还是现有用户,您都会发送整个响应。在此处发送额外数据比发送两个请求要好)

      4. 后端应用程序读取uidaccess_token。通过关注 (https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken) 或使用访问令牌向提供者请求用户数据,确保 access_token 有效。 (由于 OAuth 访问令牌是基于每个应用程序/开发人员生成的,因此无效的访问令牌将失败)现在,搜索您的后端数据库。

      5. 如果uid 存在于数据库中,则更新数据库中用户的 access_token 和 expiresIn。 (access_token 允许您从 Facebook 获取该特定用户的更多信息,它通常提供几个小时的访问权限。)

      6. 否则,您使用 uid、登录等信息创建一个新用户。

      7. 更新 access_token 或创建新用户后,您发送包含 uid 的 JWT 令牌。 (对 jwt 进行加密,这样可以确保它是您发送的并且没有被篡改。结帐https://github.com/auth0/express-jwt

      8. 在前端用户收到/login的jwt后,通过sessionStorage.setItem('jwt', token);将其保存到sessionStorage

      9. 在前端,还添加以下内容:

      if ($window.sessionStorage.token) { xhr.setRequestHeader("Authorization", $window.sessionStorage.token); }

      这将确保如果有 jwt 令牌,它会随每个请求一起发送。

      1. 在您的 Node.js app.js 文件中,添加

      app.use(jwt({ secret: 'shhhhhhared-secret'}).unless({path: ['/login']}));

      这将验证路径中任何内容的 jwt,确保用户已登录,否则不允许访问并重定向到登录页面。这里的例外情况是 /login,因为这是您为新用户或未经身份验证的用户提供 JWT 的地方。

      您可以在 Github URL 上找到有关如何获取令牌以及找出您当前正在处理的用户请求的更多信息。

      【讨论】:

      • 如何在后端保护您的 /login 端点,以便只允许客户端发帖而不允许任何人发帖?
      • 您使用 CORS。在你的后端添加Access-Control-Allow-Origin: http://www.foo.com,这样只有Origin: http://www.foo.com可以发送POST数据到/login
      • 但是,请记住,这可能会被欺骗。我更新了第 4 步以获取相关信息。
      • @RahatMahbub 你怎么看:github.com/gkatsanos/boilerplate-server。我也做电子邮件验证。 (仅使用用户/密码 => JWT,没有 Facebook/Google oAuth)(没有前端代码,只有 API/服务器)
      • PassportJS 有时设置起来太麻烦了。通常它不够灵活,无法在前端生成正确的错误输出。 PassportJS 远非完美的解决方案。
      猜你喜欢
      • 2022-10-22
      • 1970-01-01
      • 2015-04-28
      • 2019-09-04
      • 2021-10-16
      • 2019-10-15
      • 2019-06-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多