DooTask WebDAV 使用指南:挂载「我的文件」与「共享文件」,用应用密码安全连接

原创2026-10-14 21:59:301,863 阅读
文章标签:后端前端桌面应用协同办公

DooTask WebDAV 使用指南:挂载「我的文件」与「共享文件」,用应用密码安全连接

DooTask 为文件模块提供了标准 WebDAV 服务,用户可以在 Windows 文件资源管理器、macOS Finder、Linux 挂载工具以及支持 WebDAV 的办公软件中,直接访问自己的「我的文件」和他人共享的「共享文件」,并保持与网页端一致的权限、历史版本与并发保护语义。读完本文,你将掌握如何创建一次性展示的 WebDAV 应用密码、正确填写连接地址与认证信息、理解 files/ 与 shared/ 的目录映射规则,以及如何安全地管理、撤销和删除设备凭据。

一、WebDAV 是什么,在 DooTask 中能做什么

WebDAV(Web Distributed Authoring and Versioning)是构建在 HTTP 之上的文件访问与编辑协议,允许客户端像操作本地磁盘一样对远程文件进行查询、下载、上传、创建、复制、移动、重命名与删除。DooTask 将该协议用于文件模块,让「我的文件」和「共享文件」可以通过操作系统原生文件管理器直接挂载访问。

从 docs/webdav-design.md 的设计目标看,WebDAV 访问与网页端保持以下行为一致:

  • 文件和目录权限一致;
  • 写入会生成新的文件历史版本;
  • 移动、重命名、删除会触发现有消息推送和搜索同步;
  • 共享文件保持现有所有者、创建者、只读、读写权限语义;
  • 凭据可以独立创建、过期和撤销,不影响主账号登录;
  • 并发写入遵循 WebDAV 锁和 HTTP 条件请求,避免静默覆盖。

需要特别说明的是:WebDAV 暴露的是由 files、file_contents、file_users 等数据表构成的虚拟文件系统,并不直接暴露服务器上的物理上传目录;协议层也只包含「我的文件」和「共享文件」两类内容,聊天附件、任务附件与项目协作文件聚合视图不在首版范围内(详见 docs/webdav-design.md 第 2 节「范围边界」)。

二、使用前的前置条件

在开始连接之前,请确认以下前提:

  1. 管理员已启用 WebDAV,并且当前用户处于允许使用的成员范围内;
  2. 使用 HTTPS 地址访问 DooTask:服务端只允许通过 HTTPS 传输,非 HTTPS 环境下协议请求会被拒绝(生产环境返回 403 HTTPS is required,创建凭据的接口也会直接报错);
  3. 不能使用 DooTask 登录密码连接:WebDAV 必须使用单独创建的「应用密码」。

2.1 管理员侧的开启与策略

管理员在「系统设置 > 文件设置 > WebDAV」区域完成启用与策略配置,可配置项及默认值如下(对应 app/Services/WebDav/WebDavConfig.php 中 get() 与 normalizeAdminInput() 的实现):

配置项 含义 默认值/取值范围
webdav_enabled 全局开关 close(默认关闭)
webdav_permission_type 允许范围 all(全员)或 appoint(指定成员)
webdav_permission_userids 指定成员名单 空数组
webdav_max_credentials 每用户有效凭据上限 默认 5,1~20
webdav_default_expire_days 默认有效期 默认 90 天
webdav_max_expire_days 最大有效期 默认 365 天,上限 3650 天
webdav_max_file_bytes 单文件上限 默认 1 GB(协议硬上限)
webdav_copy_max_nodes 目录复制节点数上限 默认 1000,上限 10000
webdav_audit_retention_days 操作审计保留天数 默认 90 天,7~365 天

其中协议硬限制与默认值统一放在 config/dootask.php 的 webdav 段:

'webdav' => [
    'max_file_bytes' => 1024 * 1024 * 1024,
    'lock_timeout_seconds' => 1800,
    'lock_max_timeout_seconds' => 7200,
],

lock_timeout_seconds 为默认锁超时(30 分钟),lock_max_timeout_seconds 为客户端可请求的最大锁时长(2 小时)。管理员侧在启用前还会做「文件路径冲突审计」:若同一父目录下存在同名同扩展名的文件记录(pid + userid + name + ext 分组计数大于 1),则禁止开启并列出待处理文件(WebDavConfig::pathConflictCount() / pathConflicts(),对应 app/Services/WebDav/WebDavConfig.php)。

全局关闭 WebDAV 时,所有 DAV 请求立即返回 503 Service Unavailable,但凭据记录会保留,以便重新启用后继续使用;管理员也可以另行执行全部撤销。

三、创建连接凭据(应用密码)

