跳到内容

身份验证与访问控制

LangGraph 平台提供灵活的认证和授权系统,可以与大多数认证方案集成。

核心概念

认证 vs. 授权

尽管这些术语常被交替使用,但它们代表着不同的安全概念

  • 认证(“AuthN”)验证您是谁。这作为每个请求的中间件运行。
  • 授权(“AuthZ”)决定您能做什么。这在每个资源的基础上验证用户的权限和角色。

在 LangGraph 平台中,认证由您的 authenticate() 处理程序处理,而授权由您的 on() 处理程序处理。

默认安全模型

LangGraph 平台提供不同的安全默认设置

  • LangGraph 云
  • 自托管

LangGraph 云

  • 默认使用 LangSmith API 密钥
  • 需要在 x-api-key 头中提供有效的 API 密钥
  • 可以使用您的认证处理程序进行自定义

自定义认证

LangGraph 云中的所有计划都支持自定义认证。

自托管

  • 无默认认证
  • 实现您安全模型的完全灵活性
  • 您控制认证和授权的所有方面

自定义认证

企业版自托管计划支持自定义认证。自托管精简版计划本身不支持自定义认证。

系统架构

典型的认证设置涉及三个主要组件

  1. 认证提供者(身份提供者/IdP)

  2. 管理用户身份和凭证的专用服务

  3. 处理用户注册、登录、密码重置等。
  4. 成功认证后颁发令牌(JWT、会话令牌等)
  5. 示例:Auth0、Supabase Auth、Okta 或您自己的认证服务器

  6. LangGraph 后端(资源服务器)

  7. 包含业务逻辑和受保护资源的 LangGraph 应用程序

  8. 与认证提供者验证令牌
  9. 根据用户身份和权限强制执行访问控制
  10. 不直接存储用户凭证

  11. 客户端应用程序(前端)

  12. Web 应用程序、移动应用程序或 API 客户端

  13. 收集时效性用户凭证并发送给认证提供者
  14. 从认证提供者接收令牌
  15. 在发送到 LangGraph 后端的请求中包含这些令牌

这些组件的典型交互方式如下

sequenceDiagram
    participant Client as Client App
    participant Auth as Auth Provider
    participant LG as LangGraph Backend

    Client->>Auth: 1. Login (username/password)
    Auth-->>Client: 2. Return token
    Client->>LG: 3. Request with token
    Note over LG: 4. Validate token (authenticate())
    LG-->>Auth:  5. Fetch user info
    Auth-->>LG: 6. Confirm validity
    Note over LG: 7. Apply access control (on())
    LG-->>Client: 8. Return resources

LangGraph 中的 authenticate() 处理程序处理步骤 4-6,而您的 on() 处理程序实现步骤 7。

身份验证

LangGraph 中的认证作为每个请求的中间件运行。您的 authenticate() 处理程序接收请求信息并应

  1. 验证凭证
  2. 如果有效,返回包含用户身份和用户信息的用户信息
  3. 如果无效,抛出 HTTPException 或错误
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";

// (1) Validate the credentials
const isValidKey = (key: string) => {
  return true;
};

export const auth = new Auth().authenticate(async (request: Request) => {
  const apiKey = request.headers.get("x-api-key");

  if (!apiKey || !isValidKey(apiKey)) {
    // (3) Raise an HTTPException
    throw new HTTPException(401, { message: "Invalid API key" });
  }

  // (2) Return user information containing the user's identity and user information if valid
  return {
    // required, unique user identifier
    identity: "user-123",
    // required, list of permissions
    permissions: [],
    // optional, assumed `true` by default
    is_authenticated: true,

    // You can add more custom fields if you want to implement other auth patterns
    role: "admin",
    org_id: "org-123",
  };
});

返回的用户信息可用

  • 通过 on() 回调中的 user 属性供您的授权处理程序使用。
  • 在您的应用程序中通过 config["configuration"]["langgraph_auth_user"] 使用
Request 输入参数

authenticate() 处理程序接受 Request 实例作为参数,但 Request 对象可能不包含请求体。

您仍然可以使用 Request 实例提取其他字段,例如头部、查询参数等。

授权

认证后,LangGraph 调用您的 on() 处理程序来控制对特定资源(例如,线程、助手、定时任务)的访问。这些处理程序可以

  1. 通过直接修改 value["metadata"] 对象,在资源创建期间添加要保存的元数据。有关每个动作可取的值类型列表,请参阅支持的动作表
  2. 在搜索/列表或读取操作期间,通过返回筛选对象,按元数据筛选资源。
  3. 如果拒绝访问,则引发 HTTP 错误。

