【问题标题】:Placing Guardian tokens inside cookies and reading them from there将 Guardian 令牌放在 cookie 中并从那里读取它们
【发布时间】:2017-08-05 09:57:58
【问题描述】:

我有一个使用 Guardian 的 JWT 身份验证的应用程序。当用户登录时,响应在正文中包含 jwt。然后前端(它是一个 SPA)将该 jwt 存储在 localStorage 中,并将其附加到从那里发送的每个请求的 Authorization 标头。然后服务器使用 Guardian 的内置验证插件对此进行验证:

pipeline :api do
  plug :accepts, ["json"]
  plug Guardian.Plug.VerifyHeader, realm: "Bearer"
end

我想更改此设置,以便服务器不将 JWT 存储在 localStorage 中(这不安全),而是将它们作为安全 cookie 发送到前端(使用 SecureHttpOnly 设置) .然后我希望 Guardian 从 cookie 中读取 jwt,而不是从 Authorization 标头中读取。

Guardian 是否支持此功能?

这是我的 SessionController create 函数:

def create(conn, params) do
  case authenticate(params) do
    {:ok, user} ->
      new_conn = Guardian.Plug.api_sign_in(conn, user, :access)
      jwt = Guardian.Plug.current_token(new_conn)

      new_conn
      |> put_status(:created)
      |> render("show.json", user: user, jwt: jwt)
    :error ->
      conn
      |> put_status(:unauthorized)
      |> render("error.json")
  end
end

【问题讨论】:

  • 一个 JWT 是按定义签名的。为什么本地存储不如 cookie 存储安全??
  • 因为 localStorage 可以用 JavaScript 读取。这意味着如果将恶意 JavaScript 注入页面(例如在 XSS 攻击中),jwt 可能会被窃取并用于访问应用程序。相比之下,HttpOnly cookie 只能由浏览器访问。
  • 并且cookies受CSRF约束,本地存储不受。你在抢劫彼得来付钱给保罗。
  • 请阅读我上面提供的链接。具体来说,第 6 个要点。 “不要将会话标识符存储在本地存储中,因为 JavaScript 始终可以访问数据。Cookie 可以使用 httpOnly 标志降低这种风险。”

标签: elixir phoenix-framework


【解决方案1】:

更新:这是 Guardian 1.0(如果可以的话,你应该使用它!)。

我认为你可以这样做:

defmodule MyApp.SessionController do
  def login(conn, params) do
    # your code to find the user based on auth strategy here
    {:ok, jwt, _} = MyApp.Guardian.encode_and_sign(user)
    conn = put_session(conn, :auth_token, jwt)
    # send your response...
  end
end

将在 cookie 上使用 HttpOnlySecure 标志。以这种方式存储客户端应该没问题。

要获取用户,您可能需要编写自己的小插件,但您的 MyApp.Guardian 模块可能如下所示:

defmodule MyApp.Guardian do
  use Guardian, otp_app: :my_app
  alias MyApp.Accounts

  def subject_for_token(%Accounts.Human{} = human, _claims) do
    {:ok, to_string(human.id)}
  end
  def subject_for_token(_, _) do
    {:error, :resource_not_found}
  end

  def resource_from_claims(%{"sub" => sub}) do
    case Accounts.find_human(sub) do
      nil -> {:error, :resource_not_found}
      human -> {:ok, human}
    end
  end
  def resource_from_claims(_) do
    {:error, :resource_not_found}
  end
end

【讨论】:

    【解决方案2】:

    只需使用加密的会话 cookie。 JWT 令牌不打算存储在本地(在 localStorage 或 cookie 中),因为您只是放弃了 JWT 的用途。

    从您的代码 sn-p 看来,您正在使用 JWT 令牌作为会话 cookie 的替代品。为什么不一开始就使用会话 cookie?

    【讨论】:

    • 我自己也一直在考虑这个问题。一旦我开始将 jwt 令牌存储在数据库表中以便能够轻松地撤销它们,我意识到我刚刚重新发明了会话的想法——除了不太安全并且远没有经过实战测试。没错,只使用会话 cookie 可能更容易!
    • 没错。 JWT 最好用作一次性使用的短命令牌。当您开始将它们存储在服务器端数据库中以供撤销,以及某种形式的客户端存储以供重用时,那么您一开始就已经破坏了它们的大部分目的。坚持使用加密的会话 cookie 并继续为您的实际应用程序编写解决方案,而不是试图寻找解决方法以使 JWT 作为会话替换机制工作。
    • 我整天都在努力让 cookie 与我的 SPA 一起工作,但不幸的是我还没有找到解决方案。 Cookie 是从服务器发送的(带有 Set-Cookie 标头),但浏览器从不保存它们,可能是因为我正在通过 ajax 进行身份验证并且从未进行 300 重定向。 =/
    • 浏览器肯定会保存它们,但是您的 ajax 调用可能不会发送它们。首先,如果 cookie 上有HttpOnly 标志,那么您的 JS 无法读取它,或者其次,取决于您可能使用的 Ajax 库,您可能必须启用使用 ajax 请求发送 cookie 数据。
    • 如果浏览器正在保存它们,我会无法在 cookie 部分找到它吗?当我从 localhost 查找保存的 cookie 时,什么都没有。
    【解决方案3】:

    我认为您误解了 JWT 的工作原理。是否暴露无所谓。登录时获取 JWT,并在页面重新加载/刷新时进行验证。它在后端签名。它的重点是能够拥有一个可以在 FE 中传递的对象,而不必暴露你的 SK

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-04
      • 2015-10-11
      • 1970-01-01
      • 2014-05-17
      • 1970-01-01
      • 2012-09-14
      • 2013-03-17
      • 2018-08-08
      相关资源
      最近更新 更多