查看源代码 Phoenix.Token (Phoenix v1.7.14)

在令牌中对数据进行签名/加密的便捷方法,用于通道、API 认证等。

存储在令牌中的数据已签名以防止篡改,并且可以选择加密。这意味着,只要密钥(见下文)保持机密,您就可以确信存储在令牌中的数据未被第三方篡改。但是,除非令牌已加密,否则使用此令牌存储私密信息(例如用户的敏感识别数据)并不安全,因为可以轻松对其进行解码。如果令牌已加密,其内容将对客户端保密,但最好还是尽可能少地编码秘密信息,以最大程度地减少密钥泄露的影响。

示例

在为 API 或通道生成唯一令牌时,建议使用用户的唯一标识符,通常是数据库中的 id。例如

iex> user_id = 1
iex> token = Phoenix.Token.sign(MyAppWeb.Endpoint, "user auth", user_id)
iex> Phoenix.Token.verify(MyAppWeb.Endpoint, "user auth", token, max_age: 86400)
{:ok, 1}

在该示例中,我们有用户的 id,我们生成一个令牌并使用给定 endpoint 中配置的密钥基础对其进行验证。我们通过设置最大年龄来保证令牌仅在一天内有效(建议)。

sign/4verify/4encrypt/4decrypt/4 的第一个参数可以是以下之一:

  • Phoenix 端点的模块名称(如上所示) - 密钥基础从端点中提取
  • Plug.Conn - 密钥基础从存储在连接中的端点中提取
  • Phoenix.SocketPhoenix.LiveView.Socket - 密钥基础从存储在套接字中的端点中提取
  • 一个字符串,表示密钥基础本身。应使用至少 20 个随机生成的字符的密钥基础来提供足够的熵

第二个参数是 密码盐,它必须在 sign/4verify/4 的两次调用中相同,或者在 encrypt/4decrypt/4 的两次调用中相同。例如,它可以称为“用户身份验证”,并在生成将在通道或 API 上用于身份验证用户的令牌时被视为命名空间。

第三个参数可以是您希望编码到令牌中的任何项(字符串、整数、列表等)。在成功验证后,将从令牌中提取相同的项。

用法

在对令牌进行签名后,我们可以通过多种方式将其发送给客户端。

一种是通过元标记

<%= tag :meta, name: "channel_token",
               content: Phoenix.Token.sign(@conn, "user auth", @current_user.id) %>

或返回它的端点

def create(conn, params) do
  user = User.create(params)
  render(conn, "user.json",
         %{token: Phoenix.Token.sign(conn, "user auth", user.id), user: user})
end

令牌发送后,客户端现在可以将其作为身份验证机制发送回服务器。例如,我们可以使用它来对 Phoenix 通道上的用户进行身份验证

defmodule MyApp.UserSocket do
  use Phoenix.Socket

  def connect(%{"token" => token}, socket, _connect_info) do
    case Phoenix.Token.verify(socket, "user auth", token, max_age: 86400) do
      {:ok, user_id} ->
        socket = assign(socket, :user, Repo.get!(User, user_id))
        {:ok, socket}
      {:error, _} ->
        :error
    end
  end

  def connect(_params, _socket, _connect_info), do: :error
end

在此示例中,phoenix.js 客户端将在 connect 命令中发送令牌,然后由服务器进行验证。

Phoenix.Token 还可用于验证 API、处理密码重置、电子邮件确认等。

摘要

函数

从令牌中解密原始数据并验证其完整性。

将数据编码、加密并签名到您可以发送给客户端的令牌中。其用法与 sign/4 相同,但数据是使用 decrypt/4 而不是 verify/4 提取的。

将数据编码并签名到您可以发送给客户端的令牌中。

从令牌中解码原始数据并验证其完整性。

类型

@type context() ::
  Plug.Conn.t()
  | %{:endpoint => atom(), optional(atom()) => any()}
  | atom()
  | binary()
@type max_age_opt() :: {:max_age, pos_integer() | :infinity}
@type shared_opt() ::
  {:key_iterations, pos_integer()}
  | {:key_length, pos_integer()}
  | {:key_digest, :sha256 | :sha384 | :sha512}