如果您只想实现简单的用户范围访问控制,可以对所有资源和操作使用单个 on() 处理程序。如果您希望根据资源和操作有不同的控制,可以使用资源特定处理程序。有关支持访问控制的完整资源列表,请参阅支持的资源部分。

import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";

export const auth = new Auth()
  .authenticate(async (request: Request) => ({
    identity: "user-123",
    permissions: [],
  }))
  .on("*", ({ value, user }) => {
    // Create filter to restrict access to just this user's resources
    const filters = { owner: user.identity };

    // If the operation supports metadata, add the user identity
    // as metadata to the resource.
    if ("metadata" in value) {
      value.metadata ??= {};
      value.metadata.owner = user.identity;
    }

    // Return filters to restrict access
    // These filters are applied to ALL operations (create, read, update, search, etc.)
    // to ensure users can only access their own resources
    return filters;
  });

资源特定处理程序

您可以通过使用 on() 方法将资源和操作名称链接起来,为特定的资源和操作注册处理程序。当发出请求时,将调用与该资源和操作匹配的最具体处理程序。以下是如何为特定资源和操作注册处理程序的示例。对于以下设置:

  1. 已认证用户能够创建线程、读取线程、在线程上创建运行
  2. 只有拥有“assistants:create”权限的用户才允许创建新助手
  3. 所有其他端点(例如,删除助手、定时任务、存储)对所有用户都禁用。

支持的处理程序

有关支持的资源和操作的完整列表,请参阅下面的支持的资源部分。

import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";

export const auth = new Auth()
  .authenticate(async (request: Request) => ({
    identity: "user-123",
    permissions: ["threads:write", "threads:read"],
  }))
  .on("*", ({ event, user }) => {
    console.log(`Request for ${event} by ${user.identity}`);
    throw new HTTPException(403, { message: "Forbidden" });
  })

  // Matches the "threads" resource and all actions - create, read, update, delete, search
  // Since this is **more specific** than the generic `on("*")` handler, it will take precedence over the generic handler for all actions on the "threads" resource
  .on("threads", ({ permissions, value, user }) => {
    if (!permissions.includes("write")) {
      throw new HTTPException(403, {
        message: "User lacks the required permissions.",
      });
    }

    // Not all events do include `metadata` property in `value`.
    // So we need to add this type guard.
    if ("metadata" in value) {
      value.metadata ??= {};
      value.metadata.owner = user.identity;
    }

    return { owner: user.identity };
  })

  // Thread creation. This will match only on thread create actions.
  // Since this is **more specific** than both the generic `on("*")` handler and the `on("threads")` handler, it will take precedence for any "create" actions on the "threads" resources
  .on("threads:create", ({ value, user, permissions }) => {
    if (!permissions.includes("write")) {
      throw new HTTPException(403, {
        message: "User lacks the required permissions.",
      });
    }

    // Setting metadata on the thread being created will ensure that the resource contains an "owner" field
    // Then any time a user tries to access this thread or runs within the thread,
    // we can filter by owner
    value.metadata ??= {};
    value.metadata.owner = user.identity;

    return { owner: user.identity };
  })

  // Reading a thread. Since this is also more specific than the generic `on("*")` handler, and the `on("threads")` handler,
  .on("threads:read", ({ user }) => {
    // Since we are reading (and not creating) a thread,
    // we don't need to set metadata. We just need to
    // return a filter to ensure users can only see their own threads.
    return { owner: user.identity };
  })

  // Run creation, streaming, updates, etc.
  // This takes precedence over the generic `on("*")` handler and the `on("threads")` handler
  .on("threads:create_run", ({ value, user }) => {
    value.metadata ??= {};
    value.metadata.owner = user.identity;

    return { owner: user.identity };
  })

  // Assistant creation. This will match only on assistant create actions.
  // Since this is **more specific** than both the generic `on("*")` handler and the `on("assistants")` handler, it will take precedence for any "create" actions on the "assistants" resources
  .on("assistants:create", ({ value, user, permissions }) => {
    if (!permissions.includes("assistants:create")) {
      throw new HTTPException(403, {
        message: "User lacks the required permissions.",
      });
    }

    // Setting metadata on the assistant being created will ensure that the resource contains an "owner" field.
    // Then any time a user tries to access this assistant, we can filter by owner
    value.metadata ??= {};
    value.metadata.owner = user.identity;

    return { owner: user.identity };
  });

