Skip to content

认证与访问控制

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

核心概念

认证与授权

尽管这两个术语经常被互换使用,但它们代表了不同的安全概念:

  • 认证 ("AuthN") 验证您是谁。这作为中间件在每个请求中运行。
  • 授权 ("AuthZ") 确定您可以做什么。这会根据资源验证用户的权限和角色。

在 LangGraph 平台上,认证由您的 @auth.authenticate 处理程序处理,而授权由您的 @auth.on 处理程序处理。

默认安全模型

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

LangGraph 平台

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

自定义认证

LangGraph 平台的所有计划都 支持 自定义认证。

自托管

  • 没有默认的认证机制
  • 完全可以自由实现自己的安全模型
  • 你可以控制认证和授权的所有方面

自定义认证

自定义认证仅对 企业版 的自托管部署 支持。 独立容器(Lite)部署不原生支持自定义认证。

系统架构

一个典型的认证设置包括三个主要组件:

  1. 认证提供者 (Identity Provider/IdP)

    • 一个专门管理用户身份和凭证的服务
    • 处理用户注册、登录、密码重置等
    • 认证成功后发放令牌(如 JWT、会话令牌等)
    • 示例:Auth0、Supabase Auth、Okta 或您自己的认证服务器
  2. LangGraph 后端 (资源服务器)

    • 包含业务逻辑和受保护资源的 LangGraph 应用程序
    • 使用认证提供者验证令牌
    • 根据用户身份和权限执行访问控制
    • 不直接存储用户凭证
  3. 客户端应用 (前端)

    • Web 应用、移动应用或 API 客户端
    • 收集临时用户凭证并发送到认证提供者
    • 从认证提供者接收令牌
    • 在请求 LangGraph 后端时包含这些令牌

这些组件通常以以下方式交互:

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

    Client->>Auth: 1. 登录(用户名/密码)
    Auth-->>Client: 2. 返回令牌
    Client->>LG: 3. 带令牌的请求
    Note over LG: 4. 验证令牌 (@auth.authenticate)
    LG-->>Auth:  5. 获取用户信息
    Auth-->>LG: 6. 确认有效性
    Note over LG: 7. 应用访问控制 (@auth.on.*)
    LG-->>Client: 8. 返回资源

LangGraph 中的 @auth.authenticate 处理器负责处理步骤 4-6,而您的 @auth.on 处理器实现步骤 7。

认证

LangGraph 的认证机制作为中间件在每个请求上运行。你的 @auth.authenticate 处理器接收请求信息,并应执行以下操作:

  1. 验证凭证
  2. 如果凭证有效,返回包含用户身份和用户信息的 用户信息
  3. 如果无效,引发 HTTP 异常 或 AssertionError
from langgraph_sdk import Auth

auth = Auth()

@auth.authenticate
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
    # 验证凭证(例如 API 密钥、JWT 令牌)
    api_key = headers.get("x-api-key")
    if not api_key or not is_valid_key(api_key):
        raise Auth.exceptions.HTTPException(
            status_code=401,
            detail="Invalid API key"
        )

    # 返回用户信息 - 仅 identity 和 is_authenticated 是必需的
    # 添加任何你用于授权的额外字段
    return {
        "identity": "user-123",        # 必需:唯一的用户标识符
        "is_authenticated": True,      # 可选:默认为 True
        "permissions": ["read", "write"] # 可选:基于权限的认证
        # 你可以添加更多自定义字段以实现其他认证模式
        "role": "admin",
        "org_id": "org-456"

    }

返回的用户信息可用于以下位置:

  • 通过 ctx.user 提供给你的授权处理器
  • 在你的应用中通过 config["configuration"]["langgraph_auth_user"] 使用
支持的参数

