Skip to content

feat: 新增可选的签名预览,预览请求可携带带有效期的签名令牌(JWT),默认关闭 - #806

Open
yuhaibohotmail wants to merge 1 commit into
kekingcn:masterfrom
yuhaibohotmail:feat/signed-preview
Open

yuhaibohotmail wants to merge 1 commit into
kekingcn:masterfrom
yuhaibohotmail:feat/signed-preview

Conversation

@yuhaibohotmail

Copy link
Copy Markdown
Contributor

背景

对应 #805 的功能建议。把 kkFileView 嵌入业务系统时,「谁能预览哪个文件、带什么水印」通常由业务系统决定。接入方希望预览链接有有效期,并且链接里「看哪个文件、带什么水印」由业务系统说了算。

这个 PR 增加一个可选的、默认关闭的「签名预览」。不配置任何新项时,行为、接口参数与现在完全一样。

设计

1. 预览入口校验令牌(signed.preview.enabled,默认 false)

新增过滤器 SignedPreviewFilter,挂在 /onlinePreview、/picturesPreview 上,排在 ChinesePathFilter 之后,信任站点、信任目录、水印三个过滤器之前。

  • 关闭时:第一行就把请求原样交给后面的过滤器,不读令牌、不包装请求。
  • 开启后:
    • 从请求参数(默认 token)里取令牌,校验签名、算法、有效期。令牌头里的 alg 必须与配置的算法一致;必须带数字形式的 exp;带了 nbf 时也会校验。
    • 按令牌重建请求参数:文件地址、文件名、水印、是否强制重新转换取令牌里的;浏览器带的参数只保留放行名单里的(默认 officePreviewType,tifPreviewType,page,highlightall,filePassword,key),其余丢弃。由令牌决定的参数即使写进放行名单也不从浏览器取。
    • 放行名单可以限定取值,例如 officePreviewType=image|pdf|html,page:带 = 的只放过列出的值(区分大小写;值不在其中,或同一个参数给了多个值时,丢掉这个参数,不拒绝整个请求),不带 = 的任意值。
    • 后面的控制器、FileHandlerService、水印过滤器都不用改,读到的就是重建后的参数。
    • 令牌缺失、无效、过期时返回 403 和提示页(web/signedPreviewDenied.html,三种情况文案不同)。开启了但没有配置密钥时,全部请求返回 403 并提示联系管理员。
  • 「强制重新转换」每枚令牌只执行一次。转换中的等待页(waiting.ftl)会整页刷新当前地址,令牌仍在地址里;如果每次都执行,就会每刷新一次重新转换一次。

2. 压缩包内的文件

压缩包预览页里,包内文件的预览链接是页面自己拼的。开启签名预览后,CompressFilePreviewImpl 在出页面时由 kkFileView 自己签一枚令牌交给页面,compress.ftl 拼链接时带上:

  • 令牌只对这个压缩包解开的目录有效:包内文件的地址必须在该目录下,kkCompressfileKey 必须是这个目录,kkCompressfilepath 必须落在目录内;
  • 水印沿用打开压缩包时那枚令牌里的;
  • 有效期为 signed.preview.archive.ttl,没单独配时用 signed.preview.ttl;
  • 只有当前这次压缩包预览请求通过了签名校验才签发。关闭时不签发,模板输出与现在一致。

接入方只需要为压缩包本身签一次。压缩包里再套压缩包时同样适用。

3. 取原文件时附带令牌(signed.fetch.enabled,默认 false,独立开关)

开启后,kkFileView 自己用 http/https 下载原文件时(DownloadUtils.downLoad),在请求头(默认 kk-fetch-token)里带一枚自己签发的令牌,缺省的载荷为:

{"use":"fetch","url":"<本次请求的地址>","iat":1767225000,"exp":1767225600}

请求头的写法和令牌的形状可以按文件来源一方已有的校验方式来配:

  • signed.fetch.header 加 signed.fetch.header.prefix:例如配成 Authorization 和 Bearer,发出的是 Authorization: Bearer <令牌>。请求头配成 Authorization 时以取件令牌为准,地址上的 basic 认证参数和转发来的 kk-proxy-authorization 不会覆盖它;请求头是别的名字时,这些逻辑照旧。
  • signed.fetch.claim.url:地址放在载荷的哪个字段,支持点分的对象嵌套,例如 payload.url 签成 {"payload":{"url":"<地址>"}}。
  • signed.fetch.ttl:取件令牌的有效期,没单独配时用 signed.preview.ttl。

