<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0"><channel><title>Halo - 强大易用的开源建站工具 | 应用市场 | 保险箱插件 | 版本发布</title><link>https://www.halo.run</link><atom:link href="https://www.halo.run/feed/app-store/apps/app-ak7nvfvj/releases.xml" rel="self" type="application/rss+xml"/><description>Halo - 强大易用的开源建站工具 | 应用市场 | 保险箱插件 | 版本发布</description><generator>Halo v2.25.4</generator><language>zh-cn</language><image><url>https://www.halo.run/upload/logo.png</url><title>Halo - 强大易用的开源建站工具 | 应用市场 | 保险箱插件 | 版本发布</title><link>https://www.halo.run</link></image><lastBuildDate>Tue, 30 Jun 2026 00:13:33 GMT</lastBuildDate><follow_challenge><feedId>69597013489248256</feedId><userId>41706424548048896</userId></follow_challenge><item><title><![CDATA[保险箱插件 1.0.0 发布]]></title><link>https://www.halo.run/store/apps/app-ak7nvfvj/releases/app-release-gcdntcpi</link><description><![CDATA[<img src="https://www.halo.run/plugins/feed/assets/telemetry.gif?title=%E4%BF%9D%E9%99%A9%E7%AE%B1%E6%8F%92%E4%BB%B6%201.0.0%20%E5%8F%91%E5%B8%83&amp;url=/store/apps/app-ak7nvfvj/releases/app-release-gcdntcpi" width="1" height="1" alt="" style="opacity:0;">
<h1 dir="auto">保险箱插件</h1>
<p dir="auto">保险箱插件为 Halo 站点提供基于规则和密码的资源访问保护。命中规则后，访客会先进入解锁页，密码验证通过后插件写入带有效期的 HttpOnly Cookie，并自动跳回原资源。</p>
<h2 dir="auto">交流群</h2>
<p dir="auto"><a href="https://qm.qq.com/q/wuC7NZr0sw" rel="nofollow">点击链接加入群聊【halo博客-lywq插件】</a></p>
<a target="_blank" rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/52553268/448343335-bf162401-07fd-49ec-b50f-5218c9510937.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODIyMTA0NjMsIm5iZiI6MTc4MjIxMDE2MywicGF0aCI6Ii81MjU1MzI2OC80NDgzNDMzMzUtYmYxNjI0MDEtMDdmZC00OWVjLWI1MGYtNTIxOGM5NTEwOTM3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA2MjMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNjIzVDEwMjI0M1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTEzNzllYWE1NmE1NDI0NjQ5ZGE1Y2RjZWU2M2FkMjY1NmZmYzZhNGY1MzU1NTI2NjIwZWFmYjJlYjM0MDY4M2UmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRnBuZyJ9.kWLrtNiKz1uXyDk0l78rOkWiwu5oYzP7d37RIWvnibg"><img src="https://www.halo.run/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F52553268%2F448343335-bf162401-07fd-49ec-b50f-5218c9510937.png%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODIyMTA0NjMsIm5iZiI6MTc4MjIxMDE2MywicGF0aCI6Ii81MjU1MzI2OC80NDgzNDMzMzUtYmYxNjI0MDEtMDdmZC00OWVjLWI1MGYtNTIxOGM5NTEwOTM3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA2MjMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNjIzVDEwMjI0M1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTEzNzllYWE1NmE1NDI0NjQ5ZGE1Y2RjZWU2M2FkMjY1NmZmYzZhNGY1MzU1NTI2NjIwZWFmYjJlYjM0MDY4M2UmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRnBuZyJ9.kWLrtNiKz1uXyDk0l78rOkWiwu5oYzP7d37RIWvnibg&amp;size=m" style="max-width: 100%;"></a>
<h2 dir="auto">功能</h2>
<ul dir="auto">
 <li>控制台规则管理：创建、编辑、启停、删除保险箱规则，支持按关键词、启用状态和保护对象筛选。</li>
 <li>多类型保护对象：支持附件路径、指定前台路由、整站私密三类保护范围。</li>
 <li>路径模式匹配：支持精确路径、父路径覆盖子路径，以及 <code class="notranslate">*</code>、<code class="notranslate">**</code>、<code class="notranslate">?</code> 通配符。</li>
 <li>命中预览：输入 URL 或路径后，按后端实际逻辑展示候选规则、最终命中规则、优先级和匹配精确度。</li>
 <li>附件列表集成：在 Halo 附件列表操作菜单中可以直接为有固定访问地址的附件新增保险箱规则。</li>
 <li>公开解锁流程：受保护资源统一跳转到固定解锁页，主题可覆盖页面模板。</li>
 <li>权限控制：控制台规则查看和管理分别由 <code class="notranslate">plugin:safebox:rule:view</code>、<code class="notranslate">plugin:safebox:rule:manage</code> 控制；公开解锁接口自动授权匿名访问。</li>
