跳转至

LDAP 自定义认证与 Func 属性加工(部署版)

部署版可以将 LDAP 认证交给客户维护的 Func 函数,也可以保留内置 LDAP 认证,仅由 Func 加工认证成功后的用户属性。两种方式均复用现有 LDAP 登录入口和 SSO 账号、工作空间及角色映射规则。

使用前,请确认 Studio 后端版本已支持 LDAPClientSet.authMode、LDAPClientSet.external 以及原生 Func 函数接口的响应格式。启用 secret 加密还需配套支持无品牌 v2 协议的 Studio 与 Func core;仅更新下载脚本不能使旧 Studio 支持 v2。现有部署保持 authMode: builtin(或不配置),且不启用非空 server.hosts 时,继续使用原单地址链路;无需为升级配置 Func 或 secret。

选择模式

authMode 密码由谁验证 Func 收到什么 Func 成功时返回什么
builtin(默认) Studio 内置 LDAP 不调用 Func 不适用
builtin_transform Studio 内置 LDAP;失败时不调用 Func 用户名、已通过认证的 userinfo,不含本次登录密码 用户属性的顶层补丁
external 客户 Func 用户名、密码 完整 userinfo

builtin_transform 共用现有 LDAP 连接、TLS、搜索和映射配置,也可同时配置 多域控。它使用新认证执行链路;即使未配置 hosts,也应按多域控文档中的新链路要求检查 SIMPLE 用户密码验证、SAFE_SYNC 和启动环境。

external 不再连接 Studio 配置中的 LDAP 服务器,客户 Func 负责实际认证。Studio 仍使用 LDAPClientSet.mapping 解释返回属性;不要由函数返回 Studio token、账号 UUID 或角色 UUID 来代替用户信息。

两种外部模式按需选择,每次登录只调用 external.url 指定的函数。external 指向 authenticate 时不会自动再调用 transform;如需由客户函数同时完成认证和属性加工,可在认证成功后加工完整 userinfo 再返回。若保留 Studio 内置 LDAP 认证、仅自定义属性,请使用 builtin_transform 并将 URL 指向 transform。

准备 Func 脚本

以下示例包含标准 LDAP 密码验证和属性加工两个入口,可作为客户脚本的起点:

  • 下载 core.py:LDAP 连接、协议编解码和属性校验。
  • 下载 main.py:Func 函数入口,包含 authenticate(body="") 和 transform(body="")。
  • 下载来源与校验信息。两份脚本与 Studio 实现同源;升级协议时应配套更新。

  • 在客户可管理的 Func 环境中创建脚本集,例如 ldap_client_demo。

  • 创建子脚本,ID 为 core,粘贴 core.py;再创建 ID 为 main 的子脚本,粘贴 main.py。main 中的 import __core as core 引用同脚本集的 core,不要改成从互联网下载代码。
  • 确认 Func Python 环境已安装兼容版本的 ldap3、cryptography、requests。选择与 Func Python 版本兼容的依赖,不要覆盖运行环境中其他脚本的依赖;私有 CA 文件需挂载到 Func 执行容器。
  • 配置下述 Func 环境变量,先发布 core,再发布 main。Func 调用依赖已发布脚本,不能只保存草稿。
  • 为所需函数创建同步函数 API:外部认证使用 ldap_client_demo__main.authenticate,属性加工使用 ldap_client_demo__main.transform。函数参数名为 body,类型为字符串。复制同步调用 URL,勿使用异步任务接口。

有关环境变量、脚本发布和函数 API,参见 Func 基本概念 与 编写和调用函数。

Func 环境变量

名称 说明
LDAP_CLIENT_DEMO_SECRET 可不配置,默认空字符串。需要加密请求体时,与 Studio external.secret 配置相同的非空值;建议使用安全生成的随机共享值,不要将它写入脚本、URL 或日志。
LDAP_CLIENT_DEMO_CONFIG JSON 对象或 JSON 字符串,内容为 LDAPClientSet 内部的 LDAP 配置,不再套一层 LDAPClientSet。外部认证需要目录配置;仅属性加工也应配置与 Studio 一致的 mapping。

