无法理解 JWT 身份验证中的解构 (Phoenix)
Can't understand destructuring in JWT auth (Phoenix)
我正在设置一个模式,我在 Phoenix 的几个地方看到过这种模式,用于 API 身份验证,使用 Comeonin 和 Guardian 进行 JWT 身份验证。
当我从 CURL POST 到 MyApp.SessionsController.create/2
时,我得到了 MyApp.Session.authenticate/1
的 user
响应,正如我所期望的那样。但是,我应该将其解构为 {:ok, jwt, _full_claims}
,然后可以将其通过管道传输到 Guardian。我使用 IO.inspect user
查看 user
对象并得到以下错误:
航站楼:
curl -H "Content-Type: application/json" -X POST -d '{"email":"me@myapp.com","password":"password", "session":{"email":"mark@myapp.com", "password":"password"}}' http://localhost:4000/api/v1/sessions
当我在 IEX 中 IO.inspect
user
时,我看到了这个:
%MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, avatar_url: nil,
email: "me@myapp.com", handle: "me", id: 2,
inserted_at: ~N[2017-08-22 18:26:10.000033], password: nil,
password_hash: "b$LpJTWWEEUzrkkzu2w9sRheGHkh0YOgUIOkLluk05StlmTP6EiyPA6",
updated_at: ~N[2017-08-22 18:26:10.007796]}
我看到这个错误:
Request: POST /api/v1/sessions
** (exit) an exception was raised:
** (MatchError) no match of right hand side value: %MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, avatar_url: nil, email: "me@myapp.com", handle: "mark", id: 2, inserted_at: ~N[2017-08-22 18:26:10.000033], password: nil, password_hash: "b$LpJTWWEEUzrkkzu2w9sRheGHkh0YOgUIOkLluk05StlmTP6EiyPA6", updated_at: ~N[2017-08-22 18:26:10.007796]}
(myapp) web/controllers/api/v1/sessions_controller.ex:11: MyApp.SessionsController.create/2
这到底是什么意思:{:ok, jwt, _full_claims} = user
?
设置如下:
# mix.exs
defp deps do
[
{:distillery, "~> 1.4", runtime: false},
{:phoenix, "~> 1.3.0-rc", override: true},
{:phoenix_ecto, "~> 3.2"},
...
{:comeonin, "~> 4.0"},
{:bcrypt_elixir, "~> 0.12.0"},
{:guardian, "~> 0.14.5"},
]
# web/router.ex
...
pipeline :api do
plug :accepts, ["json"]
plug Guardian.Plug.VerifyHeader
end
scope "/api", MyApp do
pipe_through :api
scope "/v1" do
post "/sessions", SessionsController, :create
end
end
...
# web/controllers/session_controller.ex
defmodule MyApp.SessionsController do
use MyApp.Web, :controller
alias MyApp.{Repo, User}
plug :scrub_params, "session" when action in [:create]
def create(conn, %{"session" => session_params}) do
case MyApp.Session.authenticate(session_params) do
{:ok, user} ->
{:ok, jwt, _full_claims} = user
IO.inspect user # Trying to test it here
|> Guardian.encode_and_sign(:token)
conn
|> put_status(:created)
|> render("show.json", jwt: jwt, user: user)
:error ->
conn
|> put_status(:unprocessable_entity)
|> render("error.json")
end
end
# web/services/session.ex
defmodule MyApp.Session do
alias MyApp.{Repo, User}
import Bcrypt
def authenticate(%{"email" => email, "password" => password}) do
case Repo.get_by(User, email: email) do
nil ->
:error
user ->
case verify_password(password, user.password_hash) do
true ->
{:ok, user}
_ ->
:error
end
end
end
defp verify_password(password, pw_hash) do
Comeonin.Bcrypt.checkpw(password, pw_hash)
end
end
# lib/MyApp/User.ex
defmodule MyApp.User do
use MyApp.Web, :model
schema "users" do
field :email, :string
field :handle, :string
field :password_hash, :string
field :avatar_url, :string
field :password, :string, virtual: true
timestamps
end
def changeset(model, params \ :empty) do
model
|> cast(params, [:email, :handle, :password_hash, :password, :avatar_url])
|> validate_required([:email])
|> validate_length(:email, min: 1, max: 255)
|> validate_format(:email, ~r/@/)
end
编辑:添加监护人信息
#config/config.exs
config :guardian, Guardian,
issuer: "MyApp",
ttl: { 30, :days},
verify_issuer: true,
secret_key: "abc123",
serializer: MyApp.GuardianSerializer
#lib/MyApp/guardian_serializer.ex
defmodule MyApp.GuardianSerializer do
@behaviour Guardian.Serializer
alias MyApp.Repo
alias MyApp.User
def for_token(user = %User{}), do: {:ok, "User:#{user.id}"}
def for_token(_), do: {:error, "Unknown resource type"}
def from_token("User:" <> id), do: {:ok, Repo.get(User, id)}
def from_token(_), do: {:error, "Unknown resource type"}
end
{:ok, jwt, _full_claims}
是调用 Guardian.encode_and_sign(user, :token)
返回的值。这是您链接到的教程中的原始代码:
{:ok, jwt, _full_claims} = user
|> Guardian.encode_and_sign(:token)
等同于:
{:ok, jwt, _full_claims} = Guardian.encode_and_sign(user, :token)
另一方面,您的代码执行 {:ok, jwt, _full_claims} = user
并且下一行是一个新语句。如果你想检查用户并仍然按照教程的方式进行,你可以这样做:
{:ok, jwt, _full_claims} = user
|> IO.inspect
|> Guardian.encode_and_sign(:token)
IO.inspect
returns 打印后传递的值,因此此代码的功能与教程相同,只是它也会打印 user
的值。
我正在设置一个模式,我在 Phoenix 的几个地方看到过这种模式,用于 API 身份验证,使用 Comeonin 和 Guardian 进行 JWT 身份验证。
当我从 CURL POST 到 MyApp.SessionsController.create/2
时,我得到了 MyApp.Session.authenticate/1
的 user
响应,正如我所期望的那样。但是,我应该将其解构为 {:ok, jwt, _full_claims}
,然后可以将其通过管道传输到 Guardian。我使用 IO.inspect user
查看 user
对象并得到以下错误:
航站楼:
curl -H "Content-Type: application/json" -X POST -d '{"email":"me@myapp.com","password":"password", "session":{"email":"mark@myapp.com", "password":"password"}}' http://localhost:4000/api/v1/sessions
当我在 IEX 中 IO.inspect
user
时,我看到了这个:
%MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, avatar_url: nil,
email: "me@myapp.com", handle: "me", id: 2,
inserted_at: ~N[2017-08-22 18:26:10.000033], password: nil,
password_hash: "b$LpJTWWEEUzrkkzu2w9sRheGHkh0YOgUIOkLluk05StlmTP6EiyPA6",
updated_at: ~N[2017-08-22 18:26:10.007796]}
我看到这个错误:
Request: POST /api/v1/sessions
** (exit) an exception was raised:
** (MatchError) no match of right hand side value: %MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, avatar_url: nil, email: "me@myapp.com", handle: "mark", id: 2, inserted_at: ~N[2017-08-22 18:26:10.000033], password: nil, password_hash: "b$LpJTWWEEUzrkkzu2w9sRheGHkh0YOgUIOkLluk05StlmTP6EiyPA6", updated_at: ~N[2017-08-22 18:26:10.007796]}
(myapp) web/controllers/api/v1/sessions_controller.ex:11: MyApp.SessionsController.create/2
这到底是什么意思:{:ok, jwt, _full_claims} = user
?
设置如下:
# mix.exs
defp deps do
[
{:distillery, "~> 1.4", runtime: false},
{:phoenix, "~> 1.3.0-rc", override: true},
{:phoenix_ecto, "~> 3.2"},
...
{:comeonin, "~> 4.0"},
{:bcrypt_elixir, "~> 0.12.0"},
{:guardian, "~> 0.14.5"},
]
# web/router.ex
...
pipeline :api do
plug :accepts, ["json"]
plug Guardian.Plug.VerifyHeader
end
scope "/api", MyApp do
pipe_through :api
scope "/v1" do
post "/sessions", SessionsController, :create
end
end
...
# web/controllers/session_controller.ex
defmodule MyApp.SessionsController do
use MyApp.Web, :controller
alias MyApp.{Repo, User}
plug :scrub_params, "session" when action in [:create]
def create(conn, %{"session" => session_params}) do
case MyApp.Session.authenticate(session_params) do
{:ok, user} ->
{:ok, jwt, _full_claims} = user
IO.inspect user # Trying to test it here
|> Guardian.encode_and_sign(:token)
conn
|> put_status(:created)
|> render("show.json", jwt: jwt, user: user)
:error ->
conn
|> put_status(:unprocessable_entity)
|> render("error.json")
end
end
# web/services/session.ex
defmodule MyApp.Session do
alias MyApp.{Repo, User}
import Bcrypt
def authenticate(%{"email" => email, "password" => password}) do
case Repo.get_by(User, email: email) do
nil ->
:error
user ->
case verify_password(password, user.password_hash) do
true ->
{:ok, user}
_ ->
:error
end
end
end
defp verify_password(password, pw_hash) do
Comeonin.Bcrypt.checkpw(password, pw_hash)
end
end
# lib/MyApp/User.ex
defmodule MyApp.User do
use MyApp.Web, :model
schema "users" do
field :email, :string
field :handle, :string
field :password_hash, :string
field :avatar_url, :string
field :password, :string, virtual: true
timestamps
end
def changeset(model, params \ :empty) do
model
|> cast(params, [:email, :handle, :password_hash, :password, :avatar_url])
|> validate_required([:email])
|> validate_length(:email, min: 1, max: 255)
|> validate_format(:email, ~r/@/)
end
编辑:添加监护人信息
#config/config.exs
config :guardian, Guardian,
issuer: "MyApp",
ttl: { 30, :days},
verify_issuer: true,
secret_key: "abc123",
serializer: MyApp.GuardianSerializer
#lib/MyApp/guardian_serializer.ex
defmodule MyApp.GuardianSerializer do
@behaviour Guardian.Serializer
alias MyApp.Repo
alias MyApp.User
def for_token(user = %User{}), do: {:ok, "User:#{user.id}"}
def for_token(_), do: {:error, "Unknown resource type"}
def from_token("User:" <> id), do: {:ok, Repo.get(User, id)}
def from_token(_), do: {:error, "Unknown resource type"}
end
{:ok, jwt, _full_claims}
是调用 Guardian.encode_and_sign(user, :token)
返回的值。这是您链接到的教程中的原始代码:
{:ok, jwt, _full_claims} = user
|> Guardian.encode_and_sign(:token)
等同于:
{:ok, jwt, _full_claims} = Guardian.encode_and_sign(user, :token)
另一方面,您的代码执行 {:ok, jwt, _full_claims} = user
并且下一行是一个新语句。如果你想检查用户并仍然按照教程的方式进行,你可以这样做:
{:ok, jwt, _full_claims} = user
|> IO.inspect
|> Guardian.encode_and_sign(:token)
IO.inspect
returns 打印后传递的值,因此此代码的功能与教程相同,只是它也会打印 user
的值。