请注意,在上面的示例中,我们混合使用了全局处理程序和资源特定处理程序。由于每个请求都由最具体的处理程序处理,因此创建 thread 的请求将匹配 thread:create 处理程序,但匹配 * 处理程序。但是,更新线程的请求将由全局处理程序处理,因为我们没有针对该资源和操作的更具体处理程序。创建、更新的请求,

筛选操作

授权处理程序可以返回 None、布尔值或筛选对象。

  • nullvoidtrue 表示“授权访问所有底层资源”
  • False 表示“拒绝访问所有底层资源(引发 403 错误)”
  • 元数据筛选对象将限制对资源的访问。支持精确匹配和运算符。
筛选对象语法

支持以下运算符

  • 精确匹配简写:{"field": "value"}
  • 精确匹配:{"field": {"$eq": "value"}}
  • 包含:{"field": {"$contains": "value"}}

具有多个键的元数据筛选对象被视为逻辑 AND 筛选。例如,{"owner": org_id, "allowed_users": {"$contains": user_id}} 将只匹配元数据中“owner”为 org_id 且“allowed_users”列表包含 user_id 的资源。

常见访问模式

以下是一些典型的授权模式

单一所有者资源

这种常见模式允许您将所有线程、助手、定时任务和运行范围限定为单个用户。它对于常见的单用户用例(如常规聊天机器人风格的应用程序)非常有用。

import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";

export const auth = new Auth()
  .authenticate(async (request: Request) => ({
    identity: "user-123",
    permissions: ["threads:write", "threads:read"],
  }))
  .on("*", ({ value, user }) => {
    if ("metadata" in value) {
      value.metadata ??= {};
      value.metadata.owner = user.identity;
    }
    return { owner: user.identity };
  });

基于权限的访问

此模式允许您根据权限控制访问。如果您希望某些角色对资源拥有更广泛或更受限制的访问权限,这会很有用。

import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";

export const auth = new Auth()
  .authenticate(async (request: Request) => ({
    identity: "user-123",
    // Define permissions in auth
    permissions: ["threads:write", "threads:read"],
  }))
  .on("threads:create", ({ value, user, permissions }) => {
    if (!permissions.includes("threads:write")) {
      throw new HTTPException(403, { message: "Unauthorized" });
    }

    if ("metadata" in value) {
      value.metadata ??= {};
      value.metadata.owner = user.identity;
    }
    return { owner: user.identity };
  })
  .on("threads:read", ({ user, permissions }) => {
    if (
      !permissions.includes("threads:read") &&
      !permissions.includes("threads:write")
    ) {
      throw new HTTPException(403, { message: "Unauthorized" });
    }

    return { owner: user.identity };
  });

支持的资源

LangGraph 提供三种级别的授权处理程序,从最通用到最具体

  1. 全局处理程序 (on("*")):匹配所有资源和操作
  2. 资源处理程序(例如,on("threads")on("assistants")on("crons")):匹配特定资源的所有操作
  3. 操作处理程序(例如,on("threads:create")on("threads:read")):匹配特定资源上的特定操作

将使用最具体的匹配处理程序。例如,on("threads:create") 在线程创建时优先于 on("threads")。如果注册了更具体的处理程序,则更通用的处理程序不会为该资源和操作调用。

支持的动作事件

资源 事件 描述 值类型
线程 threads:create 线程创建 ThreadsCreate
threads:read 线程检索 ThreadsRead
threads:update 线程更新 ThreadsUpdate
threads:delete 线程删除 ThreadsDelete
threads:search 列出线程 ThreadsSearch
threads:create_run 创建或更新运行 RunsCreate
助手 assistants:create 助手创建 AssistantsCreate
assistants:read 助手检索 AssistantsRead
assistants:update 助手更新 AssistantsUpdate
assistants:delete 助手删除 AssistantsDelete
assistants:search 列出助手 AssistantsSearch
Crons crons:create 定时任务创建 CronsCreate
crons:read 定时任务检索 CronsRead
crons:update 定时任务更新 CronsUpdate
crons:delete 定时任务删除 CronsDelete
crons:search 列出定时任务 CronsSearch
关于运行 (Runs)

运行(Runs)的访问控制范围限定在其父线程。这意味着权限通常从线程继承,反映了数据模型的对话性质。除了创建操作外,所有运行操作(读取、列出)都由线程的处理程序控制。有一个特定的 threads:create_run 事件用于创建新运行,因为它有更多您可以在处理程序中查看的参数。

下一步

有关实现细节