文件来源一方可以用同一把密钥校验,确认请求来自这台预览服务、且就是为了取这个地址。/getCorsFile 是代浏览器取文件,不带这枚令牌。

令牌绑定的是原始请求地址,只发给原始地址那一方:带令牌的那次请求不自动跟随跳转;文件来源回 3xx 且 kk.enable.redirect 允许跳转时,kkFileView 另发一次不带令牌的请求去取跳转目标。取件签名关着时,跳转的处理与现在完全一样。

4. 三种令牌互不通用

接入方签的预览令牌、kkFileView 签的压缩包令牌和取件令牌用同一把密钥,靠载荷里的用途字段区分。缺省字段名是 use:预览令牌不写或写 "preview",另两种分别是 "archive"、"fetch",入口只把前者当预览令牌。

  • 字段名(signed.preview.claim.use)和三种用途的值(signed.preview.use.preview / .archive / .fetch)都可配,接入方已有自己的用途字段时不用改签发那一侧。
  • signed.preview.use.required=true 时,预览令牌必须写用途,不写就拒绝;缺省 false。
  • 三个值必须都不为空且互不相同。配置不满足时不影响应用启动,但签名预览入口拒绝全部请求(提示页写明配置有误),压缩包令牌和取件令牌也都不签发,三种令牌不会被当成同一种。

5. 字段名可以对上接入方已有的令牌格式

令牌所在的参数名、文件地址、多图地址、文件名、水印文字、强制重新转换这几个字段各有单独的配置项。其余九个水印参数和 currentUrl(对应现有的同名请求参数,现有的多图预览页面目前并不使用它)用一张映射表 signed.preview.claim.map 改名,格式为 请求参数名:令牌字段名,例如 watermarkAlpha:wmAlpha,currentUrl:cur;没写进表的仍与请求参数同名。有单独配置项的那五个字段不走这张表,表里写了也以单独那一项为准。压缩包令牌沿用水印时用的是同一套字段名。

6. 实现上的取舍

  • JWT 的签发和校验用 JDK 自带的 HMAC 加项目里已有的 Jackson 实现(JwtUtils,约 150 行),没有新增依赖。目前支持 HS256、HS384、HS512。
  • 没有改 TrustHostFilter、TrustDirFilter、OnlinePreviewController、FileHandlerService。

配置项

application.properties 与 test.properties 同步增加,都支持环境变量;已接入 ConfigConstants 与 ConfigRefreshComponent(改配置文件后动态生效)。

配置项 环境变量 默认值 说明
signed.preview.enabled KK_SIGNED_PREVIEW_ENABLED false 是否开启签名预览
signed.preview.secret KK_SIGNED_PREVIEW_SECRET false(未配置) 密钥,按 UTF-8 字节使用,建议不少于 32 个字符
signed.preview.algorithm KK_SIGNED_PREVIEW_ALGORITHM HS256 支持 HS256、HS384、HS512
signed.preview.token.param KK_SIGNED_PREVIEW_TOKEN_PARAM token 令牌放在哪个请求参数里
signed.preview.claim.url KK_SIGNED_PREVIEW_CLAIM_URL url 令牌里「文件地址」字段的名字
signed.preview.claim.urls KK_SIGNED_PREVIEW_CLAIM_URLS urls 令牌里「多图预览的图片地址」字段的名字
signed.preview.claim.filename KK_SIGNED_PREVIEW_CLAIM_FILENAME fullfilename 令牌里「文件名」字段的名字
signed.preview.claim.watermark KK_SIGNED_PREVIEW_CLAIM_WATERMARK watermarkTxt 令牌里「水印文字」字段的名字
signed.preview.claim.force KK_SIGNED_PREVIEW_CLAIM_FORCE forceUpdatedCache 令牌里「强制重新转换」字段的名字
signed.preview.claim.map KK_SIGNED_PREVIEW_CLAIM_MAP default(同名) 其余水印参数和 currentUrl 的字段名映射,请求参数名:令牌字段名,逗号分隔
signed.preview.claim.use KK_SIGNED_PREVIEW_CLAIM_USE use 令牌里「用途」字段的名字
signed.preview.use.preview KK_SIGNED_PREVIEW_USE_PREVIEW preview 预览令牌的用途值
signed.preview.use.archive KK_SIGNED_PREVIEW_USE_ARCHIVE archive 压缩包令牌的用途值
signed.preview.use.fetch KK_SIGNED_PREVIEW_USE_FETCH fetch 取件令牌的用途值
signed.preview.use.required KK_SIGNED_PREVIEW_USE_REQUIRED false 预览令牌是否必须写用途
signed.preview.allow.params KK_SIGNED_PREVIEW_ALLOW_PARAMS officePreviewType,tifPreviewType,page,highlightall,filePassword,key 开启后仍允许浏览器自己带的参数;参数名=值1|值2 表示只放过列出的值
signed.preview.ttl KK_SIGNED_PREVIEW_TTL 600 kkFileView 自己签发的令牌的有效期,秒
signed.preview.archive.ttl KK_SIGNED_PREVIEW_ARCHIVE_TTL default(用 signed.preview.ttl) 压缩包令牌的有效期,秒
signed.preview.leeway KK_SIGNED_PREVIEW_LEEWAY 30 校验有效期时允许的时钟误差,秒
signed.fetch.enabled KK_SIGNED_FETCH_ENABLED false 取原文件时是否附带令牌
signed.fetch.header KK_SIGNED_FETCH_HEADER kk-fetch-token 取件令牌放在哪个请求头里
signed.fetch.header.prefix KK_SIGNED_FETCH_HEADER_PREFIX default(不加) 请求头的值在令牌前加的前缀,与令牌之间一个空格,例如 Bearer
signed.fetch.claim.url KK_SIGNED_FETCH_CLAIM_URL url 取件令牌里地址所在的字段,支持点分嵌套,例如 payload.url
signed.fetch.ttl KK_SIGNED_FETCH_TTL default(用 signed.preview.ttl) 取件令牌的有效期,秒