</ul>
<h2 dir="auto">保护对象</h2>
<markdown-accessiblity-table>
 <table role="table">
  <thead>
   <tr>
    <th>类型</th>
    <th>值</th>
    <th>适用场景</th>
    <th>说明</th>
   </tr>
  </thead>
  <tbody>
   <tr>
    <td>附件</td>
    <td><code class="notranslate">ATTACHMENT</code></td>
    <td>私密图片、PDF、压缩包等上传文件</td>
    <td>只保护 <code class="notranslate">/upload</code> 下的资源。</td>
   </tr>
   <tr>
    <td>指定路由</td>
    <td><code class="notranslate">ROUTE</code></td>
    <td>会员页、下载页、活动页等前台路径</td>
    <td>保护命中的前台路由，不影响 Halo 管理后台和插件接口。</td>
   </tr>
   <tr>
    <td>整站私密</td>
    <td><code class="notranslate">SITE</code></td>
    <td>临时闭站、内部站点、密码访问站点</td>
    <td>默认按 <code class="notranslate">/**</code> 匹配，可通过排除路径放行部分页面。</td>
   </tr>
  </tbody>
 </table>
</markdown-accessiblity-table>
<p dir="auto">插件内置排除以下路径，避免把后台、接口、登录页和静态资源锁死：</p>
<pre lang="text" class="notranslate"><code class="notranslate">/safebox/**
/apis/**
/console/**
/uc/**
/login
/logout
/oauth2/**
/actuator/**
/plugins/**
/themes/**
/assets/**
</code></pre>
<h2 dir="auto">规则字段</h2>
<markdown-accessiblity-table>
 <table role="table">
  <thead>
   <tr>
    <th>字段</th>
    <th>说明</th>
   </tr>
  </thead>
  <tbody>
   <tr>
    <td>规则名称</td>
    <td>控制台展示名称，也是解锁页默认标题来源。</td>
   </tr>
   <tr>
    <td>规则说明</td>
    <td>解锁页说明文案；为空时使用默认提示。</td>
   </tr>
   <tr>
    <td>保护对象类型</td>
    <td><code class="notranslate">ATTACHMENT</code>、<code class="notranslate">ROUTE</code>、<code class="notranslate">SITE</code> 三选一。</td>
   </tr>
   <tr>
    <td>保护路径</td>
    <td>一行一个路径模式，也支持用逗号分隔。建议填写以 <code class="notranslate">/</code> 开头的 pathname，不要填写完整 URL。</td>
   </tr>
   <tr>
    <td>排除路径</td>
    <td>命中这些路径时直接放行，常用于整站私密下放开公开页面。</td>
   </tr>
   <tr>
    <td>访问密码</td>
    <td>创建时必填；更新时留空表示保留原密码。密码以加盐 SHA-256 哈希保存。</td>
   </tr>
   <tr>
    <td>优先级</td>
    <td>数字越大越先匹配。相同优先级下，路径越具体越先匹配。</td>
   </tr>
   <tr>
    <td>解锁有效期</td>
    <td>单位秒，最小 60 秒，默认 3600 秒。</td>
   </tr>
   <tr>
    <td>启用状态</td>
    <td>停用后该规则不参与命中。</td>
   </tr>
  </tbody>
 </table>
</markdown-accessiblity-table>
<h2 dir="auto">匹配规则</h2>
<p dir="auto">插件会从启用规则中选择最终命中的一条规则：</p>
<ol dir="auto">
 <li>先按保护对象类型过滤，例如附件规则只处理 <code class="notranslate">/upload</code> 下的资源。</li>
 <li>再排除内置路径和规则自身的排除路径。</li>
 <li>匹配保护路径；不包含通配符的路径会同时覆盖其子路径，例如 <code class="notranslate">/docs</code> 会命中 <code class="notranslate">/docs/a.pdf</code>。</li>
 <li>候选规则按 <code class="notranslate">priority</code> 倒序排列。</li>
 <li><code class="notranslate">priority</code> 相同时，路径匹配越具体越优先。</li>
</ol>
<p dir="auto">访问行为：</p>
<ul dir="auto">
 <li><code class="notranslate">GET</code>、<code class="notranslate">HEAD</code> 请求命中规则且未解锁时，会以 <code class="notranslate">303 See Other</code> 跳转到 <code class="notranslate">/safebox/unlock?path=...</code>。</li>
 <li>非 <code class="notranslate">GET</code>、<code class="notranslate">HEAD</code> 请求命中规则且未解锁时，会返回 <code class="notranslate">401 Unauthorized</code>。</li>
 <li>解锁成功后，插件会写入当前规则专属 Cookie，Cookie 有效期来自规则的 <code class="notranslate">ttlSeconds</code>。</li>
 <li>待访问 URL 中的 query 和 fragment 不参与路径匹配，匹配时只使用 pathname。</li>
</ul>
<p dir="auto">常用示例：</p>
<pre lang="text" class="notranslate"><code class="notranslate">/upload/private/report.pdf     # 保护单个附件
/upload/private/**             # 保护整个附件目录
/vip                           # 保护 /vip 及其子路径
/**                            # 整站私密
/public/**                     # 可作为整站私密的排除路径
</code></pre>
<h2 dir="auto">使用流程</h2>
<ol dir="auto">
 <li>在 Halo 后台安装并启用插件。</li>
 <li>进入控制台菜单「保险箱」创建规则。</li>
 <li>选择保护对象类型，填写保护路径、访问密码、优先级和有效期。</li>
 <li>在「资源路径命中预览」中输入真实 URL 或路径，确认最终命中规则。</li>
 <li>前台访问命中资源时会跳转到 <code class="notranslate">/safebox/unlock</code>，验证成功后自动回到原资源。</li>
</ol>
<p dir="auto">如果只想保护某个附件，也可以在 Halo 附件列表中打开该附件的操作菜单，选择「新增保险箱规则」。附件没有固定访问地址时不能创建规则，因为插件需要稳定的访问路径才能进行匹配。</p>
<h2 dir="auto">主题集成</h2>
<p dir="auto">主题不需要主动判断资源是否受保护，访问拦截由插件的 WebFilter 统一处理。主题侧主要做一件事：按自己的设计覆盖解锁页模板。</p>
<p dir="auto">受保护资源会统一跳转到：</p>
<pre lang="text" class="notranslate"><code class="notranslate">/safebox/unlock?path=/upload/example.png
</code></pre>
<p dir="auto">主题可以在当前主题的模板目录中提供 <code class="notranslate">safebox-unlock.html</code> 覆盖插件内置解锁页；未提供时使用插件内置模板。</p>
<pre lang="text" class="notranslate"><code class="notranslate">templates/
  safebox-unlock.html
</code></pre>
<p dir="auto">解锁表单必须使用 <code class="notranslate">POST</code> 提交到 <code class="notranslate">unlockAction</code>，并保留 <code class="notranslate">path</code>、<code class="notranslate">password</code> 两个字段：</p>
<div class="highlight highlight-text-html-basic" dir="auto">
 <pre class="notranslate"><span class="pl-c1">&lt;!doctype html<span class="pl-kos">&gt;</span></span>
<span class="pl-kos">&lt;</span><span class="pl-ent">html</span> <span class="pl-c1">lang</span>="<span class="pl-s">zh-CN</span>" <span class="pl-c1">xmlns:th</span>="<span class="pl-s">https://www.thymeleaf.org</span>"<span class="pl-kos">&gt;</span>
<span class="pl-kos">&lt;</span><span class="pl-ent">head</span><span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;</span><span class="pl-ent">meta</span> <span class="pl-c1">charset</span>="<span class="pl-s">utf-8</span>"<span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;</span><span class="pl-ent">meta</span> <span class="pl-c1">name</span>="<span class="pl-s">viewport</span>" <span class="pl-c1">content</span>="<span class="pl-s">width=device-width, initial-scale=1</span>"<span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;</span><span class="pl-ent">title</span> <span class="pl-c1">th:text</span>="<span class="pl-s">${displayName} + ' - 访问验证'</span>"<span class="pl-kos">&gt;</span>访问验证<span class="pl-kos">&lt;/</span><span class="pl-ent">title</span><span class="pl-kos">&gt;</span>
<span class="pl-kos">&lt;/</span><span class="pl-ent">head</span><span class="pl-kos">&gt;</span>
<span class="pl-kos">&lt;</span><span class="pl-ent">body</span><span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;</span><span class="pl-ent">main</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;</span><span class="pl-ent">h1</span> <span class="pl-c1">th:text</span>="<span class="pl-s">${displayName}</span>"<span class="pl-kos">&gt;</span>受保护资源<span class="pl-kos">&lt;/</span><span class="pl-ent">h1</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;</span><span class="pl-ent">p</span> <span class="pl-c1">th:text</span>="<span class="pl-s">${description}</span>"<span class="pl-kos">&gt;</span>该资源已开启访问保护，请输入密码后继续访问。<span class="pl-kos">&lt;/</span><span class="pl-ent">p</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;</span><span class="pl-ent">p</span> <span class="pl-c1">th:if</span>="<span class="pl-s">${error != null and error != ''}</span>" <span class="pl-c1">th:text</span>="<span class="pl-s">${error}</span>"<span class="pl-kos">&gt;</span>密码不正确。<span class="pl-kos">&lt;/</span><span class="pl-ent">p</span><span class="pl-kos">&gt;</span>

    <span class="pl-kos">&lt;</span><span class="pl-ent">form</span> <span class="pl-c1">method</span>="<span class="pl-s">post</span>" <span class="pl-c1">th:action</span>="<span class="pl-s">${unlockAction}</span>"<span class="pl-kos">&gt;</span>
      <span class="pl-kos">&lt;</span><span class="pl-ent">input</span> <span class="pl-c1">type</span>="<span class="pl-s">hidden</span>" <span class="pl-c1">name</span>="<span class="pl-s">path</span>" <span class="pl-c1">th:value</span>="<span class="pl-s">${path}</span>"<span class="pl-kos">&gt;</span>
      <span class="pl-kos">&lt;</span><span class="pl-ent">label</span> <span class="pl-c1">for</span>="<span class="pl-s">password</span>"<span class="pl-kos">&gt;</span>访问密码<span class="pl-kos">&lt;/</span><span class="pl-ent">label</span><span class="pl-kos">&gt;</span>
      <span class="pl-kos">&lt;</span><span class="pl-ent">input</span> <span class="pl-c1">id</span>="<span class="pl-s">password</span>" <span class="pl-c1">name</span>="<span class="pl-s">password</span>" <span class="pl-c1">type</span>="<span class="pl-s">password</span>" <span class="pl-c1">autocomplete</span>="<span class="pl-s">current-password</span>" <span class="pl-c1">required</span><span class="pl-kos">&gt;</span>
      <span class="pl-kos">&lt;</span><span class="pl-ent">button</span> <span class="pl-c1">type</span>="<span class="pl-s">submit</span>"<span class="pl-kos">&gt;</span>解锁资源<span class="pl-kos">&lt;/</span><span class="pl-ent">button</span><span class="pl-kos">&gt;</span>
    <span class="pl-kos">&lt;/</span><span class="pl-ent">form</span><span class="pl-kos">&gt;</span>
  <span class="pl-kos">&lt;/</span><span class="pl-ent">main</span><span class="pl-kos">&gt;</span>
<span class="pl-kos">&lt;/</span><span class="pl-ent">body</span><span class="pl-kos">&gt;</span>
<span class="pl-kos">&lt;/</span><span class="pl-ent">html</span><span class="pl-kos">&gt;</span></pre>
</div>
<p dir="auto">可用模板变量：</p>
<markdown-accessiblity-table>
 <table role="table">
  <thead>
   <tr>
    <th>变量</th>
    <th>说明</th>
   </tr>
  </thead>
  <tbody>
   <tr>
    <td><code class="notranslate">displayName</code></td>
    <td>匹配规则名称；为空时为 <code class="notranslate">受保护资源</code>。</td>
   </tr>
   <tr>
    <td><code class="notranslate">description</code></td>
    <td>匹配规则说明；为空时为默认提示。</td>
   </tr>
   <tr>
    <td><code class="notranslate">path</code></td>
    <td>待解锁资源路径，验证通过后会跳回该路径。</td>
   </tr>
   <tr>
    <td><code class="notranslate">error</code></td>
    <td>错误文案；密码错误时为 <code class="notranslate">密码不正确。</code>。</td>
   </tr>
   <tr>
    <td><code class="notranslate">unlockAction</code></td>
    <td>解锁表单提交地址，当前为 <code class="notranslate">/apis/public.safebox.muyin.site/v1alpha1/unlock</code>。</td>
   </tr>
   <tr>
    <td><code class="notranslate">unlockPagePath</code></td>
    <td>固定解锁页路径，当前为 <code class="notranslate">/safebox/unlock</code>。</td>
   </tr>
   <tr>
    <td><code class="notranslate">rule</code></td>
    <td>匹配到的 <code class="notranslate">SafeBoxRule</code> 对象，可读取 <code class="notranslate">rule.spec.displayName</code>、<code class="notranslate">rule.spec.description</code> 等字段。</td>
   </tr>
  </tbody>
 </table>
</markdown-accessiblity-table>
<p dir="auto">注意事项：</p>
<ul dir="auto">
 <li>不要改表单字段名，<code class="notranslate">path</code> 和 <code class="notranslate">password</code> 少一个都无法解锁。</li>
 <li>不需要 Finder API，也不需要主题自己调用校验接口。表单提交成功后，插件会写入 Cookie 并重定向回原资源。</li>
 <li>如果资源被 CDN、对象存储或反向代理绕过 Halo 直接返回，插件无法拦截。资源必须经过 Halo 应用层访问。</li>
 <li>整站私密场景下，建议为公开页面配置排除路径，避免把无需保护的入口也锁住。</li>
</ul>
<h2 dir="auto">开发环境</h2>
<ul dir="auto">
 <li>Java 21+</li>
 <li>Node.js 18+</li>
 <li>pnpm 10+</li>
 <li>Halo 运行版本要求：<code class="notranslate">&gt;= 2.20.14</code></li>
</ul>
<h2 dir="auto">开发</h2>
<div class="highlight highlight-source-shell" dir="auto">
 <pre class="notranslate"><span class="pl-c"><span class="pl-c">#</span> 启动 Halo 开发服务器</span>
./gradlew haloServer

<span class="pl-c"><span class="pl-c">#</span> 修改后热重载插件</span>
./gradlew reload

<span class="pl-c"><span class="pl-c">#</span> 开发前端资源</span>
<span class="pl-c1">cd</span> ui
pnpm install
pnpm dev</pre>
</div>
<h2 dir="auto">构建</h2>
<div class="highlight highlight-source-shell" dir="auto">
 <pre class="notranslate">./gradlew build</pre>
</div>
<p dir="auto">构建完成后，可以在 <code class="notranslate">build/libs</code> 目录找到插件 JAR 文件。</p>
<h2 dir="auto">测试</h2>
<div class="highlight highlight-source-shell" dir="auto">
 <pre class="notranslate">./gradlew <span class="pl-c1">test</span>

<span class="pl-c1">cd</span> ui
pnpm test:unit
pnpm type-check</pre>
</div>
<h2 dir="auto">许可证</h2>
<p dir="auto"><a href="https://www.halo.run/./LICENSE">GPL-3.0</a> © Lywq</p>
<hr>
<p dir="auto"><em>Generated from <a href="https://github.com/liuyiwuqing/halo-plugin-safe-box/releases/tag/1.0.0">1.0.0</a></em></p>
<hr>
<p><a href="https://www.halo.run/store/apps/app-ak7nvfvj/releases/app-release-gcdntcpi">查看版本详情</a></p>]]></description><guid isPermaLink="false">store.halo.run/Release/app-release-gcdntcpi</guid><pubDate>Tue, 23 Jun 2026 10:22:45 GMT</pubDate></item><item><title><![CDATA[保险箱插件 1.0.0 发布]]></title><link>https://www.halo.run/store/apps/app-ak7nvfvj/releases/app-release-bhxooov9</link><description><![CDATA[<img src="https://www.halo.run/plugins/feed/assets/telemetry.gif?title=%E4%BF%9D%E9%99%A9%E7%AE%B1%E6%8F%92%E4%BB%B6%201.0.0%20%E5%8F%91%E5%B8%83&amp;url=/store/apps/app-ak7nvfvj/releases/app-release-bhxooov9" width="1" height="1" alt="" style="opacity:0;">
<h1 id="%E4%BF%9D%E9%99%A9%E7%AE%B1%E6%8F%92%E4%BB%B6" tabindex="-1">保险箱插件</h1>
<p>保险箱插件为 Halo 站点提供基于规则和密码的资源访问保护。命中规则后，访客会先进入解锁页，密码验证通过后插件写入带有效期的 HttpOnly Cookie，并自动跳回原资源。</p>
<h2 id="%E4%BA%A4%E6%B5%81%E7%BE%A4" tabindex="-1">交流群</h2>
<p><a href="https://qm.qq.com/q/wuC7NZr0sw">点击链接加入群聊【halo博客-lywq插件】</a></p>
<img src="https://www.halo.run/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fbf162401-07fd-49ec-b50f-5218c9510937&amp;size=m" style="height: 400px !important; width: auto; object-fit: contain;">
<h2 id="%E5%8A%9F%E8%83%BD" tabindex="-1">功能</h2>
<ul>
 <li>控制台规则管理：创建、编辑、启停、删除保险箱规则，支持按关键词、启用状态和保护对象筛选。</li>
 <li>多类型保护对象：支持附件路径、指定前台路由、整站私密三类保护范围。</li>
 <li>路径模式匹配：支持精确路径、父路径覆盖子路径，以及 <code>*</code>、<code>**</code>、<code>?</code> 通配符。</li>
 <li>命中预览：输入 URL 或路径后，按后端实际逻辑展示候选规则、最终命中规则、优先级和匹配精确度。</li>
 <li>附件列表集成：在 Halo 附件列表操作菜单中可以直接为有固定访问地址的附件新增保险箱规则。</li>
 <li>公开解锁流程：受保护资源统一跳转到固定解锁页，主题可覆盖页面模板。</li>
 <li>权限控制：控制台规则查看和管理分别由 <code>plugin:safebox:rule:view</code>、<code>plugin:safebox:rule:manage</code> 控制；公开解锁接口自动授权匿名访问。</li>
</ul>
<h2 id="%E4%BF%9D%E6%8A%A4%E5%AF%B9%E8%B1%A1" tabindex="-1">保护对象</h2>
<table>
 <thead>
  <tr>
   <th>类型</th>
   <th>值</th>
   <th>适用场景</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>附件</td>
   <td><code>ATTACHMENT</code></td>
   <td>私密图片、PDF、压缩包等上传文件</td>
   <td>只保护 <code>/upload</code> 下的资源。</td>
  </tr>
  <tr>
   <td>指定路由</td>
   <td><code>ROUTE</code></td>
   <td>会员页、下载页、活动页等前台路径</td>
   <td>保护命中的前台路由，不影响 Halo 管理后台和插件接口。</td>
  </tr>
  <tr>
   <td>整站私密</td>
   <td><code>SITE</code></td>
   <td>临时闭站、内部站点、密码访问站点</td>
   <td>默认按 <code>/**</code> 匹配，可通过排除路径放行部分页面。</td>
  </tr>
 </tbody>
</table>
<p>插件内置排除以下路径，避免把后台、接口、登录页和静态资源锁死：</p>
<pre><code class="language-text">/safebox/**
/apis/**
/console/**
/uc/**
/login
/logout
/oauth2/**
/actuator/**
/plugins/**
/themes/**
/assets/**
</code></pre>
<h2 id="%E8%A7%84%E5%88%99%E5%AD%97%E6%AE%B5" tabindex="-1">规则字段</h2>
<table>
 <thead>
  <tr>
   <th>字段</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td>规则名称</td>
   <td>控制台展示名称，也是解锁页默认标题来源。</td>
  </tr>
  <tr>
   <td>规则说明</td>
   <td>解锁页说明文案；为空时使用默认提示。</td>
  </tr>
  <tr>
   <td>保护对象类型</td>
   <td><code>ATTACHMENT</code>、<code>ROUTE</code>、<code>SITE</code> 三选一。</td>
  </tr>
  <tr>
   <td>保护路径</td>
   <td>一行一个路径模式，也支持用逗号分隔。建议填写以 <code>/</code> 开头的 pathname，不要填写完整 URL。</td>
  </tr>
  <tr>
   <td>排除路径</td>
   <td>命中这些路径时直接放行，常用于整站私密下放开公开页面。</td>
  </tr>
  <tr>
   <td>访问密码</td>
   <td>创建时必填；更新时留空表示保留原密码。密码以加盐 SHA-256 哈希保存。</td>
  </tr>
  <tr>
   <td>优先级</td>
   <td>数字越大越先匹配。相同优先级下，路径越具体越先匹配。</td>
  </tr>
  <tr>
   <td>解锁有效期</td>
   <td>单位秒，最小 60 秒，默认 3600 秒。</td>
  </tr>
  <tr>
   <td>启用状态</td>
   <td>停用后该规则不参与命中。</td>
  </tr>
 </tbody>
</table>
<h2 id="%E5%8C%B9%E9%85%8D%E8%A7%84%E5%88%99" tabindex="-1">匹配规则</h2>
<p>插件会从启用规则中选择最终命中的一条规则：</p>
<ol>
 <li>先按保护对象类型过滤，例如附件规则只处理 <code>/upload</code> 下的资源。</li>
 <li>再排除内置路径和规则自身的排除路径。</li>
 <li>匹配保护路径；不包含通配符的路径会同时覆盖其子路径，例如 <code>/docs</code> 会命中 <code>/docs/a.pdf</code>。</li>
 <li>候选规则按 <code>priority</code> 倒序排列。</li>
 <li><code>priority</code> 相同时，路径匹配越具体越优先。</li>
</ol>
<p>访问行为：</p>
<ul>
 <li><code>GET</code>、<code>HEAD</code> 请求命中规则且未解锁时，会以 <code>303 See Other</code> 跳转到 <code>/safebox/unlock?path=...</code>。</li>
 <li>非 <code>GET</code>、<code>HEAD</code> 请求命中规则且未解锁时，会返回 <code>401 Unauthorized</code>。</li>
 <li>解锁成功后，插件会写入当前规则专属 Cookie，Cookie 有效期来自规则的 <code>ttlSeconds</code>。</li>
 <li>待访问 URL 中的 query 和 fragment 不参与路径匹配，匹配时只使用 pathname。</li>
</ul>
<p>常用示例：</p>
<pre><code class="language-text">/upload/private/report.pdf     # 保护单个附件
/upload/private/**             # 保护整个附件目录
/vip                           # 保护 /vip 及其子路径
/**                            # 整站私密
/public/**                     # 可作为整站私密的排除路径
</code></pre>
<h2 id="%E4%BD%BF%E7%94%A8%E6%B5%81%E7%A8%8B" tabindex="-1">使用流程</h2>
<ol>
 <li>在 Halo 后台安装并启用插件。</li>
 <li>进入控制台菜单「保险箱」创建规则。</li>
 <li>选择保护对象类型，填写保护路径、访问密码、优先级和有效期。</li>
 <li>在「资源路径命中预览」中输入真实 URL 或路径，确认最终命中规则。</li>
 <li>前台访问命中资源时会跳转到 <code>/safebox/unlock</code>，验证成功后自动回到原资源。</li>
</ol>
<p>如果只想保护某个附件，也可以在 Halo 附件列表中打开该附件的操作菜单，选择「新增保险箱规则」。附件没有固定访问地址时不能创建规则，因为插件需要稳定的访问路径才能进行匹配。</p>
<h2 id="%E4%B8%BB%E9%A2%98%E9%9B%86%E6%88%90" tabindex="-1">主题集成</h2>
<p>主题不需要主动判断资源是否受保护，访问拦截由插件的 WebFilter 统一处理。主题侧主要做一件事：按自己的设计覆盖解锁页模板。</p>
<p>受保护资源会统一跳转到：</p>
<pre><code class="language-text">/safebox/unlock?path=/upload/example.png
</code></pre>
<p>主题可以在当前主题的模板目录中提供 <code>safebox-unlock.html</code> 覆盖插件内置解锁页；未提供时使用插件内置模板。</p>
<pre><code class="language-text">templates/
  safebox-unlock.html
</code></pre>
<p>解锁表单必须使用 <code>POST</code> 提交到 <code>unlockAction</code>，并保留 <code>path</code>、<code>password</code> 两个字段：</p>
<pre><code class="language-html">&lt;!doctype html&gt;
&lt;html lang="zh-CN" xmlns:th="https://www.thymeleaf.org"&gt;
&lt;head&gt;
  &lt;meta charset="utf-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
  &lt;title th:text="${displayName} + ' - 访问验证'"&gt;访问验证&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;main&gt;
    &lt;h1 th:text="${displayName}"&gt;受保护资源&lt;/h1&gt;
    &lt;p th:text="${description}"&gt;该资源已开启访问保护，请输入密码后继续访问。&lt;/p&gt;
    &lt;p th:if="${error != null and error != ''}" th:text="${error}"&gt;密码不正确。&lt;/p&gt;

    &lt;form method="post" th:action="${unlockAction}"&gt;
      &lt;input type="hidden" name="path" th:value="${path}"&gt;
      &lt;label for="password"&gt;访问密码&lt;/label&gt;
      &lt;input id="password" name="password" type="password" autocomplete="current-password" required&gt;
      &lt;button type="submit"&gt;解锁资源&lt;/button&gt;
    &lt;/form&gt;
  &lt;/main&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>可用模板变量：</p>
<table>
 <thead>
  <tr>
   <th>变量</th>
   <th>说明</th>
  </tr>
 </thead>
 <tbody>
  <tr>
   <td><code>displayName</code></td>
   <td>匹配规则名称；为空时为 <code>受保护资源</code>。</td>
  </tr>
  <tr>
   <td><code>description</code></td>
   <td>匹配规则说明；为空时为默认提示。</td>
  </tr>
  <tr>
   <td><code>path</code></td>
   <td>待解锁资源路径，验证通过后会跳回该路径。</td>
  </tr>
  <tr>
   <td><code>error</code></td>
   <td>错误文案；密码错误时为 <code>密码不正确。</code>。</td>
  </tr>
  <tr>
   <td><code>unlockAction</code></td>
   <td>解锁表单提交地址，当前为 <code>/apis/public.safebox.muyin.site/v1alpha1/unlock</code>。</td>
  </tr>
  <tr>
   <td><code>unlockPagePath</code></td>
   <td>固定解锁页路径，当前为 <code>/safebox/unlock</code>。</td>
  </tr>
  <tr>
   <td><code>rule</code></td>
   <td>匹配到的 <code>SafeBoxRule</code> 对象，可读取 <code>rule.spec.displayName</code>、<code>rule.spec.description</code> 等字段。</td>
  </tr>
 </tbody>
</table>
<p>注意事项：</p>
<ul>
 <li>不要改表单字段名，<code>path</code> 和 <code>password</code> 少一个都无法解锁。</li>
 <li>不需要 Finder API，也不需要主题自己调用校验接口。表单提交成功后，插件会写入 Cookie 并重定向回原资源。</li>
 <li>如果资源被 CDN、对象存储或反向代理绕过 Halo 直接返回，插件无法拦截。资源必须经过 Halo 应用层访问。</li>
 <li>整站私密场景下，建议为公开页面配置排除路径，避免把无需保护的入口也锁住。</li>
</ul>
<h2 id="%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83" tabindex="-1">开发环境</h2>
<ul>
 <li>Java 21+</li>
 <li>Node.js 18+</li>
 <li>pnpm 10+</li>
 <li>Halo 运行版本要求：<code>&gt;= 2.20.14</code></li>
</ul>
<h2 id="%E5%BC%80%E5%8F%91" tabindex="-1">开发</h2>
<pre><code class="language-bash"># 启动 Halo 开发服务器
./gradlew haloServer

# 修改后热重载插件
./gradlew reload

# 开发前端资源
cd ui
pnpm install
pnpm dev
</code></pre>
<h2 id="%E6%9E%84%E5%BB%BA" tabindex="-1">构建</h2>
<pre><code class="language-bash">./gradlew build
</code></pre>
<p>构建完成后，可以在 <code>build/libs</code> 目录找到插件 JAR 文件。</p>
<h2 id="%E6%B5%8B%E8%AF%95" tabindex="-1">测试</h2>
<pre><code class="language-bash">./gradlew test

cd ui
pnpm test:unit
pnpm type-check
</code></pre>
<h2 id="%E8%AE%B8%E5%8F%AF%E8%AF%81" tabindex="-1">许可证</h2>
<p><a href="https://www.halo.run/./LICENSE">GPL-3.0</a> © Lywq</p>
<hr>
<p><a href="https://www.halo.run/store/apps/app-ak7nvfvj/releases/app-release-bhxooov9">查看版本详情</a></p>]]></description><guid isPermaLink="false">store.halo.run/Release/app-release-bhxooov9</guid><pubDate>Mon, 22 Jun 2026 07:49:07 GMT</pubDate></item></channel></rss>