3.1 操作步骤

  1. 打开左侧栏「文件」,停留在「我的文件」或「共享文件」;
  2. 点击页面右上角加号右侧的圆形「···」图标,在菜单中选择「WebDAV」;
  3. 在弹出窗口中点击「新建」(或「创建应用密码」),填写用于识别客户端的设备名称和有效期;
  4. 创建后立即保存弹窗中的三项信息:连接地址、用户名和应用密码。应用密码只显示一次,关闭弹窗后无法再次查看。

3.2 凭据的生成原理

从源码看,应用密码的生成与存储位于 app/Models/WebDavCredential.php 的 issue() 方法:

$secret = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$credential = self::createInstance([
    'public_id' => 'dtw_' . strtolower((string) Str::ulid()),
    'name' => $name,
    'password_hash' => Hash::make($secret),
    'password_suffix' => substr($secret, -4),
    'expires_at' => $expireDays ? now()->addDays($expireDays) : null,
]);

即:

  • 用户名(public_id):以 dtw_ 前缀开头的 ULID 公开标识,不含任何秘密信息,用于 Basic 认证的用户名;
  • 密码:32 字节加密随机数生成的 base64url 字符串;
  • 数据库只保存 Laravel Hash::make() 的哈希结果和密码末四位(password_suffix),不保存明文或可逆密文;创建响应只返回一次完整密码。

选择独立公开用户名而不是邮箱的原因(见 docs/webdav-design.md 第 7.1 节):凭据可以独立撤销;无需处理 LDAP/SSO 密码;认证查询可以命中唯一索引;不会泄露登录邮箱。

四、连接地址与认证方式

4.1 服务地址

WebDAV 服务地址固定为:

https://{你的DooTask域名}/dav/

根目录使用不随语言变化的固定 ASCII URI 段:files/ 与 shared/(见 docs/webdav-design.md 第 4 节)。路由注册于 routes/web.php,/dav/{path?} 同时匹配 OPTIONS、PROPFIND、PROPPATCH、HEAD、GET、PUT、MKCOL、COPY、MOVE、DELETE、LOCK、UNLOCK 共 12 种方法,统一由 WebDavProtocolController 承载。

4.2 Basic 认证与安全约束

连接采用 HTTP Basic 应用密码认证,但服务端只允许通过 HTTPS 传输。入口控制器(app/Http/Controllers/WebDavProtocolController.php)的处理顺序是:

  1. 检查全局开关,关闭时返回 503 WebDAV is disabled.;
  2. 检查是否 HTTPS,非 HTTPS(且非 local/testing 环境)返回 403 HTTPS is required.;
  3. 通过 WebDavAuthenticator 完成 Basic 认证,失败返回 401 并携带 WWW-Authenticate: Basic realm="DooTask WebDAV", charset="UTF-8" 响应头;
  4. 认证成功后构建 Sabre Server 处理协议请求,并把请求方法、URI、状态码、耗时、IP、User-Agent 写入 webdav_operation_logs 操作审计表。

认证层还包含以下防护(app/Services/WebDav/WebDavAuthenticator.php):

  • 按 IP + 公开用户名 组合限流,同一组合 1 分钟内失败超过 10 次返回 429 并带 Retry-After: 60;
  • 使用 Hash::check 校验密码,错误响应不区分「用户名不存在、密码错误、凭据已撤销」;
  • 认证成功后使用 RateLimiter::clear 清空计数;
  • 最近使用时间、IP、客户端标识每 5 分钟最多写库一次(last_used_at 距上次不足 5 分钟则跳过更新)。

因此,在操作系统中添加网络位置时,服务器地址、用户名、密码必须使用弹窗给出的三项:弹窗中的连接地址 + 用户名 + 一次性应用密码。

五、连接后的目录结构

根目录固定包含两个虚拟集合:

/dav/
├── files/
└── shared/

5.1 files/:我的文件

files/ 映射当前用户「我的文件」的根(对应数据层 pid = 0 AND userid = 当前用户),允许按现有文件权限进行新建、读取、修改、移动和删除。后续每一级路径按「父目录 + 完整文件名」解析,文件夹按 type = folder 判断、不携带扩展名语义(详见 docs/webdav-design.md 第 4.2 节,实现见 app/Services/WebDav/Nodes/FilesRoot.php)。

5.2 shared/:共享文件