@auth.authenticate 处理器可以按名称接受以下任意参数:

  • request (Request): 原始 ASGI 请求对象
  • body (dict): 解析后的请求体
  • path (str): 请求路径,例如 "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
  • method (str): HTTP 方法,例如 "GET"
  • path_params (dict[str, str]): URL 路径参数,例如 {"thread_id": "abcd-1234-abcd-1234", "run_id": "abcd-1234-abcd-1234"}
  • query_params (dict[str, str]): URL 查询参数,例如 {"stream": "true"}
  • headers (dict[bytes, bytes]): 请求头
  • authorization (str | None): Authorization 请求头的值(例如 "Bearer ")

在我们很多教程中,为了简洁起见,我们将只展示 "authorization" 参数,但你可以根据需要选择接受更多信息以实现自定义的认证方案。

授权

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

  1. 通过直接修改 value["metadata"] 字典,在资源创建时添加元数据。请参阅 支持的操作表 以了解每种操作中 value 可能采用的类型列表。
  2. 在搜索/列表或读取操作期间,通过返回一个 过滤字典 来根据元数据筛选资源。
  3. 如果访问被拒绝,则抛出 HTTP 异常。

如果你想实现简单的用户作用域访问控制,可以使用一个 @auth.on 处理程序来处理所有资源和操作。如果你希望根据资源和操作的不同进行不同的控制,可以使用 特定资源的处理程序。请参阅 支持的资源 部分,以获取支持访问控制的完整资源列表。

@auth.on
async def add_owner(
    ctx: Auth.types.AuthContext,
    value: dict  # 发送到此访问方法的负载
) -> dict:  # 返回一个限制资源访问的过滤字典
    """授权对线程、运行、定时任务和助手的所有访问。

    此处理程序执行以下两项操作:
        - 向资源元数据中添加值(以便与资源一起保存,以便以后进行过滤)
        - 返回一个过滤器(以限制对现有资源的访问)

    参数:
        ctx: 包含用户信息、权限、路径的认证上下文
        value: 发送到端点的请求负载。对于创建操作,这包含资源参数。对于读取操作,这包含正在访问的资源。

    返回:
        LangGraph 使用的过滤字典,用于限制对资源的访问。
        有关支持的操作符,请参见 [过滤操作](#filter-operations)。
    """
    # 创建过滤器,仅限制访问该用户的资源
    filters = {"owner": ctx.user.identity}

    # 获取或在负载中创建元数据字典
    # 这里我们存储关于资源的持久信息
    metadata = value.setdefault("metadata", {})

    # 向元数据中添加所有者 - 如果这是创建或更新操作,
    # 这些信息将与资源一起保存
    # 因此,我们可以在读取操作中通过它进行过滤
    metadata.update(filters)

    # 返回过滤器以限制访问
    # 这些过滤器应用于所有操作(创建、读取、更新、搜索等)
    # 以确保用户只能访问自己的资源
    return filters

特定资源的处理程序

你可以通过使用 @auth.on 装饰器,将资源和操作名称连接起来,为特定资源和操作注册处理程序。当请求发生时,将调用最符合该资源和操作的处理程序。下面是如何为特定资源和操作注册处理程序的示例。对于以下设置:

  1. 认证用户可以创建线程、读取线程,并在线程上创建运行
  2. 仅具有 "assistants:create" 权限的用户才允许创建新助手
  3. 所有其他端点(例如,删除助手、定时任务、存储)对所有用户都是禁用的。

支持的处理程序

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

# 通用 / 全局处理程序捕获未被更具体处理程序处理的请求
@auth.on
async def reject_unhandled_requests(ctx: Auth.types.AuthContext, value: Any) -> False:
    print(f"请求 {ctx.path}{ctx.user.identity}")
    raise Auth.exceptions.HTTPException(
        status_code=403,
        detail="Forbidden"
    )