配置项的名字、默认值都可以按维护者的习惯调整。

令牌里的字段

接入方签发的预览令牌(下表是缺省的字段名,除 exp、nbf 外都可以改名):

字段 必填 说明
exp 是 过期时间,Unix 秒
url /onlinePreview 必填 要预览的文件地址,明文,不需要 Base64
urls /picturesPreview 必填 图片地址,数组或用 | 分隔的字符串
currentUrl 否 原样交给现有的 currentUrl 请求参数;现有的多图预览页面目前并不使用它
fullfilename 否 文件名;地址里没有 fullfilename 参数时,按现有约定接在地址后面
watermarkTxt 否 水印文字;不写则使用配置文件里的默认水印
watermarkXSpace、watermarkYSpace、watermarkFont、watermarkFontsize、watermarkColor、watermarkAlpha、watermarkWidth、watermarkHeight、watermarkAngle 否 其余水印参数,名字与现有请求参数相同
forceUpdatedCache 否 true 时强制重新转换,每枚令牌只执行一次
nbf 否 生效时间,Unix 秒
use 缺省否 不写或写 "preview";signed.preview.use.required=true 时必须写

接入方已有自己的令牌格式时的一组配置示例(名字都是示意):

signed.preview.token.param = accessToken
signed.preview.claim.url = fileUrl
signed.preview.claim.watermark = mark
signed.preview.claim.use = kind
signed.preview.use.preview = view
signed.preview.use.archive = pack
signed.preview.use.fetch = pull
signed.preview.use.required = true
signed.preview.allow.params = officePreviewType=image|pdf,page
signed.fetch.header = Authorization
signed.fetch.header.prefix = Bearer
signed.fetch.claim.url = payload.url
signed.fetch.ttl = 120

这时预览令牌的载荷形如 {"kind":"view","fileUrl":"...","mark":"...","exp":...},取原文件的请求带 Authorization: Bearer <令牌>,令牌载荷为 {"kind":"pull","payload":{"url":"..."},"iat":...,"exp":...}。

签发示例(Java,只用 JDK;用任何 JWT 库签出来的 HS256 令牌也一样):