shared/ 列出别人共享给当前用户(以及当前用户自己共享出去、仍归自己所有的顶层项)的资源。共享顶层名称带有 [#文件ID] 标识,例如:

/dav/shared/产品资料 [#128]/设计/首页.fig
/dav/shared/预算表 [#356].xlsx

这个 [#ID] 用于避免不同所有者共享同名资源时产生歧义(同名共享根冲突的决策,见 docs/webdav-design.md 第 4.3 节与第 20 节风险决策表)。解析时同时验证 ID 与当前名称,旧路径返回 404 而非永久重定向,避免 DAV 客户端缓存错误。实现位于 app/Services/WebDav/Nodes/SharedRoot.php:getChild() 用正则 / \<a href="https://link.gitcode.com/i/5c715b24c825de402f1bf3ff477593bf" target="_blank">#(\d+)\</a>?$/u 提取共享根 ID,再校验完整名称一致性。

因此,不要手工删掉 [#文件ID] 这个标识,否则路径将无法解析。另外,displayname 属性返回的是不带 [#ID] 的原始名称;你自己共享出去的文件仍位于 files/,不会在 shared/ 中重复展示。

5.3 共享权限语义

共享权限 WebDAV 能力
只读 只能浏览和下载(PROPFIND、HEAD、GET,可作为复制来源)
读写 可保存修改,也可以在共享文件夹中创建内容

与 DooTask 权限级别的对应关系(docs/webdav-design.md 第 6.1 节):

  • -1 无权限:统一表现为 404,不泄露资源存在性;
  • 0 只读:查询、下载;
  • 1 读写:只读能力 + 创建、更新、重命名、加锁;不能删除或移走他人资源;
  • 1000 所有者或创建者:全部能力,包括 DELETE、MOVE。

其他关键规则:shared/ 虚拟根永远不可写;用户自己在共享目录中创建的子项(created_id 为当前用户)可以由该用户移动和删除;不允许把 shared/ 顶层项 MOVE 到 files/,也不允许通过 DAV 改变共享关系;从共享目录 COPY 到 files/ 时新副本归当前用户所有。每个请求实时读取当前共享权限,共享被撤销后,后续请求立即变为 404。

六、支持的 WebDAV 方法与协议能力

6.1 方法矩阵

方法 作用 行为说明
OPTIONS 能力发现 返回 DAV: 1, 2、允许方法列表
PROPFIND 查询资源属性 支持 Depth: 0/1,Depth: infinity 返回 403,避免全树扫描
PROPPATCH 设置死属性 支持非保护属性,系统属性返回 403
HEAD 文件元数据 与 GET 相同响应头,不返回内容
GET 下载文件 支持 Range、ETag、Last-Modified 和条件读取
PUT 新建或覆盖文件 流式写入;覆盖生成新的历史版本
MKCOL 创建文件夹 请求体非空返回 415
COPY 复制资源 文件及目录,遵循 Depth、Destination、Overwrite
MOVE 移动或重命名 同一 DAV 服务内;跨 files/shared 边界按权限判断
DELETE 删除资源 进入现有文件回收站;递归删除目录
LOCK 创建或刷新写锁 支持排他写锁和 lock-null 资源
UNLOCK 释放写锁 校验 Lock-Token 和凭据所属用户

不支持的方法返回 405 Method Not Allowed,并带 Allow 响应头。DAV 路由的异常由专用异常映射器转换为 XML 或空响应,不会落入全局 ApiException JSON 响应。

6.2 覆盖保存与历史版本

通过 WebDAV 覆盖已有文件时,服务端保留 files.id 不变,新增一条 file_contents 记录,因此分享链接、历史记录和最近访问仍指向原文件(app/Services/FileSystem/FileSystemService.php 的 replaceFile():先 lockForUpdate 锁定文件行、更新 size/hash/updated_at,再写入新内容版本)。新文件的类型(document/word/excel/picture 等)由统一类型映射服务根据扩展名推导,不在 DAV 层重复维护匹配列表。

6.3 原子保存兼容(Office 场景)

桌面办公客户端常采用「上传临时文件,再 MOVE 覆盖目标」的方式保存。服务端通过 WebDavMovePlugin 特殊处理:当 MOVE 的目标已存在且 Overwrite: T、用户对目标有写权限并持有所需锁时,将临时文件的最新内容作为目标文件的新版本,保留目标 files.id,删除临时文件记录后返回 204 No Content。这样共享读写用户无需拥有目标文件的删除权限,也能完成 Office 原子保存(详见 docs/webdav-design.md 第 5.5 节)。

6.4 锁与并发保护

  • 客户端可通过 LOCK/UNLOCK 获取排他写锁,默认 30 分钟,可请求 1 分钟至 2 小时(app/Services/WebDav/WebDavLockBackend.php);
  • 锁记录存放在 webdav_locks 表,URI 加命名空间前缀(user:{userid}: 或 shared:)区分归属,并按 uri_hash(SHA-256)精确查询;
  • 即使客户端未主动 LOCK,服务端仍会通过短期互斥锁保证同一父目录命名空间与同一文件版本写入的事务一致性;
  • 撤销凭据、撤销共享、用户停用或移出允许范围时,相关锁会被同步清理(app/Models/WebDavCredential.php 的 revoke() 会 WebDavLock::whereCredentialId(...)->delete());
  • 文件强 ETag 形如 "f-{file_id}-v-{latest_file_content_id}",覆盖写入必须处理 If-Match、If-None-Match 与 DAV If 头,条件不满足返回 412 Precondition Failed(FileSystemService::etag(),app/Services/FileSystem/FileSystemService.php)。

6.5 常用状态码速查

场景 状态码
未提供或无效凭据 401 Unauthorized
认证失败超限 429 Too Many Requests
全局关闭或维护中 503 Service Unavailable
无查看权限或资源不存在 404 Not Found
有查看权限但无写权限 403 Forbidden
ETag/DAV If 条件失败或同名目标不允许覆盖 412 Precondition Failed
资源被其他锁占用 423 Locked
父目录不存在 409 Conflict
文件夹达到 300 项上限 507 Insufficient Storage
文件或复制规模超限 413 Content Too Large 或 507
PUT 新建成功 / 覆盖成功 201 Created / 204 No Content

其中文件夹每目录最多 300 项、文件名 1~200 字符、禁止 \ / : * ? " < > | 等规则,均由 app/Services/FileSystem/FileSystemService.php 统一校验并映射为对应 DAV 状态码(app/Services/WebDav/WebDavExceptionMapper.php)。

七、管理设备与凭据生命周期

再次进入「文件」右上角「···」→「WebDAV」,可以看到凭据列表,每项包含:设备名称、状态、凭据尾号(末四位)、有效期、创建时间、最后使用时间和最近使用的客户端/IP(对应 api/file/dav/credentials 接口与 FileDavController::dav__credentials)。

凭据可能处于四种状态(app/Models/WebDavCredential.php 的 status()):

  • active:可正常认证;
  • expired:超过 expires_at;
  • revoked:用户或管理员撤销;
  • disabled:全局开关、允许范围或用户状态导致不可用(不改变凭据记录本身)。

对有效凭据可以点击撤销按钮:撤销后,使用该密码的设备会立即认证失败,未过期的锁也会被清理。撤销接口是幂等的,重复撤销返回成功与原 revoked_at。

已撤销或已过期的凭据可以永久删除,且删除后无法恢复;永久删除凭据不会删除既有 WebDAV 操作审计日志。有效凭据必须先撤销才能删除(app/Services/WebDav/WebDavCredentialService.php 的 deleteInactive():对 isActive() 的凭据抛出「有效的应用密码请先撤销」,删除前写入一条不含秘密信息的 CREDENTIAL_DELETE 操作审计,并同步删除该凭据创建的锁)。

管理员侧还可以查看连接用户、最近失败与写操作审计,并可停用某个用户的全部 WebDAV 凭据(api/file/dav/userrevoke)。

八、常见问题(FAQ)

看不到入口或提示未启用? 管理员尚未开启 WebDAV,或当前用户不在指定成员范围内。请先联系管理员检查「系统设置 > 文件设置 > WebDAV」的开关与允许范围;全局关闭时用户端弹窗只展示不可用状态。

无法创建密码? 确认当前页面是 HTTPS,并检查是否达到每人应用密码数量上限(默认每人最多 5 个有效凭据,可撤销旧凭据后再创建)。另外,本地调试环境(local/testing)允许非 HTTPS,但生产环境强制 HTTPS。

密码忘记了? 应用密码不可找回:数据库只保存哈希与末四位,创建弹窗关闭后完整密码不再出现。请撤销旧密码并新建一个。

文件保存失败? 依次检查:共享权限是否允许写入、文件是否被其他客户端锁定(返回 423 Locked)、名称是否合法(1~200 字符且不含 \ / : * ? " < > | 等非法字符)、文件大小是否超过单文件上限,以及是否存在同名冲突(返回 412/409)。

连接后看不到「协作文件」? 这是设计使然:WebDAV 只提供「我的文件」(files/)和「共享文件」(shared/),不包含「协作文件」,文件页面右侧「···」菜单在协作文件板块也不显示 WebDAV 入口(见 docs/webdav-design.md 第 15.2 节)。

九、源码参考与延伸阅读

如果你想深入理解协议实现,可以按以下路径继续阅读当前仓库:

如果需要在客户端中进行协议验证,可直接用 curl 以 Basic 认证发起请求,例如:

# 查看挂载根目录属性(用户名与密码替换为弹窗给出值)
curl -u "dtw_XXXXXXXX:你的应用密码" -X PROPFIND \
  -H "Depth: 1" \
  "https://你的DooTask域名/dav/"

需要注意的是,生产环境连接必须使用 HTTPS;Windows 11 文件资源管理器、macOS Finder、Linux davfs2 以及 Microsoft Office/LibreOffice 均按本文的连接方式即可完成挂载与日常文件操作。

登录后查看全文
dootask