# 匹配 "thread" 资源和所有操作 - 创建、读取、更新、删除、搜索
# 由于这个处理程序比通用的 @auth.on 处理程序更具体,
# 它将在所有 "threads" 资源的操作中优先于通用处理程序
@auth.on.threads
async def on_thread_create(
    ctx: Auth.types.AuthContext,
    value: Auth.types.threads.create.value
):
    if "write" not in ctx.permissions:
        raise Auth.exceptions.HTTPException(
            status_code=403,
            detail="User lacks the required permissions."
        )
    # 在创建的线程上设置元数据
    # 将确保资源包含一个 "owner" 字段
    # 然后每次用户尝试访问此线程或线程内的运行时,
    # 我们都可以按所有者进行过滤
    metadata = value.setdefault("metadata", {})
    metadata["owner"] = ctx.user.identity
    return {"owner": ctx.user.identity}

# 线程创建。这将只匹配线程创建操作
# 由于这个处理程序比通用的 @auth.on 处理程序和 @auth.on.threads 处理程序都更具体,
# 它将在所有 "threads" 资源的 "create" 操作中优先于这两个处理程序
@auth.on.threads.create
async def on_thread_create(
    ctx: Auth.types.AuthContext,
    value: Auth.types.threads.create.value
):
    # 在创建的线程上设置元数据
    # 将确保资源包含一个 "owner" 字段
    # 然后每次用户尝试访问此线程或线程内的运行时,
    # 我们都可以按所有者进行过滤
    metadata = value.setdefault("metadata", {})
    metadata["owner"] = ctx.user.identity
    return {"owner": ctx.user.identity}

# 读取线程。由于这也比通用的 @auth.on 处理程序和 @auth.on.threads 处理程序更具体,
# 它将在所有 "threads" 资源的 "read" 操作中优先于这两个处理程序
@auth.on.threads.read
async def on_thread_read(
    ctx: Auth.types.AuthContext,
    value: Auth.types.threads.read.value
):
    # 由于我们正在读取(而不是创建)线程,
    # 我们不需要设置元数据。我们只需要
    # 返回一个过滤器,以确保用户只能看到他们自己的线程
    return {"owner": ctx.user.identity}

# 运行创建、流式传输、更新等
# 这个处理程序将优先于通用的 @auth.on 处理程序和 @auth.on.threads 处理程序
@auth.on.threads.create_run
async def on_run_create(
    ctx: Auth.types.AuthContext,
    value: Auth.types.threads.create_run.value
):
    metadata = value.setdefault("metadata", {})
    metadata["owner"] = ctx.user.identity
    # 继承线程的访问控制
    return {"owner": ctx.user.identity}

# 助手创建
@auth.on.assistants.create
async def on_assistant_create(
    ctx: Auth.types.AuthContext,
    value: Auth.types.assistants.create.value
):
    if "assistants:create" not in ctx.permissions:
        raise Auth.exceptions.HTTPException(
            status_code=403,
            detail="User lacks the required permissions."
        )

请注意,我们在上面的示例中混合了全局和特定资源的处理程序。由于每个请求都由最具体的处理程序处理,因此创建 thread 的请求将匹配 on_thread_create 处理程序,但不会匹配 reject_unhandled_requests 处理程序。然而,对 thread 进行更新的请求将由全局处理程序处理,因为我们没有为该资源和操作设置更具体的处理程序。

过滤操作

授权处理程序可以返回 None、布尔值或一个过滤字典。 - NoneTrue 表示“授权访问所有底层资源” - False 表示“拒绝访问所有底层资源(引发 403 异常)” - 一个元数据过滤字典将限制对资源的访问

一个过滤字典是一个键与资源元数据相匹配的字典。它支持三种操作符:

  • 默认值是精确匹配的简写形式,即 "$eq"。例如,{"owner": user_id} 将只包括元数据中包含 {"owner": user_id} 的资源。
  • $eq: 精确匹配(例如,{"owner": {"$eq": user_id}})- 这等同于上面的简写形式,{"owner": user_id}
  • $contains: 列表成员(例如,{"allowed_users": {"$contains": user_id}})这里的值必须是列表中的一个元素。存储在资源中的元数据必须是列表/容器类型。