使用标准 LDAP 认证函数时,LDAP_CLIENT_DEMO_CONFIG 示例:

{
  "server": {
    "hosts": ["ldaps://ldap-a.example.com:636", "ldaps://ldap-b.example.com:636"],
    "connect_timeout": 6,
    "use_ssl": true,
    "get_info": "SCHEMA",
    "tls": {"validate": "CERT_REQUIRED", "ca_certs_file": "/config/ldap/ca.pem"}
  },
  "connection": {"authentication": "SIMPLE", "client_strategy": "SAFE_SYNC", "receive_timeout": 5},
  "startTls": false,
  "authTimeoutSeconds": 20,
  "baseDN": "dc=example,dc=com",
  "bindDN": "cn=studio-reader,ou=service,dc=example,dc=com",
  "bindPassword": "<服务账号密码>",
  "searchAttribute": "uid",
  "personObjectClass": "inetOrgPerson",
  "mapping": {"exterId": "uid", "username": "uid", "email": "mail", "name": "displayName"}
}

将示例替换为实际目录配置,保持身份字段与原部署一致。配置中包含 bindPassword 时,将整段 JSON 字符串以 Func 环境变量的 password 类型保存;脚本会解析该字符串,避免在普通 JSON 变量列表中回显服务密码。LDAP_CLIENT_DEMO_SECRET 也使用 password 类型。此配置使用与 Studio 多域控相同的有界切换规则,用户密码提交后不会自动换节点重试。仅属性加工时,可以只配置:

{"mapping": {"exterId": "uid", "username": "uid", "email": "mail", "name": "displayName"}}

两个模式都使用 main.py 中的 _settings(),因此属性加工也必须提供有效 JSON 对象。不要从请求参数接收服务账号、LDAP 地址或 secret。

函数 API 的访问控制

示例的两个 @DFF.API 入口均设置 require_api_auth=False,创建函数接口时可以不绑定 Func API 认证。保留同步执行、20 秒超时和 cache_result=0;不要缓存密码认证结果。

Studio 固定发送下文的 kwargs.body JSON 请求,不附加自定义认证 Header,可直连上述未绑定额外 API 认证的 Func 同步入口:

Studio → 受控内网中的 Func 同步函数 API

仅在受控内网开放演示接口,限制来源和请求速率,关闭认证参数与请求体采集;传送真实密码时使用受信任 HTTPS。取消强制 Func API 认证不会跳过函数内部的 LDAP 密码验证,也不会关闭 Func 全局认证。secret 只控制请求体的可选加密,不替代 LDAP 验证、TLS 或网络访问控制。

如果已有函数接口绑定了 API 认证,更新脚本不会自动解除绑定;需要由接口管理员按实际接入策略处理。若仍保留要求额外 Header 的接口认证,应由客户部署的受控入口适配;这不是示例直连模式的必填步骤。有关可选配置,参见 Func API 认证。

无需增加 headersFile、keyFile 或其他 Studio 认证字段,也不要把 Func 的 MCP 编程地址或开发 Access-Token 当作登录业务入口。

在 Studio 中配置

按 应用配置说明 修改 Launcher forethought-core 的 Core 配置,并重启相关服务。

外部认证

LDAPClientSet:
  enable: true
  authMode: external
  authTimeoutSeconds: 20
  # 解释 Func 返回属性;必须与既有用户及 Func 配置保持一致。
  mapping:
    exterId: uid
    username: uid
    email: mail
    name: displayName
  external:
    # authenticate 的完整同步 HTTP(S) 函数接口地址,必须可从 Studio 服务访问。
    # 不是 LDAP 地址、Func 控制台页面或 MCP 编程地址;不接受重定向。
    url: "https://func.example.com/ldap/authenticate"
    # 可不配置;若启用,与 Func 环境变量中的值相同,不随请求传送。
    secret: ""
    # 可选,HTTPS 私有 CA 在 Studio 容器内的路径;空值使用系统信任。
    caCertsFile: ""

external 模式下 Studio 不使用 server、connection、bindDN 等本地 LDAP 参数;可以保留原配置用于回退。实际目录配置由 Func 环境变量维护。

内置认证后加工属性

保留原有 server、connection、startTls、目录搜索与身份映射配置,仅将模式和外部入口改为:

# 以下为 LDAPClientSet 下需要合并的配置片段,不要用它覆盖整段 LDAP 配置。
LDAPClientSet:
  authMode: builtin_transform
  authTimeoutSeconds: 20
  external:
    # transform 函数的同步 HTTP(S) 接口地址。
    url: "https://func.example.com/ldap/transform"
    secret: ""
    caCertsFile: ""

external.url 在两种外部模式下均为必填。旧 builtin 模式忽略该配置,保持默认空 URL 不影响现有部署。URL 不包含账号密码或片段;建议使用受信任 HTTPS。HTTP 不提供传输层保护,不应用于传送真实登录密码。

authTimeoutSeconds 为新链路认证及属性准备的总时限,整数 1~60 秒,默认 20 秒;不是每个环节分别计时,也不覆盖后续账号事务。Func 示例的执行超时为 20 秒,应结合 LDAP、网关和 Studio 的总时限配置,避免无界等待。Func 失败、超时或协议错误时本次登录失败,不降级到其他认证模式。

body 请求与返回协议

Func 入口的定义始终是 def authenticate(body="") 或 def transform(body="")。Studio 以 POST 发送 JSON,外层固定为:

{"kwargs": {"body": "请求体字符串"}}

外部认证的逻辑请求对象为:

{"username": "user1", "password": "pass1"}

双方都未配置 secret 时,body 是该对象序列化一次得到的 JSON 字符串,实际 HTTP 请求示例:

{"kwargs": {"body": "{\"username\":\"user1\",\"password\":\"pass1\"}"}}

双方配置相同非空 secret 时,body 是形如 v2:... 的加密字符串。下载的 core.py 负责加解密,无需手工实现;只配置一侧、两侧不一致或密文校验失败均拒绝请求,不回退到明文。空格也属于 secret 内容,双方必须完全一致。

协议使用 HKDF-SHA256 派生 32 字节密钥,AES-256-GCM 加密;HKDF 的 salt 为空,密钥派生上下文为 ldap-forward/v2,AAD 为 ldap-forward/v2/request。传输格式为 v2: 加标准 Base64,编码内容依次为 12 字节随机 nonce、密文及 16 字节认证标签。仅加密请求中的 body,返回值仍是 JSON;该协议不提供请求过期或重放去重能力,仍需 HTTPS 和入口访问控制。

这里的 AES-GCM 认证标签用于校验加密请求体的完整性,不是额外的数字签名。Studio 不增加 sign、signature、签名 Header 或 AK/SK;客户无需准备签名密钥文件。双方未配置 secret 时,只透传 JSON 字符串,不附加签名或加密认证标签。

品牌无关协议: 加密只使用双方约定的 secret 和上述无品牌协议参数,不读取产品名称、品牌标识或品牌域名。页面文案和帮助地址按品牌替换,不参与密钥派生或密文校验。

属性加工的逻辑请求对象为:

{
  "username": "user1",
  "userinfo": {"uid": ["user1"], "mail": ["user1@example.com"], "memberOf": ["cn=readers,dc=example,dc=com"]}
}

其中不发送本次登录密码。函数返回业务对象即可,外部认证成功示例:

return {"status": "success", "userinfo": {
    "uid": ["user1"],
    "mail": ["user1@example.com"],
    "displayName": ["示例用户"]
}}

密码错误或不允许登录返回 {"status": "denied"};目录、配置或处理异常返回 {"status": "error"}。不要附带密码、异常堆栈或内部配置。原生 Func 同步 API 直接返回该业务对象,函数本身无需再套 ok、data。Studio 也兼容旧入口使用以下包装:

{"ok": true, "data": {"result": {"status": "success", "userinfo": {"uid": ["user1"], "mail": ["user1@example.com"]}}}}

Studio 要求 HTTP 200,并验证原生业务对象或上述包装中的业务结果;只要外层出现 ok,就严格校验包装,外层失败或畸形时不回退原生成功。异步任务 ID、重定向、HTML 页面或任意文本都不是有效认证结果。解密后的请求对象、用户属性对象以及 Func 业务结果各最多 256 KiB,HTTP 响应体最多 1 MiB,不要返回大批无关属性。

修改认证或属性加工逻辑

自定义认证

main.py 的 authenticate 默认调用同源 LDAP 执行器验证真实用户名和密码。若对接其他身份服务,应在 _run() 的认证分支替换实际验证步骤:先调用客户可信身份服务并确认密码或认证凭证有效,再构造 userinfo;认证拒绝返回 denied,服务异常返回 error。不要仅凭用户名存在就返回成功。

userinfo 的字段名由 mapping 决定。例如 mapping.exterId: uid 时必须返回非空 uid,还必须至少有可映射的用户名或邮箱。身份字段可以是字符串或单元素字符串数组,不能返回多个候选身份。其他授权所需组属性应沿用现有结构。

接入多个独立身份源时,由客户函数确定身份源并验证身份;必须保证 exterId 在接入范围内全局稳定且唯一。同名或同邮箱不代表同一人,不会自动合并现有 LDAP/OIDC 账号。迁移现有用户前应核对身份来源和已有绑定,避免创建重复账号或身份冲突。

属性加工

先为 ldap_client_demo__main.transform 创建同步 HTTP 接口,在 Studio 配置 authMode: builtin_transform,将 external.url 指向该接口;共用原 LDAP 连接、搜索、TLS 和 mapping。如启用请求体加密,继续使用与 Func 相同的 secret。

下载示例的 patch = {} 默认不修改任何属性。客户在 main.py 的 transform_only 分支编写实际加工规则,修改后重新发布 main,例如:

# 示例:实际值应由可信业务规则或属性服务计算,不接收客户端任意指定。
patch = {"displayName": ["业务显示名"]}
core.merge_transform(original, patch, config["mapping"])
return {"status": "success", "userinfo": patch}

同时在 Studio 与 Func 的 mapping 中配置 name: displayName,其余原映射保持一致。空补丁 {} 表示保留原属性;补丁按顶层字段合并,不是递归合并。未返回的字段保留,显式返回空值或空数组可能清空字段并改变角色映射结果。

属性加工不得改变 mapping.exterId 对应的身份值;修改其他属性也应先验证工作空间与角色映射。对嵌套对象更新时,应返回该字段所需的完整内容。不要返回密码、token、私钥或共享 secret 等凭证字段。

安全验证与回退

旧加密协议仅用于测试环境。切换至 v2 时,先暂停相关测试认证并等待在途请求及 Func 队列排空,先发布新版 core,再更新 Studio,验证后恢复。双端协议版本必须一致;不自动接受旧密文,也不回退明文。未配置 secret 的 JSON 透传不受影响。

上线前先使用合成数据和专用测试账号验证:

  1. 两侧 secret 都为空时,Func 收到 JSON 字符串;都配置相同 secret 时收到密文且认证成功;两侧不一致时失败。
  2. 正确密码成功,错误密码拒绝;Func 停止、超时、响应格式错误时拒绝,不切回旧认证。
  3. builtin_transform 的错误密码请求不进入 Func;空补丁保留身份及组属性,修改身份值的补丁被拒绝。
  4. 现有用户仍进入原账号,账号资料、工作空间和角色符合预期;仍按现有策略完成后续登录与 MFA。
  5. 检查 Studio、Func、网关、任务记录和 APM 的采集策略,确保认证请求参数、密码、secret 不进入日志或监控。示例入口不打印这些内容,但不自动关闭平台的参数与任务采集;加密也不代替该项检查。普通用户属性也应按最小必要范围返回。

排查失败时,先核对 Studio 到网关/Func 的 DNS、网络和 CA,再核对 Func API 的同步方式、访问认证、两侧 secret、映射与返回结构。查看错误类别和 trace 信息,勿通过打印完整请求体或配置排查。

需要回退时,恢复升级前的 authMode: builtin、原单地址 server.host 与原 LDAP 配置,清空或删除 server.hosts,重启相关服务。不必删除 Func 脚本。回退不自动恢复加工过的属性或合并已产生的账号,须单独核对已有账号与授权状态。

文档评价

文档内容是否对您有帮助?