要使JWT无效,人们使用以下两种方法之一
- 黑名单/白名单(带guardian_db(。
- 具有较短的过期访问令牌的刷新令牌(允许重新生成访问令牌(。
我不想在我的项目中使用guardian_db。那么,如何在登录终端节点中生成访问令牌和刷新令牌?
我的代码是:
文件混合.ex
# Authentication library
{:guardian, "~> 1.0"}
文件配置.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
使用此代码,我有一个有效的访问令牌,但我不知道当他过期时如何生成刷新令牌以重新生成/刷新访问令牌......
有人可以帮忙吗?
我的解决方案是这样的...
在登录端点中,我使用 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
我使用的是旧版本的 Guardian,所以我的代码有点不同,但如果你想在同一端点中执行此操作,你可以匹配正文中的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 时间从未来的时间戳更改为以毫秒为单位的时间,但您可以根据需要使用它。