Phoenix/Elixir搭配Vue前端如何生成csrf_token结合JWT实现用户认证
实现方案
1. 调整路由配置
前后端分离场景下CSRF Token依赖session存储,首先需要给API路由增加session支持,同时新增公开的CSRF Token获取接口:
# lib/auth_tutorial_phoenix_web/router.ex defmodule AuthTutorialPhoenixWeb.Router do use AuthTutorialPhoenixWeb, :router # 新增带session支持的API管道 pipeline :api_with_session do plug :accepts, ["json"] plug :fetch_session plug Plug.CSRFProtection end scope "/api", AuthTutorialPhoenixWeb do pipe_through :api_with_session # 新增CSRF Token获取接口 get "/csrf-token", SessionController, :get_csrf post("/users/sign_in", SessionController, :new) # 其他需要认证的接口也走该管道 end end
2. 实现CSRF Token获取接口
在SessionController中新增接口方法,直接返回生成的CSRF Token:
# lib/auth_tutorial_phoenix_web/controllers/session_controller.ex def get_csrf(conn, _params) do csrf_token = Plug.CSRFProtection.get_csrf_token() json(conn, %{csrf_token: csrf_token}) end
3. 调整登录逻辑,将CSRF存入JWT
修改登录方法,接收前端传递的CSRF Token,存入Guardian生成的JWT载荷中,登录成功后同步返回给前端:
# 修改原new方法 def new(conn, %{"email" => email, "password" => password, "csrf_token" => csrf_token}) do case Accounts.authenticate_user(email, password) do {:ok, user} -> # 将csrf_token存入JWT载荷 {:ok, access_token, _claims} = Guardian.encode_and_sign(user, %{csrf: csrf_token}, token_type: "access", ttl: {15, :minute}) {:ok, refresh_token, _claims} = Guardian.encode_and_sign(user, %{csrf: csrf_token}, token_type: "refresh", ttl: {7, :day}) conn |> put_resp_cookie("ruid", refresh_token, http_only: true, same_site: :strict) |> put_status(:created) # 登录成功后返回csrf_token给前端存储 |> render("token.json", access_token: access_token, user: user, csrf_token: csrf_token) {:error, :unauthorized} -> body = Jason.encode!(%{error: "unauthorized"}) conn |> send_resp(401, body) end end
4. 新增CSRF校验Plug
自定义Plug用于后续接口校验,比对请求头中的CSRF Token和JWT中存储的CSRF是否一致:
# lib/auth_tutorial_phoenix_web/plugs/validate_csrf_in_jwt.ex defmodule AuthTutorialPhoenixWeb.Plugs.ValidateCSRFInJWT do import Plug.Conn import Guardian.Plug def init(opts), do: opts def call(conn, _opts) do jwt_csrf = current_claims(conn) |> Map.get("csrf") request_csrf = get_req_header(conn, "x-csrf-token") |> List.first() if jwt_csrf == request_csrf && jwt_csrf != nil do conn else conn |> send_resp(403, Jason.encode!(%{error: "无效的CSRF Token"})) |> halt() end end end
将该Plug加入到需要认证的接口管道中即可生效。
5. 前端Vue适配逻辑
- 用户进入登录页时,先调用
GET /api/csrf-token接口获取CSRF Token,存储到本地 - 提交登录表单时,将CSRF Token放入请求体或
X-CSRF-Token请求头中传递给后端 - 登录成功后,存储返回的新CSRF Token,后续所有业务请求都将其放入
X-CSRF-Token请求头中携带
内容的提问来源于stack exchange,提问作者Cracho
相关产品推荐
相关产品推荐