Base64.Encoder b64 = Base64.getUrlEncoder().withoutPadding();
long exp = System.currentTimeMillis() / 1000 + 300;   // 5 分钟有效
String header = b64.encodeToString("{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
String payload = b64.encodeToString(("{\"url\":\"https://files.example.com/demo/test.docx\","
        + "\"watermarkTxt\":\"张三 0012\",\"exp\":" + exp + "}").getBytes(StandardCharsets.UTF_8));
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String token = header + "." + payload + "."
        + b64.encodeToString(mac.doFinal((header + "." + payload).getBytes(StandardCharsets.UTF_8)));
String previewUrl = "http://127.0.0.1:8012/onlinePreview?token=" + token;

切换预览方式、翻页等仍然直接加在地址上,例如 ...onlinePreview?token=<令牌>&officePreviewType=image。

兼容性

  • 两个开关默认都是 false。关闭时 SignedPreviewFilter 直接放行原请求;压缩包页面不签发令牌,模板输出不变;取原文件的请求不多带请求头。
  • HttpRequestUtils.executeHttpRequest 原来的四个参数的方法保留,行为不变;新增一个多带 signFetch 参数的重载,只有 DownloadUtils 传 true。
  • 与现有配置的关系([FEATURE] 可选的「签名预览」:预览请求携带带有效期的签名令牌(JWT),默认关闭 #805 里留待讨论的一条,这里取的是改动最小的做法,欢迎调整):
    • trust.host / not.trust.host:叠加。令牌里的文件地址照常经过信任站点、信任目录检查。
    • kk.key:并存。key 在默认放行名单里,原有的校验照常执行。
    • /getCorsFile、/addTask:不在这次范围内,仍由各自的开关控制。
  • 开启签名预览后,首页演示页里的预览链接没有令牌,会看到提示页;这是开启后的预期表现。
  • 已知限制:
    • 「强制重新转换只执行一次」记在单个实例的内存里;多实例部署时每个实例各认一次。
    • 压缩包预览页:外层的目录页和里面嵌的包内文件预览页各画一遍水印(两遍是同一段文字),包内文件区域看起来水印是叠着的。
    • 仓库里目前没有集中列出配置项的文档,所以接入说明写在了配置文件的注释里和这份说明里;如果需要单独的文档页,我可以补。

验证

单元测试

新增 6 个测试类共 49 条(JUnit 5,不需要 LibreOffice)。测试里的令牌由测试自己按 JWT 规范用 javax.crypto.Mac 签发和校验,不经过被测的 JwtUtils。

  • SignedPreviewFilterTests(19 条):关闭时带不带令牌请求都原样通过(同一个请求对象、参数不变);开启后无令牌、过期、签名不对、载荷被替换、算法与配置不一致(含 alg=none、头里写 HS256 实际用 HS512 签)、不带 exp、未配置密钥都返回 403;时钟误差;令牌里的地址与浏览器另带的 url、urls、currentUrl、urlPath 冲突时以令牌为准;信任站点检查仍然作用在令牌里的地址上;水印与强制重新转换取令牌里的,强制重新转换每枚令牌只执行一次;放行名单以外的参数被丢弃,放行名单可配,且不能把由令牌决定的参数放回给浏览器;文件名字段;参数名、字段名可配;多图预览;其它用途的令牌不能当预览令牌用。
  • SignedPreviewOptionsTests(7 条):用途字段名和三种用途的值可配,自己签发的两种令牌用的也是配置的值;要求必须写用途时不写就拒绝;三个用途值有重复或为空、用途字段名为空时,入口全部拒绝并提示配置有误,压缩包令牌和取件令牌都不签发;映射表给其余水印参数和 currentUrl 改名,改名后原来的名字不再认,压缩包令牌沿用水印时用映射后的名字;有单独配置项的字段不受映射表影响;放行名单限定取值(不在名单里的值、大小写或空格不同的、同一参数多个值的都丢掉参数而不拒绝请求);压缩包令牌与取件令牌各自的有效期,没单独配时用共用的那一项。
  • SignedPreviewArchiveTests(5 条):关闭时压缩包页面没有令牌;不在已校验的请求里时不签发、页面照常出;开启后页面拿到的令牌内容正确、模板把它带进了链接;页面签出的令牌能打开本压缩包里的文件并带上水印;拿它去开别的压缩包、目录以外的路径(含多重编码)、普通文件地址、多图预览,以及过期后,都返回 403。
  • SignedFetchTests(11 条,起一个本机的 com.sun.net.httpserver.HttpServer 当文件来源):开启后收到的请求头能用同一把密钥验过,载荷里是本次请求的地址;请求头名可配;地址可以放在嵌套字段里;请求头的值可以带前缀;请求头配成 Authorization 时以取件令牌为准(地址上带 basic 认证参数、同时转发了 kk-proxy-authorization 的情况下只有令牌那一个值),请求头是别的名字或不签名时这些逻辑照旧;关闭或未配置密钥时不带;代浏览器取文件时不带;DownloadUtils.downLoad 下载原文件时带;文件来源回 302 时,原始地址收到令牌、跳转目标收不到、内容照常取回;取件签名关着时跳转的处理不变;不允许跳转时带令牌的请求也不跟随。
  • SignedPreviewCustomFormatTests(1 条):把整套配成上面「配置示例」那种格式,用测试自己签的令牌过预览入口,再用入口交出来的地址经 DownloadUtils.downLoad 向本机文件服务取原文件,核对文件来源收到的是 Authorization: Bearer <令牌>、令牌里地址在 payload.url、用途值和 120 秒有效期都对;缺省格式的令牌、不写用途的令牌、放在缺省参数名里的令牌在这套配置下都不通过。
  • SignedPreviewConfigTests(6 条):两份配置文件里两个开关默认关闭,各可配项的默认值与说明一致;过滤器挂在两个预览入口上,顺序在信任站点、信任目录、水印过滤器之前。

每一组都是先写测试、看到失败,再写实现:

  1. 入口、压缩包、取件这三块:先只放配置项和空实现(过滤器直接放行、不签发令牌),33 条里 Failures: 20, Errors: 5;通过的 8 条是「关闭时不变」「默认值为关闭」这一类,空实现下本来就该通过。
  2. 跳转:redirectTargetDoesNotReceiveTheFetchToken 先失败(跳转目标收到了原来那枚令牌),实现后通过。
  3. 用途、映射表、取值限定、取件令牌的形状、分开的有效期:只接好配置项、逻辑未用上时,新加的 13 条里 10 条失败;通过的 3 条是「单独配置项优先于映射表」和两份配置文件的默认值检查。
  4. 实现完成后逐处改坏再各跑一次,共 47 处,每一处都有对应的测试失败。例如:忽略开关、不校验算法或签名或过期、优先用浏览器的 url、不按放行名单过滤、不限制包内文件地址、关闭时也签压缩包令牌、带令牌的请求自动跟随跳转、不检查三个用途值是否互不相同、用途值配置有误时仍签发、请求头用追加而不是覆盖、映射表盖过单独配置项、取值限定不生效或放过多个值。49 条里 47 条至少失败过一次,剩下 2 条是 test.properties 的默认值检查,与 application.properties 那两条是同样的断言。

mvn -B -pl server test -Dtest='!ServerMainTests,!EncodingTests'(Windows,JDK 25):Tests run: 135, Failures: 0, Errors: 1。唯一的 1 条是 ArchiveDirectorySecurityTests.cachedArchivePreviewRendersItsTreeWithoutAnEnumerationRequest,它要创建文件名带双引号的文件,Windows 不允许,在未修改的 master 上同样失败(master 用 -Dtest='!ServerMainTests' 是 87 条、同样 1 条 Error),与本次修改无关。EncodingTests(1 条,按默认配置启动整个应用)在较早的一版上通过,最终版没有再单独跑它,最终版能正常启动由下面的实际启动验证。

实际启动

用这个分支打出的 kkFileView-5.0.3.jar 在 Windows 上启动(JDK 25,仓库自带的 LibreOfficePortable),另起一个只监听本机的小文件服务当文件来源并记录收到的请求头,用 curl 请求。trust.host 配成文件服务所在的主机。下面「开启」那一组是在最终版上做的;在此之前的构建上也做过同样的一遍,两次结果逐条一致(包括跳转那一条)。「不配置任何新项」那一组是在较早的构建上做的,最终版没有重做。

不配置任何新项:

请求 结果
/onlinePreview?url=<base64> 预览 txt 200,文本预览页,内容正确
同上,另带一个乱写的 token 参数 200,同样的页面
文件服务收到的请求 没有 kk-fetch-token 头

KK_SIGNED_PREVIEW_ENABLED=true、KK_SIGNED_FETCH_ENABLED=true 并配置密钥:

请求 结果
不带令牌 403,「预览链接缺少签名令牌,请回到原页面重新打开」
过期的令牌 403,「预览链接已过期,请回到原页面重新打开」
用另一把密钥签的令牌 403,「预览链接的签名令牌无效,请回到原页面重新打开」
有效令牌预览 txt 200,内容是令牌里那个文件
同上,另带 url= 指向另一个文件 200,仍是令牌里那个文件
有效令牌预览 docx 200,转换成功,PDF 预览页,水印是令牌里的
另一个 docx,令牌 + officePreviewType=image 先是等待页,再请求同一地址得到图片预览页
图片预览页上「使用PDF预览」的地址(令牌 + officePreviewType=pdf) 200,PDF 预览页
令牌里 forceUpdatedCache=true,同一地址连续请求 4 次 第 1 次重新下载并转换(等待页),后 3 次直接出图片预览页,没有再下载和转换
有效令牌预览 zip 200,目录页;页面里拼包内文件链接的脚本带着 kkFileView 签发的令牌
用页面里那枚令牌打开包内两个文件(一个在子目录) 都是 200,内容正确,水印沿用压缩包令牌里的
拿那枚令牌去开文件服务上的另一个文件 / 目录以外的路径 / 不带地址 都是 403
包内文件链接不带令牌 403
文件服务收到的取 txt、docx、zip 的请求 都带 kk-fetch-token,同一把密钥验签通过,载荷里的 url 就是被请求的地址
kk.enable.redirect = true,文件服务对某个地址回 302 预览 200、内容正确;原始地址那次请求带令牌,跳转后的那次请求不带

预览页切换预览方式的脚本(pdf.ftl 的 goForImage、officePicture.ftl 的 changePreviewType)都是在当前地址上追加或替换 officePreviewType,令牌留在地址里;上表按脚本拼出的地址请求过,都是 200。

另按一套自定义的名字与形状配置后重跑(最终版):令牌参数名、三种用途值都换成自定义的,预览令牌必须写用途,取件用 Authorization: Bearer <令牌>、地址放在嵌套字段里、有效期 120 秒,放行名单对 officePreviewType 限定取值。

请求 结果
自定义格式的令牌预览 txt(令牌里多带一个 jti) 200,内容正确,水印是令牌里的
预览 docx(令牌带 forceUpdatedCache=true 和 jti),同一地址请求 3 次 都是 200,PDF 预览页,只转换了 1 次
docx 加 officePreviewType=allImages(不在限定的取值里) 200,参数被丢弃,回到缺省的 PDF 预览
没预览过的 docx 加 officePreviewType=image 先是等待页,再请求得到图片预览页
预览 zip 200,目录页;页面里的压缩包令牌放在配置的参数名下,用途是配置的值
用页面里那枚令牌打开包内两个文件 都是 200,内容正确,水印沿用压缩包令牌里的
拿那枚令牌去开文件服务上的另一个文件 403
不写用途的令牌;用途写成缺省值的令牌;令牌放在缺省参数名里 都是 403
拿文件服务收到的取件令牌当预览令牌 403
文件服务收到的取 txt、docx、zip 的请求 都带 Authorization: Bearer <令牌>(没有缺省的那个请求头),同一把密钥验签通过;载荷里是配置的用途值、嵌套字段里的地址(与被请求的地址一致)、iat 和 exp,exp 距收到请求正好 120 秒

这套配置的放行名单里没有 key,压缩包页面拼链接时带的 key 被丢弃,包内文件照常预览(当时 kk.key 没有开启);同时开启 kk.key 的部署需要把 key 留在放行名单里。

浏览器

最终版、缺省格式,用 Chrome(Windows)实际打开:

  • 缺少令牌、过期、签名无效三种提示页都正常显示,文案各不相同;
  • txt、docx(PDF 方式)、xlsx、图片、pdf 都正常显示,水印是令牌里的文字;令牌里没写水印的 docx 不带水印;
  • 令牌带 forceUpdatedCache=true 的 docx:先出「文件转换中」的等待页,页面自己刷新后出预览,没有反复重转;
  • 图片预览页上点「使用PDF预览」按钮:跳到 PDF 预览页,地址里令牌还在、多了 officePreviewType=pdf;
  • PDF 预览页上点切换到图片预览的按钮:地址里令牌还在、多了 officePreviewType=image,请求通过了校验,页面显示的是 [ISSUE] PDF 方式预览 Office 文件后,再切换为图片预览,预览失败 #754 的「Office转换缓存异常」(与本 PR 无关,不开签名预览时同样存在);
  • zip:目录页正常,点开顶层文件和子目录里的文件都能预览、内容正确;
  • 多图预览两张图正常显示;
  • 浏览器控制台没有报错。

用 curl 实际启动时也遇到过 #754 的同一个现象:同一个 docx 先用 PDF 方式预览、再切到图片方式,页面提示「Office转换缓存异常,请联系管理员」。

没有做的验证:没有跑 tests/e2e(需要另外安装 Playwright 浏览器和 Python、7z 等依赖)。

Closes #805

把 kkFileView 嵌入业务系统时,「谁能预览哪个文件、带什么水印」通常由业务系统决定。
新增 signed.preview.enabled(默认 false)。关闭时请求原样往后传,行为与参数和之前
完全一样;开启后 /onlinePreview、/picturesPreview 只接受带有效签名令牌的请求:

- 令牌由接入方用约定的密钥签发,必须带 exp;算法支持 HS256、HS384、HS512,
  令牌头里的 alg 必须与配置一致。只用 JDK 自带的 HMAC 与已有的 Jackson,不加依赖。
- 文件地址、文件名、水印、是否强制重新转换取令牌里的;浏览器带的参数只保留放行名单
  (signed.preview.allow.params)里的,名单可以限定取值(officePreviewType=image|pdf)。
  trust.host、not.trust.host、kk.key 照常生效。
- 强制重新转换每枚令牌只执行一次,转换等待页带着同一枚令牌刷新时不会反复重新转换。
- 压缩包预览页拼包内文件链接时带上 kkFileView 自己签发的令牌,只对该压缩包解开的
  目录有效,水印沿用打开压缩包时令牌里的,接入方不需要为包内文件再签。
- 令牌缺失、无效、过期时返回 403 提示页(web/signedPreviewDenied.html)。

三种令牌(接入方签的预览令牌、kkFileView 签的压缩包令牌与取件令牌)靠用途字段区分。
字段名、三种用途的值、预览令牌是否必须写用途都可配;三个值为空或有重复时入口拒绝
全部请求、也不签发自己的令牌,不影响应用启动。

另增 signed.fetch.enabled(默认 false,独立开关):kkFileView 自己下载原文件时在请求头
里带一枚自己签发的令牌,载荷里是本次请求的地址,文件来源一方可用同一把密钥校验。
/getCorsFile 代浏览器取文件时不带。令牌只给原始地址:带令牌的那次请求不自动跟随跳转,
遇到跳转且 kk.enable.redirect 允许时,另发一次不带令牌的请求去取;开关关着时跳转行为不变。

密钥、算法、令牌所在的参数名、令牌里的字段名(五个单独配置项加一张映射表
signed.preview.claim.map)、放行名单、有效期(压缩包令牌与取件令牌可各自单独配)、
时钟误差、取件请求头的名字与前缀(可配成 Authorization: Bearer,此时以取件令牌为准)、
取件令牌里地址所在的字段(支持 payload.url 这样的点分嵌套)均可配置,缺省值即上述行为,
已有自己令牌格式的接入方可以直接对上。写法为 ${KK_XXX:默认值},application.properties 与 test.properties 同步增加,
并接入 ConfigConstants 与 ConfigRefreshComponent。

新增测试 SignedPreviewFilterTests、SignedPreviewOptionsTests、SignedPreviewArchiveTests、
SignedFetchTests、SignedPreviewCustomFormatTests、SignedPreviewConfigTests,测试里的令牌按 JWT 规范独立签发与校验,不经过被测代码。

Closes kekingcn#805

Signed-off-by: yuhaibo <yuhaibo@hotmail.com>
@yuhaibohotmail

Copy link
Copy Markdown
Contributor Author

补充一次实际启动的验证:用本 PR 的最终版本打包启动,不配置任何 signed.* 项(只为测试跳转打开了 kk.enable.redirect),结果与未开启签名预览时一致:

请求 结果
/onlinePreview?url=<base64> 预览 txt 200,内容正确
同上,另带一个乱写的 token 参数 200,内容正确,参数被忽略
docx 200,转换成功,PDF 预览页
zip 200,目录页;页面脚本里没有附加令牌的那一行,原有的 key 照旧
压缩包内顶层文件、子目录里的文件 都是 200,内容正确
文件来源回 302 照常跟随跳转,内容正确
文件来源收到的全部请求(含跳转前后两次) 都不带 kk-fetch-token,也不带 Authorization

服务日志中没有 ERROR。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] 可选的「签名预览」:预览请求携带带有效期的签名令牌(JWT),默认关闭

1 participant