具有多个键的字典使用逻辑 AND 过滤器进行处理。例如,{"owner": org_id, "allowed_users": {"$contains": user_id}} 将只匹配元数据中 "owner" 为 org_id 并且 "allowed_users" 列表包含 user_id 的资源。 有关更多信息,请参阅此处的参考 here

常见访问模式

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

单所有者资源

这种常见模式允许你将所有线程、助手、定时任务和运行限定到单个用户。它适用于常见的单用户用例,例如常规的聊天机器人风格应用。

@auth.on
async def owner_only(ctx: Auth.types.AuthContext, value: dict):
    metadata = value.setdefault("metadata", {})
    metadata["owner"] = ctx.user.identity
    return {"owner": ctx.user.identity}

基于权限的访问

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

# 在你的认证处理程序中:
@auth.authenticate
async def authenticate(headers: dict) -> Auth.types.MinimalUserDict:
    ...
    return {
        "identity": "user-123",
        "is_authenticated": True,
        "permissions": ["threads:write", "threads:read"]  # 在认证中定义权限
    }

def _default(ctx: Auth.types.AuthContext, value: dict):
    metadata = value.setdefault("metadata", {})
    metadata["owner"] = ctx.user.identity
    return {"owner": ctx.user.identity}

@auth.on.threads.create
async def create_thread(ctx: Auth.types.AuthContext, value: dict):
    if "threads:write" not in ctx.permissions:
        raise Auth.exceptions.HTTPException(
            status_code=403,
            detail="Unauthorized"
        )
    return _default(ctx, value)


@auth.on.threads.read
async def rbac_create(ctx: Auth.types.AuthContext, value: dict):
    if "threads:read" not in ctx.permissions and "threads:write" not in ctx.permissions:
        raise Auth.exceptions.HTTPException(
            status_code=403,
            detail="Unauthorized"
        )
    return _default(ctx, value)

受支持的资源

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

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

最具体的匹配处理程序将被使用。例如,@auth.on.threads.create 会优先于 @auth.on.threads 来处理线程创建。 如果注册了一个更具体的处理程序,则对于该资源和操作,更通用的处理程序将不会被调用。

类型安全

每个处理程序在 Auth.types.on.<resource>.<action>.value 中都提供了其 value 参数的类型提示。例如:

@auth.on.threads.create
async def on_thread_create(
    ctx: Auth.types.AuthContext,
    value: Auth.types.on.threads.create.value  # 线程创建的特定类型
):
    ...

@auth.on.threads
async def on_threads(
    ctx: Auth.types.AuthContext,
    value: Auth.types.on.threads.value  # 所有线程操作的联合类型
):
    ...

@auth.on
async def on_all(
    ctx: Auth.types.AuthContext,
    value: dict  # 所有可能操作的联合类型
):
    ...
更具体的处理程序提供更好的类型提示,因为它们处理的操作类型更少。

支持的操作和类型

以下是所有支持的操作处理程序:

资源 处理程序 描述 值类型
Threads @auth.on.threads.create 创建线程 ThreadsCreate
@auth.on.threads.read 获取线程 ThreadsRead
@auth.on.threads.update 更新线程 ThreadsUpdate
@auth.on.threads.delete 删除线程 ThreadsDelete
@auth.on.threads.search 列出线程 ThreadsSearch
@auth.on.threads.create_run 创建或更新运行 RunsCreate
Assistants @auth.on.assistants.create 创建助手 AssistantsCreate
@auth.on.assistants.read 获取助手 AssistantsRead
@auth.on.assistants.update 更新助手 AssistantsUpdate
@auth.on.assistants.delete 删除助手 AssistantsDelete
@auth.on.assistants.search 列出助手 AssistantsSearch
Crons @auth.on.crons.create 创建定时任务 CronsCreate
@auth.on.crons.read 获取定时任务 CronsRead
@auth.on.crons.update 更新定时任务 CronsUpdate
@auth.on.crons.delete 删除定时任务 CronsDelete
@auth.on.crons.search 列出定时任务 CronsSearch
关于 Runs

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

下一步

关于实现细节: