- 后端
- 网络
- 存储
【免费下载链接】sftpgo
Full-featured and highly configurable SFTP, HTTP/S, FTP/S and WebDAV server - S3, Google Cloud Storage, Azure Blob
导读
本指南围绕 SFTPGo 的External Authentication(外部认证)机制,讲解如何在 Windows Server 上通过sftpgo-ldap-http-server项目,让 SFTPGo 的用户认证经由一个或多个 Active Directory 连接完成,并结合 Caddy Web 服务器实现静态文件对外公开访问。读完本文,你将掌握外部认证钩子的配置方法、PHP 端configuration.php的完整参数含义、虚拟文件夹与权限对象的设计技巧,以及一份可直接改编的 Caddyfile 示例。
本文示例源自仓库中的 examples/php-activedirectory-http-server/README.md,与本仓库的 examples/ldapauthserver(基于 Go 的 LDAP 认证 HTTP 服务示例)同属外部认证钩子的落地参考,可作为对照学习。
一、方案全景:为什么用外部认证钩子接 Active Directory
SFTPGo 是一个功能完整的 SFTP/HTTP(S)/FTP(S)/WebDAV 服务器,其用户认证默认由内置数据提供者(SQLite、MySQL、PostgreSQL、内存等)完成。但在企业场景中,账号体系通常沉淀在 Active Directory 中,直接为每个员工在 SFTPGo 内维护账号并不现实。为此,SFTPGo 提供了外部认证钩子(External Auth Hook):
配置项
external_auth_hook可以是一个 HTTP URL,也可以是一个外部程序的可执行路径,用于在用户登录时验证凭据并(按需)创建或更新用户。
该机制在源码中定义于 internal/dataprovider/dataprovider.go,相关注释明确说明:它比内置认证方式更慢,但非常灵活,任何人都可以轻松编写自己的认证钩子。
本方案的技术栈如下:
| 组件 | 作用 |
|---|---|
| SFTPGo | SFTP/WebDAV 等文件传输服务主体,位于 Windows Server 上 |
| sftpgo-ldap-http-server | 独立的外部认证 HTTP 服务,监听localhost:9001,代理到 Active Directory 查询 |
| Active Directory | 账号与组的权威来源,可配置一个或多个连接 |
| Caddy | 反向代理/静态文件服务器,将F:\files\public等目录以 HTTPS 公开 |
| configuration.php | PHP 配置文件,控制虚拟文件夹、连接输出对象、自动组模式等全部行为 |
该示例与仓库内ldapauthserver示例思路相近,但更聚焦于 Active Directory 与 Windows Server 场景:ldapauthserver基于 Go 编写(配置见 examples/ldapauthserver/ldapauth.toml),而sftpgo-ldap-http-server基于 PHP,两者都以“HTTP 外部认证服务 + SFTPGo 钩子”的方式工作。
二、部署步骤:从下载到跑通认证
原文档给出的部署流程如下,其中每一步都有对应的 SFTPGo 配置落点:
1. 下载并解压 sftpgo-ldap-http-server
从sftpgo-ldap-http-server项目发布页下载最新的 Release ZIP 包,将其解压到 Windows Server 上一个独立文件夹。ZIP 内包含:
sftpgo-ldap-http-server.exe:可执行主程序;OpenLDAP文件夹:主要用来支持 LDAP 连接启用 TLS 所需的库文件;Data文件夹:内含logs目录、configuration.example.php、functions.php、LICENSE 与 README 文件。
2. 安装 SFTPGo 并准备配置
在服务器上安装 SFTPGo(Windows 安装方式可参考 init/sftpgo.service 对应的服务化思路与官方安装包)。然后进入Data目录,将configuration.example.php复制一份并重命名为configuration.php,开始按需定制:
- 填入你自己的 LDAP/Active Directory 连接设置;
- 设置目录创建方式;
- 最低限度必须把 home 目录(home directories)配置成符合你环境的期望路径——如果你用不到虚拟文件夹或其他高级功能,可以完全不管它们。
3. 启动 HTTP 认证服务
在命令提示符(cmd)窗口中,先切换到解压 ZIP 的目录,然后直接执行:
sftpgo-ldap-http-server.exe此时程序会启动一个监听Port 9001、仅绑定 localhost 的简易 HTTP 服务器。端口同样可以在configuration.php中调整。
4. 配置 SFTPGo 的 external_auth_hook
SFTPGo 侧需要把data_provider.external_auth_hook指向该服务:
{ "data_provider": { "external_auth_hook": "http://localhost:9001/", "external_auth_scope": 0 } }仓库根目录的默认配置文件 sftpgo.json 中即包含这两个键的占位(默认值为空字符串与 0)。完成配置后,即可发起认证测试——前提是 LDAP 设置全部正确且没有中间问题。
5. 对照:仓库内置的 ldapauthserver 如何构建
若你更偏好 Go 实现,可在 examples/ldapauthserver 目录执行:
go build -ldflags "-s -w" -o ldapauthserver其配置通过 ldapauth.toml 完成,处理逻辑在 httpd/ldapauth.go:接收 SFTPGo 发来的认证请求 → 连接并绑定 LDAP → 搜索用户(要求结果恰好一条,len(sr.Entries) != 1时返回Expected one user, found: N)→ 若带公钥则比对 LDAP 中存储的公钥,否则用userdn + password二次 Bind 校验密码 → 组装 SFTPGo 用户 JSON 返回。这套请求/响应契约与 PHP 方案完全一致。
三、源码视角:SFTPGo 外部认证钩子的请求与响应契约
请求格式
当external_auth_hook以http开头时,SFTPGo 会向该 URL 发送POST application/json请求,载荷字段见 getExternalAuthResponse:
| JSON 字段 | 含义 |
|---|---|
username | 尝试登录的用户名 |
ip | 客户端 IP |
password | 明文密码 |
public_key | 公钥字符串(公钥认证时) |
protocol | 发起认证的协议 |
keyboard_interactive | 键盘交互认证时的响应 |
tls_cert | TLS 客户端证书(若启用) |
user | 已存在的用户对象(若 SFTPGo 中已有该账号,ID > 0时附带) |
钩子必须返回HTTP 200,否则会报wrong external auth http status code错误(internal/dataprovider/dataprovider.go)。
响应格式与处理逻辑
认证服务返回的是一个 SFTPGo 用户 JSON 对象。SFTPGo 侧的处理流程(doExternalAuth)值得注意:
- 空响应:表示不修改用户,仅当 SFTPGo 中已存在该用户时才放行,否则报“用户不存在”;已存在时还会用本地密码库校验密码(
checkPasswordAfterEmptyExtAuthResponse)。 - 响应中
username为空:视为认证失败,返回ErrInvalidCredentials(internal/dataprovider/dataprovider.go)。 - 响应中
username非空:该用户名(而非登录名)被用作最终账号;若与登录名不同,SFTPGo 会按返回的用户名查找已有账号,用于实现“多个登录名映射到同一 SFTPGo 账号”;用户已存在则保留配额、上次登录、TOTP 等字段后更新,不存在则自动创建(provider.addUser)。
这解释了原文档强调的“认证通过的同时,用户会被自动写入 SFTPGo 数据提供者”这一行为,也意味着configuration.php里返回的对象字段越完整,用户在 SFTPGo 中落地后越可用。
认证范围(external_auth_scope)
external_auth_scope控制钩子参与哪些认证方式(见 internal/dataprovider/dataprovider.go 与 isExternalAuthConfigured):
0:全部范围(密码、公钥、键盘交互、TLS 证书都走外部认证);1:仅密码;2:仅公钥;4:仅键盘交互;8:仅 TLS 证书;- 可组合,例如
3= 密码 + 公钥,5= 密码 + 键盘交互。取值范围0~15,越界会被重置为 0(见 internal/config/config.go)。
四、虚拟文件夹:为每个用户同时挂载“私有”与“公共”目录
原文档给出了完整的虚拟文件夹配置示例。SFTPGo 的虚拟文件夹(Virtual Folder)把用户家目录之外的磁盘路径映射为登录后的虚拟路径——定义见 internal/vfs/folder.go:虚拟路径不能是/,映射的父目录必须存在,SFTPGo 会在登录时尝试自动创建缺失的父目录。
以下配置为每位用户生成两个虚拟文件夹(#USERNAME#会被替换成实际登录用户名):
$virtual_folders['example'] = [ [ //"id" => 0, "name" => "private-#USERNAME#", "mapped_path" => 'F:\files\private\#USERNAME#', //"used_quota_size" => 0, //"used_quota_files" => 0, //"last_quota_update" => 0, "virtual_path" => "/_private", "quota_size" => -1, "quota_files" => -1 ], [ //"id" => 0, "name" => "public-#USERNAME#", "mapped_path" => 'F:\files\public\#USERNAME#', //"used_quota_size" => 0, //"used_quota_files" => 0, //"last_quota_update" => 0, "virtual_path" => "/_public", "quota_size" => -1, "quota_files" => -1 ] ];各字段含义如下:
| 字段 | 说明 |
|---|---|
name | 虚拟文件夹在 SFTPGo 中的名称,支持#USERNAME#占位符 |
mapped_path | 对应的真实磁盘绝对路径,同样支持占位符 |
virtual_path | 登录后挂载的虚拟路径(如/_private) |
quota_size | 字节配额上限,0表示无限制,-1表示计入用户总配额(与quota_files语义一致,见 internal/vfs/folder.go) |
quota_files | 文件数配额上限,0表示无限制,-1表示计入用户总配额 |
原文档特别提醒:“Public” 虚拟文件夹本身并不是公开的。仅靠 SFTPGo 侧配置,用户仍需认证登录才能访问。要真正把F:\files\public变成公开站点,必须配合下方第五节的 Caddy 配置(将站点根目录指向该磁盘路径)才能实现。
五、连接输出对象:用权限矩阵把根目录变成“只读门户”
原文档指出“魔法发生在 permissions 值里”:通过把根(home/root)目录的权限限制为仅list与download,再给两个虚拟文件夹授予全部权限,即可实现“根目录没有文件、文件都放在公私虚拟文件夹中”的目录结构。
$connection_output_objects['example'] = [ 'status' => 1, 'username' => '', 'expiration_date' => 0, 'home_dir' => '', 'uid' => 0, 'gid' => 0, 'max_sessions' => 0, 'quota_size' => 0, 'quota_files' => 100000, 'permissions' => [ "/" => ["list", "download"], "/_private" => ["*"], "/_public" => ["*"], ], 'upload_bandwidth' => 0, 'download_bandwidth' => 0, 'filters' => [ 'allowed_ip' => [], 'denied_ip' => [], ], 'public_keys' => [], ];字段要点:
status = 1:用户启用;username留空表示沿用登录用户名;home_dir留空时由服务端按configuration.php的 home 设置生成;uid/gid:Windows 场景下通常填0;max_sessions:0表示不限制并发会话数;quota_files = 100000:给根目录设置一个宽松的文件数上限,避免“无配额”误报;permissions:以目录为键的权限矩阵。SFTPGo 支持的权限字符串定义于 internal/dataprovider/user.go:"*":全部权限(list、download、upload、overwrite、delete、rename、create_dirs、create_symlinks、chmod、chown、chtimes、copy 等);"list":允许列出文件与目录(PermListItems);"download":允许下载文件(PermDownload);- 其余常用值还有
upload、overwrite、delete、rename、create_dirs、copy等;
filters.allowed_ip/filters.denied_ip:IP 白名单/黑名单,空数组表示不限制;public_keys:允许的公钥列表,可留空让认证走 AD 密码。
借助该对象,你可以做到:用户登录后看不到家目录里的任何业务文件(根目录仅可浏览/下载),而/_private与/_public两个挂载点则拥有完整读写权限——/_public再配合 Caddy 对外发布,就是典型的“私有空间 + 公开共享空间”企业文件分发模型。
六、自动组模式:按组前缀批量生成虚拟文件夹
企业里用户往往属于多个 AD 组。sftpgo-ldap-http-server支持自动把用户所属组映射为虚拟文件夹,但若不加限制,所有组都会被塞进 SFTPGo。原文档给出的做法是:开启自动组模式 + 声明允许的组前缀,只让前缀匹配的组进入,并自动剥离前缀。
// If automatic groups mode is disabled, then you have to manually add the allowed groups into $allowed_groups down below: // If enabled, then any groups you are a memberof will automatically be added in using the template below. $auto_groups_mode = true; $auto_groups_mode_virtual_folder_template = [ [ //"id" => 0, "name" => "groups-#GROUP#", "mapped_path" => 'F:\files\groups\#GROUP#', //"used_quota_size" => 0, //"used_quota_files" => 0, //"last_quota_update" => 0, "virtual_path" => "/groups/#GROUP#", "quota_size" => 0, "quota_files" => 100000 ] ]; // Used only when auto groups mode is enabled and will help prevent all your groups from being // added into SFTPGo since only groups with the prefixes defined here will be automatically added // with prefixes automatically removed when listed as a virtual folder (e.g. a group with name // "sftpgo-example" would simply become "example"). $allowed_group_prefixes = [ 'sftpgo-' ];行为说明:
auto_groups_mode = false时,必须手动把允许的组填入$allowed_groups(原文档注释指明该变量);auto_groups_mode = true时,用户所属的任何组都会按$auto_groups_mode_virtual_folder_template模板生成虚拟文件夹,但只有名称以$allowed_group_prefixes中某个前缀开头的组才会被添加;- 前缀在生成虚拟文件夹名称/路径时会自动移除:例如 AD 组名
sftpgo-example会变成example,对应挂载点/groups/example、磁盘目录F:\files\groups\example(模板中以#GROUP#占位替换); - 模板中
quota_size = 0表示组目录无配额上限,quota_files = 100000给出宽松的文件数上限。
七、Caddy 配置:公开静态文件 + HSTS 安全头 + 滚动日志 + WebDAV 反代
最后一块拼图是 Caddy。原文档提供的 Caddyfile 通过“可复用片段(snippets)”组织,包含静态文件服务、安全响应头、日志三个片段,以及站点与反向代理两组定义。
片段一:静态文件服务特性
(add_static_file_serving_features) { # Allow accessing files without requiring .html: try_files {path} {path}.html # Enable Static File Server and Directory Browsing: file_server browse # Enable templating functionality: templates # Enable Compression for Output: encode zstd gzip handle_errors { respond "<pre>{http.error.status_code} {http.error.status_text}</pre>" } }try_files {path} {path}.html:访问不带.html的路径时自动补全扩展名;file_server browse:启用静态文件服务并允许目录浏览(这正是让F:\files\public对外可见的关键指令);templates:启用模板渲染;encode zstd gzip:输出压缩;handle_errors:统一返回包含状态码/状态文本的纯文本错误页。
片段二:HSTS 与安全响应头
(add_hsts_headers) { header { # Enable HTTP Strict Transport Security (HSTS) to force clients to always # connect via HTTPS (do not use if only testing) Strict-Transport-Security "max-age=31536000; includeSubDomains" # Enable cross-site filter (XSS) and tell browser to block detected attacks X-XSS-Protection "1; mode=block" # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type X-Content-Type-Options "nosniff" # Disallow the site to be rendered within a frame (clickjacking protection) X-Frame-Options "DENY" # keep referrer data off of HTTP connections Referrer-Policy no-referrer-when-downgrade } }各头的作用:HSTS 强制客户端始终走 HTTPS(仅测试时可关闭);X-XSS-Protection开启浏览器跨站脚本过滤;X-Content-Type-Options: nosniff禁止 MIME 嗅探;X-Frame-Options: DENY防点击劫持;Referrer-Policy控制 referrer 泄露。
片段三:带路径参数的 JSON 滚动日志
(add_logging_with_path) { log { output file "{args.0}" { roll_size 100mb roll_keep 5 roll_keep_for 720h } format json #format console #format single_field common_log } }该片段通过{args.0}接收日志文件路径,单文件滚动阈值 100MB,保留 5 个历史文件、保留时长 720 小时,输出 JSON 格式(注释中保留了console与common_log备选格式)。
站点定义:公开的静态站点
public.example.com { # Site Root: root * F:\files\public import add_logging_with_path "F:\caddy\logs\public_example_com_access.log" import add_static_file_serving_features import add_hsts_headers }root * F:\files\public把站点根目录指向磁盘公共目录——注意这与第五节中/_public虚拟文件夹映射的F:\files\public\#USERNAME#不同层级:若想公开整个共享区,可将 root 指向F:\files\public,再配合file_server browse对外提供目录浏览。
反向代理定义:把 SFTPGo 的 WebDAV 暴露出去
webdav.example.com { reverse_proxy localhost:9000 import add_logging_with_path "F:\caddy\logs\webdav_example_com_access.log" }reverse_proxy localhost:9000将 Caddy 收到的请求转发给本机 9000 端口的 SFTPGo WebDAV 服务,让用户可以在浏览器/客户端中以https://webdav.example.com访问 WebDAV 能力,并复用上述日志片段。
八、排障与常见坑
原文档指出其配套视频详细演示了若干排障场景,结合源码可以归纳出这些高频问题:
- 认证服务未启动或端口不符:
sftpgo-ldap-http-server.exe默认监听 localhost:9001,若configuration.php改了端口,external_auth_hook必须同步修改,否则 SFTPGo 报 HTTP 连接错误(源码见 getExternalAuthResponse 的error getting external auth hook HTTP response)。 - 钩子返回非 200:SFTPGo 严格要求 HTTP 200,任何 4xx/5xx 都会导致登录被拒(
wrong external auth http status code)。ldapauthserver 的实现也展示了典型错误码用法:LDAP 连接失败 500、找不到唯一用户 404、公钥/密码校验失败 403。 - LDAP 搜索匹配不唯一:认证服务要求搜索恰好命中一个用户,命中 0 个或多个都会失败——请检查 BaseDN 与搜索过滤器是否过宽。
- home 目录路径未配置:这是原文档强调的“最低限度”要求,路径不对会直接导致后续虚拟文件夹的
mapped_path创建失败。 - Public 目录“不公开”:记得只有 Caddy
file_server browse+root指向对应磁盘目录,才能真正对外发布;SFTPGo 侧的/_public只是虚拟挂载,不等于公开。 - HTTPS 强制:HSTS 头一旦下发,浏览器会长期强制 HTTPS;仅内网测试时按注释建议先不要开启。
九、延伸阅读
- 本方案的同源对照实现:examples/ldapauthserver/README.md(Go 版 LDAP 认证服务,含 ldapauth.toml 配置与
go build命令); - 外部认证钩子的请求/响应契约与权限校验源码:internal/dataprovider/dataprovider.go;
- 虚拟文件夹定义与配额语义:internal/vfs/folder.go;
- 权限字符串完整清单:internal/dataprovider/user.go;
- SFTPGo 默认配置模板(含
external_auth_hook与external_auth_scope):sftpgo.json。
- 后端
- 网络
- 存储
【免费下载链接】sftpgo
Full-featured and highly configurable SFTP, HTTP/S, FTP/S and WebDAV server - S3, Google Cloud Storage, Azure Blob
相关推荐
SFTPGo 外部认证 HTTP 钩子:基于 LDAP 认证服务器(ldapauthserver)的完整实战指南
SFTPGo 外部认证 HTTP 钩子:基于 LDAP 认证服务器(ldapauthserver)的完整实战指南 导读 本文以 SFTPGo 仓库中 examp
后端网络存储SFTPGo 外部认证钩子实战:用 Go 编写 LDAP 认证程序(389ds / Active Directory)
SFTPGo 外部认证钩子实战:用 Go 编写 LDAP 认证程序(389ds / Active Directory) 本指南以 SFTPGo 仓库中的 exa
后端网络存储Apereo CAS 基于 LDAP 目录(Active Directory / OpenLDAP)的认证集成与配置指南
Apereo CAS 基于 LDAP 目录(Active Directory / OpenLDAP)的认证集成与配置指南 CAS 将 LDAP 目录(如 Act
后端认证鉴权单点登录
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考