如何为Absinthe内置GraphiQL配置不同的GET/POST认证流水线?
解决方案:为GraphiQL的GET/POST请求配置独立流水线
要实现GraphiQL界面加载(GET)不校验JWT,而查询请求(POST)校验JWT的需求,你可以通过拆分流水线+按请求方法路由来实现,具体步骤如下:
1. 定义专用流水线
首先创建两个流水线:一个用于加载GraphiQL界面(不需要JWT校验),另一个用于处理查询请求(需要JWT校验)。你可以基于现有的browser流水线调整,或者直接新增:
# 用于加载GraphiQL界面的GET请求(仅保留基础浏览器安全插件,无JWT校验) pipeline :graphiql_browser do plug RemoteIp, headers: ~w[x-forwarded-for], proxies: ~w[] plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end # 用于处理GraphQL查询的POST请求(添加JWT校验逻辑) pipeline :graphiql_api do plug RemoteIp, headers: ~w[x-forwarded-for], proxies: ~w[] plug :accepts, ["json"] # 引入自定义JWT校验插件 plug ApiWeb.Plug.VerifyJWT end
2. 实现JWT校验插件
写一个VerifyJWT插件,从请求头提取Authentication: Bearer <JWT>,解析出sub字段并存入conn.assigns,方便后续传递给Absinthe的上下文:
defmodule ApiWeb.Plug.VerifyJWT do import Plug.Conn alias Jason def init(opts), do: opts def call(conn, _opts) do case get_req_header(conn, "authorization") do ["Bearer " <> token] -> # 替换成你实际的JWT解析逻辑(比如用Guardian/JOSE库) case decode_and_verify(token) do {:ok, claims} -> # 将用户ID存入conn,后续传给Absinthe context assign(conn, :current_user_id, claims["sub"]) {:error, reason} -> # 校验失败返回401并终止请求 conn |> put_status(:unauthorized) |> json(%{error: "Invalid JWT: #{inspect(reason)}"}) |> halt() end _ -> # 缺少Authorization头时返回401 conn |> put_status(:unauthorized) |> json(%{error: "Authorization header missing"}) |> halt() end end # 示例JWT解码函数,替换为你的实际实现 defp decode_and_verify(token) do # 比如用Guardian:Guardian.decode_and_verify(token) # 或JOSE:JOSE.JWT.verify_strict(your_keys, ["HS256"], token) {:ok, %{"sub" => "user_123"}} end end
3. 按请求方法路由GraphiQL
拆分两个路由规则,分别处理GET和POST请求,对应不同的流水线:
# 处理GET请求:加载GraphiQL界面,使用graphiql_browser流水线 scope "/graphiql" do pipe_through :graphiql_browser get "/", Absinthe.Plug.GraphiQL, schema: ApiWeb.Schema, default_headers: {__MODULE__, :graphiql_headers}, context: %{pubsub: ApiWeb.Endpoint} end # 处理POST请求:执行GraphQL查询,使用graphiql_api流水线 scope "/graphiql" do pipe_through :graphiql_api post "/", Absinthe.Plug.GraphiQL, schema: ApiWeb.Schema, # 通过函数从conn中提取用户ID,传入Absinthe上下文 context: fn conn -> %{pubsub: ApiWeb.Endpoint, current_user_id: conn.assigns.current_user_id} end end
关键细节说明
- 关于
default_headers的生效范围:default_headers只是在GraphiQL界面初始化(GET请求加载页面)时,预填充到界面的请求头设置里,不会自动附加到后续的POST查询请求中。用户手动添加的Authentication头会直接随POST请求发送,所以必须在POST流水线里做校验。 - 流水线拆分的优势:完全隔离了界面加载和查询请求的逻辑,避免用户访问浏览器端点时触发不必要的JWT校验,符合你的需求。
- 上下文传递:POST路由的
context使用函数形式,可以动态从conn中提取已校验的用户ID,让解析器能直接使用该信息。
这样配置后,用户访问/graphiql(GET)时会正常加载界面且不校验JWT,执行查询(POST)时会自动校验请求头中的JWT,并将用户ID传入解析器上下文。
内容的提问来源于stack exchange,提问作者raarts
相关产品推荐
相关产品推荐