@type signed_at_opt() :: {:signed_at, pos_integer()}

函数

链接到此函数

decrypt(context, secret, token, opts \\ [])

查看源代码
@spec decrypt(context(), binary(), binary(), [shared_opt() | max_age_opt()]) :: term()

从令牌中解密原始数据并验证其完整性。

其用法与 verify/4 相同,但用于加密的令牌。

选项

  • :key_iterations - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 1000
  • :key_length - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 32
  • :key_digest - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 :sha256
  • :max_age - 仅在令牌是在“最大年龄”秒前生成的条件下验证令牌。默认值为 encrypt/4 在令牌中签名的最大年龄。
链接到此函数

encrypt(context, secret, data, opts \\ [])

查看源代码
@spec encrypt(context(), binary(), term(), [
  shared_opt() | max_age_opt() | signed_at_opt()
]) :: binary()

将数据编码、加密并签名到您可以发送给客户端的令牌中。其用法与 sign/4 相同,但数据是使用 decrypt/4 而不是 verify/4 提取的。

选项

  • :key_iterations - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 1000
  • :key_length - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 32
  • :key_digest - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 :sha256
  • :signed_at - 设置令牌的时间戳(以秒为单位)。默认值为 System.os_time(:millisecond)
  • :max_age - 令牌的默认最大年龄。默认值为 86400 秒(1 天),并且可以在 decrypt/4 上覆盖。
链接到此函数

sign(context, salt, data, opts \\ [])

查看源代码
@spec sign(context(), binary(), term(), [
  shared_opt() | max_age_opt() | signed_at_opt()
]) :: binary()

将数据编码并签名到您可以发送给客户端的令牌中。

选项

  • :key_iterations - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 1000
  • :key_length - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 32
  • :key_digest - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 :sha256
  • :signed_at - 设置令牌的时间戳(以秒为单位)。默认值为 System.os_time(:millisecond)
  • :max_age - 令牌的默认最大年龄。默认值为 86400 秒(1 天),并且可以在 verify/4 上覆盖。
链接到此函数

verify(context, salt, token, opts \\ [])

查看源代码
@spec verify(context(), binary(), binary(), [shared_opt() | max_age_opt()]) ::
  {:ok, term()} | {:error, :expired | :invalid | :missing}

从令牌中解码原始数据并验证其完整性。

示例

在这种情况下,我们将创建一个令牌,对其进行签名,然后将其提供给客户端应用程序。然后,客户端将使用此令牌来验证对服务器资源的请求。有关创建令牌的更多信息,请参阅 Phoenix.Token 摘要。

iex> user_id    = 99
iex> secret     = "kjoy3o1zeidquwy1398juxzldjlksahdk3"
iex> namespace  = "user auth"
iex> token      = Phoenix.Token.sign(secret, namespace, user_id)

将令牌传递给客户端的机制通常是通过 cookie、JSON 响应正文或 HTTP 标头。现在,假设客户端已收到一个可用于验证对受保护资源的请求的令牌。

服务器收到请求时,可以使用 verify/4 来确定是否应将请求的资源提供给客户端

iex> Phoenix.Token.verify(secret, namespace, token, max_age: 86400)
{:ok, 99}

在此示例中,我们知道客户端发送了一个有效的令牌,因为 verify/4 返回了一个类型为 {:ok, user_id} 的元组。现在,服务器可以继续处理请求。

但是,如果客户端发送了一个过期的令牌、无效的令牌或 nilverify/4 将返回一个错误,而不是。

iex> Phoenix.Token.verify(secret, namespace, expired, max_age: 86400)
{:error, :expired}

iex> Phoenix.Token.verify(secret, namespace, invalid, max_age: 86400)
{:error, :invalid}

iex> Phoenix.Token.verify(secret, namespace, nil, max_age: 86400)
{:error, :missing}

选项

  • :key_iterations - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 1000
  • :key_length - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 32
  • :key_digest - 传递给 Plug.Crypto.KeyGenerator 的选项,用于生成加密和签名密钥。默认值为 :sha256
  • :max_age - 仅在令牌是在“最大年龄”秒前生成的条件下验证令牌。默认值为 sign/4 在令牌中签名的最大年龄。