会话与安全

文档会话和访问控制

Doconut 令牌标识一个已打开的文档。如果任何携带该令牌的请求被服务,获取它的人就可以读取每一页。此页面解释了文档会话包含什么、会话的存活时间以及 DocImage.axd 如何决定谁可以查看其页面——在单服务器和 Web 农场环境下。

文档会话包含的内容

每一次成功的 OpenDocumentAsync 都会创建一个会话,保存在内存中:

  • 已加载的格式查看器 —— 持有已解析文档的引擎实例,
  • 每页状态 —— 旋转、翻转以及用户在小部件中应用的批注数据,
  • 可选的 搜索索引,在首次搜索时构建(或从预构建的 .srh 文件加载),
  • 会话 水印,来源于 DocOptions.Watermark。

生命周期

会话在 滑动窗口 中过期:DocOptions.TimeOut 分钟(默认 60),每次携带令牌的请求都会重置。当会话结束——无论是因超时还是通过 CloseDocument(token)——其文档引擎会被释放,内存会立即被回收。

csharp
// A short-lived session for a one-shot preview
var token = await viewer.OpenDocumentAsync(path, new DocOptions { TimeOut = 10 });

使用已过期令牌的请求会返回一张错误图片,内容为 Document session not found. Please re-open document. 页面必须重新打开文档以获取新令牌。在单服务器环境下,回收应用程序池也会结束所有会话。

页面仅对打开它们的会话提供

当 UnsafeMode = false(默认值)时,打开文档会将 ASP.NET 会话(即发起打开请求的会话)写入一个 secure-{token} 条目。DocImage.axd 本身需要会话状态,并且仅在请求的会话拥有该条目时才提供页面。任何其他浏览器使用相同令牌都会收到错误图片,内容为 You Are Not Authorized To View This Page.

csharp
Doconut.DoconutHost.Initialize(options =>
{
    // false (the default): DocImage.axd serves a page only to the ASP.NET session that opened
    // the document. The handler asks for session state itself; it only needs the site not to
    // turn session state off in Web.config.
    options.UnsafeMode = false;
});

三个实际影响:

  • 打开端点需要会话状态。 MVC 控制器和 Web Forms 页面默认拥有会话状态。自定义 IHttpHandler 除非实现 System.Web.SessionState.IRequiresSessionState,否则没有会话。没有会话的打开操作没有任何授权信息,查看器会在每页显示 “You Are Not Authorized To View This Page.”。
  • 会话状态必须保持开启。 Web.config 中的 <sessionState mode="Off" /> 会同时关闭授权写入和检查,导致所有页面被拒绝。
  • 浏览器必须发送会话 Cookie。 小部件的请求是同源的,会自动携带 ASP.NET_SessionId。没有 Cookie 桶的客户端——脚本、服务器到服务器的调用、或会剥离 Cookie 的跨域设置——都会被拒绝。这正是该特性的工作方式。

UnsafeMode = true 会关闭此检查:任何携带令牌的请求都会被服务。这是全局开关,对每个文档都生效,且无法单独覆盖。请保持为 false。

撤销

CloseDocument(token) 不仅释放内存,还会从当前请求的会话中移除 secure-{token} 条目并撤销令牌,使已关闭的令牌立即失效。

Web 农场:签名票据

在农场中,后续请求可能落在从未见过该文档的服务器上,使用不同的工作进程和自己的 ASP.NET 会话。会话授权无法随之转移。分布式模式用 签名票据 替代:票据绑定到浏览器,所有节点共享同一个文档会话存储和签名密钥,DocImage.axd 在票据对该令牌、该浏览器以及(当用户已登录时)该用户有效时才接受请求。

每个节点的相同配置:

csharp
// Global.asax.cs - Application_Start
Doconut.DoconutHost.Initialize(
    options => options.UnsafeMode = false,     // the ticket check only runs when this is false
    services => services.AddDoconutDistributed(d =>
    {
        d.Store             = CloudLocation.FileShare;
        d.FileShareRootPath = @"\\fileserver\doconut-shared";  // a folder every node can reach
        d.SigningKey        = farmKey;                          // at least 32 bytes, the SAME on every node
    }));

打开端点随后在打开前将票据绑定到浏览器,并随令牌一起返回票据:

  • 设置 DocOptions.BrowserId = DoconutFarmBinding.EnsureBrowserId(System.Web.HttpContext.Current, securityOptions) —— 这会生成与票据关联的 doconut-client Cookie —— 并且 DocOptions.UserId = DoconutFarmBinding.ResolveUserId(System.Web.HttpContext.Current);
  • 在 OpenDocumentAsync 之后,返回 viewer.AccessToken 与令牌一起;
  • 在页面中,将两者都传递给小部件:objViewer.View(token, access)。

示例包中的 Doconut.MvcSample.WebFarm 是一个完整可运行的农场节点,旁边的迁移指南逐步解释了每一步。

请将签名密钥从源码管理中排除:它既用于签名票据,也用于加密共享存储中的文档。拥有不同密钥的节点会拒绝所有其他节点签发的票据。

使用真实浏览器或保持 Cookie 的脚本测试农场。没有 doconut-client Cookie 的客户端不会收到 401,而是返回状态 200、约 2 KB 的 “未授权” 错误图片,而真实页面的大小通常是数十到数百 KB。

生产检查清单

  • 保持 UnsafeMode = false(默认)。
  • 从拥有会话状态的请求打开文档,切勿关闭会话状态。
  • 确保会话 Cookie 能到达 DocImage.axd(同源、HTTPS、允许的 SameSite 策略)。
  • 在农场中使用分布式模式,并使用所有节点共享的同一签名密钥。
  • 当用户离开文档时调用 CloseDocument——内存和安全性都会受益。
  • 永不记录或共享令牌;将其视为短期凭证。

此页面对您有帮助吗?