1

为了使 JWT 失效,人们使用两种方法之一

  • 黑名单/白名单(带有 Guardian_db)。
  • 一个刷新令牌(允许重新生成访问令牌)和一个即将过期的访问令牌。

我不想在我的项目中使用 Guardian_db。那么,我如何在登录端点中生成访问令牌和刷新令牌?

我的代码是:

文件 mix.ex

    # Authentication library
    {:guardian, "~> 1.0"}

文件 config.exs

    # Guardian configuration
    config :my_proj, MyProj.Guardian,
        issuer: "my_proj",
        verify_module: Guardian.JWT,
        secret_key: "Xxxxxxxxxxxxxxxxxxxxxxxxxx",
        allowed_drift: 2000,
        verify_issuer: true,
        ttl: {5, :minutes}

    # Ueberauth configuration
    config :ueberauth, Ueberauth,
        base_path: "/api/auth",
        providers: [
        identity: {
            Ueberauth.Strategy.Identity,
            [
                callback_methods: ["POST"],
                callback_path: "/api/auth/login",
                nickname_field: :email,
                param_nesting: "account",
                uid_field: :email
            ]
            }
        ]

文件 aut_access_pipeline.ex

    defmodule MyProjWeb.Plug.AuthAccessPipeline do
        use Guardian.Plug.Pipeline, otp_app: :my_proj

        plug(Guardian.Plug.VerifySession, claims: %{"typ" => "access"})
        plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"})
        plug(Guardian.Plug.EnsureAuthenticated)
        plug(Guardian.Plug.LoadResource, ensure: true)
        plug(MyProjWeb.Plug.CurrentUser)
        plug(MyProjWeb.Plug.CheckToken)
    end

文件 auth_controller.ex

    defmodule MyProjWeb.AuthenticationController do
        def login(conn, %{"email" => email, "password" => password}) do
            case Accounts.get_user_by_email_and_password(email, password) do
                {:ok, user} ->
                    handle_login_response(conn, user, "login.json")

                {:error, _reason} ->
                    conn
                    |> put_status(:unauthorized)
                    |> Error.render(:invalid_credentials)
                    |> halt
            end
        end

        def refresh(conn, %{"jwt" => existing_jwt}) do
            case MyProj.Guardian.refresh(existing_jwt, ttl: {5, :minutes}) do
                {:ok, {_old_token, _old_claims}, {new_jwt, _new_claims}} ->
                    current_user = current_resource(conn)
                    user = MyProj.Accounts.get_user!(current_user.id)

                    conn
                    |> put_resp_header("authorization", "Bearer #{new_jwt}")
                    |> render(MyProjWeb.AuthenticationView, json_file, %{jwt: new_jwt, user: user})
                {:error, _reason} ->
                    conn
                    |> put_status(:unauthorized)
                    |> Error.render(:invalid_credentials)
            end
        end

        defp handle_login_response(conn, user, json_file) do
            new_conn = MyProj.Guardian.Plug.sign_in(conn, user, [])
            jwt = MyProj.Guardian.Plug.current_token(new_conn)
            %{"exp" => exp} = MyProj.Guardian.Plug.current_claims(new_conn)

            new_conn
            |> put_resp_header("authorization", "Bearer #{jwt}")
            |> put_resp_header("x-expires", "#{exp}")
            |> render(MyProjWeb.AuthenticationView, json_file, %{jwt: jwt, user: user})
        end
    end
end

文件监护人.ex

    defmodule MyProj.Guardian do
        use Guardian, otp_app: :my_proj

        def subject_for_token(user = %User{}, _claims),
            do: {:ok, "User:#{user.id}"}

        def subject_for_token(_user, _claims) do
            {:error, :no_resource_id}
        end

        def resource_from_claims(%{"sub" => "User:" <> id}) do
            {:ok, MyProj.Accounts.get_user!(id)}
        end

        def resource_from_claims(_claims) do
            {:error, :no_claims_sub}
        end
    end

