9

我正在开发一个香草 Elixir / Phoenix 应用程序,并按照Programming Phoenix书中的一般步骤来实现基本的登录和注销系统(参见下面的片段)。但是,我在书中或在线上没有看到关于如何设置基于 cookie 的插件会话在一定时间后过期的建议。Phoenix 应用程序中的会话超时有哪些方法?

这是我的基本身份验证系统的一些相关片段:

endpoint.ex中,应用程序被配置为使用基于 cookie 的只读会话:

plug Plug.Session,
  store: :cookie,
  key: "_zb_key",
  signing_salt: "RANDOM HEX"

我写了一个插件auth.ex(除其他外)可以登录经过身份验证的用户,并且可以current_user根据user_id后续请求中找到的会话进行设置:

def login!(conn, user) do
  conn
    |> assign(:current_user, user)
    |> put_session(:user_id, user.id)
    |> configure_session(renew: true)
end

# ... more ...

def load_current_user(conn, _opts) do
  cond do
    conn.assigns[:current_user] ->
      conn # If :current_user was already set, honor it
    user_id = get_session(conn, :user_id) ->
      user = Zb.Repo.get!(Zb.User, user_id)
      assign(conn, :current_user, user)
    true ->
      conn # No user_id was found; make no changes
  end
end

# ... more ...
4

3 回答 3

11

The Plug.Sessions module has a built-in option to set the expiration of a cookie using the max_age key. For example, extending your endpoint.ex snippet would look like:

plug Plug.Session,
  store: :cookie,
  key: "_zb_key",
  signing_salt: "RANDOM HEX",
  max_age: 24*60*60*37       # 37 days

Credit: https://teamgaslight.com/blog/til-how-to-explicitly-set-session-expiration-in-phoenix

Documentation: https://hexdocs.pm/plug/Plug.Session.html#module-options

于 2018-11-02T19:29:54.203 回答
8

这是我们的生产解决方案(在 Gist 中查看):

sliding_session_timeout.ex
defmodule Auth.SlidingSessionTimeout do
  import Plug.Conn

  def init(opts \\ []) do
    Keyword.merge([timeout_after_seconds: 3600], opts)
  end

  def call(conn, opts) do
    timeout_at = get_session(conn, :session_timeout_at)
    if timeout_at && now() > timeout_at do
      logout_user(conn)
    else
      put_session(conn, :session_timeout_at, new_session_timeout_at(opts[:timeout_after_seconds]))
    end
  end

  defp logout_user(conn) do
    conn
    |> clear_session()
    |> configure_session([:renew])
    |> assign(:session_timeout, true)
  end

  defp now do
    DateTime.utc_now() |> DateTime.to_unix
  end

  defp new_session_timeout_at(timeout_after_seconds) do
    now() + timeout_after_seconds
  end
end

如何使用它

:browser将其插入Phoenix 应用程序的管道末端router.ex

请注意,身份验证(user_id从会话中获取,从数据库加载用户)和授权是其他插件的关注点,在管道中。因此,请确保在基于会话的身份验证和授权插件之前插入它。

pipeline :browser do
  plug :accepts, ["html"]
  plug :fetch_session
  plug :fetch_flash
  plug :put_secure_browser_headers
  plug Auth.SlidingSessionTimeout, timeout_after_seconds: 3600    # <=
end
于 2017-03-10T13:42:54.013 回答
4

我首先在Plug library中寻找 cookie 过期选项,然后意识到一种更简单(也更安全)的方法是在会话中简单地设置过期日期时间以及 user_id。会话是防篡改的,所以当我收到每个请求时,我可以将日期时间与现在进行比较;如果会话还没有过期,我设置current_user为正常。否则我打电话logout!删除过期的会话。

实现看起来像这样(需要Timex库):

# Assign current_user to the conn, if a user is logged in
def load_current_user(conn, _opts) do
  cond do
    no_login_session?(conn) ->
      conn # No user_id was found; make no changes
    current_user_already_set?(conn) ->
      conn
    session_expired?(conn) ->
      logout!(conn)
    user = load_user_from_session(conn) ->
      conn
        |> put_session(:expires_at, new_expiration_datetime_string)
        |> assign(:current_user, user)
  end
end

defp session_expired?(conn) do
  expires_at = get_session(conn, :expires_at) |> Timex.parse!("{ISO:Extended}")
  Timex.after?(Timex.now, expires_at)
end

# ... more ...

# Start a logged-in session for an (already authenticated) user
def login!(conn, user) do
  conn
    |> assign(:current_user, user)
    |> put_session(:user_id, user.id)
    |> put_session(:expires_at, new_expiration_datetime_string)
    |> configure_session(renew: true)
end

defp new_expiration_datetime_string do
  Timex.now |> Timex.shift(hours: +2) |> Timex.format("{ISO:Extended}")
end

# ... more ...
于 2017-01-29T18:47:54.577 回答