You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:51:05