使用此代码,我有一个有效的访问令牌,但我不知道如何生成刷新令牌以在他过期时重新生成/刷新访问令牌......

有人可以帮忙吗?

4

2 回答 2

2

我的解决方案是这样的......

在登录端点中,我用 2 个令牌(访问和刷新)响应...

new_conn =
  MyProj.Guardian.Plug.sign_in(
    conn,
    credentials,
    %{},
    token_type: "refresh"
  )

jwt_refresh = MyProj.Guardian.Plug.current_token(new_conn)

{:ok, _old_stuff, {jwt, %{"exp" => exp} = _new_claims}} =
  MyProj.Guardian.exchange(jwt_refresh, "refresh", "access")

基本上解决方案是使用 MyProj.Guardian.exchange(...)

在刷新端点中,我用新的访问令牌响应...

def refresh(conn, %{"jwt_refresh" => jwt_refresh}) do
case MyProj.Guardian.exchange(jwt_refresh, "refresh", "access") do
  {:ok, _old_stuff, {jwt, %{"exp" => exp} = _new_claims}} ->
    conn
    |> put_resp_header("authorization", "Bearer #{jwt}")
    |> put_resp_header("x-expires", "#{exp}")
    |> render(MyProjWeb.AuthenticationView, "refresh.json", %{jwt: jwt})

  {:error, _reason} ->
    conn
    |> put_status(:unauthorized)
    |> Error.render(:invalid_credentials)
end
end
于 2018-05-04T09:20:16.013 回答
0

我使用的是旧版本的 Guardian,所以我的代码有点不同,但是如果你想在同一个端点中执行它,你可以匹配 body 中的 grant_type 参数

def login(conn, %{"grant_type" => "password"} = params) do
 case User.find_and_confirm_password(params) do
  {:ok, user} ->
    new_conn = GP.api_sign_in(conn, user, :token, perms: User.permissions(user))
    jwt = GP.current_token(new_conn)
    {:ok, claims} = GP.claims(new_conn)
    exp = Map.get(claims, "exp") -
      (DateTime.utc_now() |> DateTime.to_unix())

    new_conn
    |> put_resp_header("authorization", "Bearer #{jwt}")
    |> put_resp_header("x-expires", "Bearer #{exp}")
    |> render(SessionView, "login.json", jwt: jwt, exp: exp)
  {:error, changeset} ->
    conn
    |> put_status(401)
    |> render(IIMWeb.ChangesetView, "error.json", changeset: changeset)
end

结尾

def login(conn, %{"grant_type" => "refresh_token"} = params) do
  jwt = GP.current_token(conn)
  with {:ok, claims} <- GP.claims(conn),
    logged_user_id when not is_nil(logged_user_id) <-
      GP.current_resource(conn)[:id],
    user <- Repo.get!(User, logged_user_id),
    {:ok, new_jwt, new_claims} <- Guardian.refresh!(jwt, claims,
      perms: User.permissions(user))
  do
    exp = Map.get(new_claims, "exp") - (DateTime.utc_now() |> DateTime.to_unix())
    conn
    |> put_resp_header("authorization", "Bearer #{new_jwt}")
    |> put_resp_header("x-expires", "Bearer #{exp}")
    |> render(SessionView, "login.json", jwt: new_jwt, exp: exp)
  else
    {:error, reason} ->
      changeset = %Login{}
      |> Login.changeset(params)
      |> Changeset.add_error(:refresh_token, to_string(reason))
      conn
      |> put_status(401)
      |> render(IIMWeb.ChangesetView, "error.json", changeset: changeset)
end

结尾

如您所见,我使用的是相同的端点,但根据 grant_type 它接收密码或旧令牌(此请求必须在旧令牌过期前几秒钟完成)我将 exp 时间从未来时间戳更改为以毫秒为单位的时间由于前端的时区,但您可以随心所欲地使用它。

于 2018-05-03T17:30:30.560 回答