保险箱插件
保险箱插件为 Halo 站点提供基于规则和密码的资源访问保护。命中规则后,访客会先进入解锁页,密码验证通过后插件写入带有效期的 HttpOnly Cookie,并自动跳回原资源。
保险箱插件
保险箱插件为 Halo 站点提供基于规则和密码的资源访问保护。命中规则后,访客会先进入解锁页,密码验证通过后插件写入带有效期的 HttpOnly Cookie,并自动跳回原资源。
交流群
功能
- 控制台规则管理:创建、编辑、启停、删除保险箱规则,支持按关键词、启用状态和保护对象筛选。
- 多类型保护对象:支持附件路径、指定前台路由、整站私密三类保护范围。
- 路径模式匹配:支持精确路径、父路径覆盖子路径,以及
*、**、?通配符。 - 命中预览:输入 URL 或路径后,按后端实际逻辑展示候选规则、最终命中规则、优先级和匹配精确度。
- 附件列表集成:在 Halo 附件列表操作菜单中可以直接为有固定访问地址的附件新增保险箱规则。
- 公开解锁流程:受保护资源统一跳转到固定解锁页,主题可覆盖页面模板。
- 权限控制:控制台规则查看和管理分别由
plugin:safebox:rule:view、plugin:safebox:rule:manage控制;公开解锁接口自动授权匿名访问。
保护对象
| 类型 | 值 | 适用场景 | 说明 |
| --- | --- | --- | --- |
| 附件 | ATTACHMENT | 私密图片、PDF、压缩包等上传文件 | 只保护 /upload 下的资源。 |
| 指定路由 | ROUTE | 会员页、下载页、活动页等前台路径 | 保护命中的前台路由,不影响 Halo 管理后台和插件接口。 |
| 整站私密 | SITE | 临时闭站、内部站点、密码访问站点 | 默认按 /** 匹配,可通过排除路径放行部分页面。 |
插件内置排除以下路径,避免把后台、接口、登录页和静态资源锁死:
/safebox/**
/apis/**
/console/**
/uc/**
/login
/logout
/oauth2/**
/actuator/**
/plugins/**
/themes/**
/assets/**
规则字段
| 字段 | 说明 |
| --- | --- |
| 规则名称 | 控制台展示名称,也是解锁页默认标题来源。 |
| 规则说明 | 解锁页说明文案;为空时使用默认提示。 |
| 保护对象类型 | ATTACHMENT、ROUTE、SITE 三选一。 |
| 保护路径 | 一行一个路径模式,也支持用逗号分隔。建议填写以 / 开头的 pathname,不要填写完整 URL。 |
| 排除路径 | 命中这些路径时直接放行,常用于整站私密下放开公开页面。 |
| 访问密码 | 创建时必填;更新时留空表示保留原密码。密码以加盐 SHA-256 哈希保存。 |
| 优先级 | 数字越大越先匹配。相同优先级下,路径越具体越先匹配。 |
| 解锁有效期 | 单位秒,最小 60 秒,默认 3600 秒。 |
| 启用状态 | 停用后该规则不参与命中。 |
匹配规则
插件会从启用规则中选择最终命中的一条规则:
- 先按保护对象类型过滤,例如附件规则只处理
/upload下的资源。 - 再排除内置路径和规则自身的排除路径。
- 匹配保护路径;不包含通配符的路径会同时覆盖其子路径,例如
/docs会命中/docs/a.pdf。 - 候选规则按
priority倒序排列。 priority相同时,路径匹配越具体越优先。
访问行为:
GET、HEAD请求命中规则且未解锁时,会以303 See Other跳转到/safebox/unlock?path=...。- 非
GET、HEAD请求命中规则且未解锁时,会返回401 Unauthorized。 - 解锁成功后,插件会写入当前规则专属 Cookie,Cookie 有效期来自规则的
ttlSeconds。 - 待访问 URL 中的 query 和 fragment 不参与路径匹配,匹配时只使用 pathname。
常用示例:
/upload/private/report.pdf # 保护单个附件
/upload/private/** # 保护整个附件目录
/vip # 保护 /vip 及其子路径
/** # 整站私密
/public/** # 可作为整站私密的排除路径
使用流程
- 在 Halo 后台安装并启用插件。
- 进入控制台菜单「保险箱」创建规则。
- 选择保护对象类型,填写保护路径、访问密码、优先级和有效期。
- 在「资源路径命中预览」中输入真实 URL 或路径,确认最终命中规则。
- 前台访问命中资源时会跳转到
/safebox/unlock,验证成功后自动回到原资源。
如果只想保护某个附件,也可以在 Halo 附件列表中打开该附件的操作菜单,选择「新增保险箱规则」。附件没有固定访问地址时不能创建规则,因为插件需要稳定的访问路径才能进行匹配。
主题集成
主题不需要主动判断资源是否受保护,访问拦截由插件的 WebFilter 统一处理。主题侧主要做一件事:按自己的设计覆盖解锁页模板。
受保护资源会统一跳转到:
/safebox/unlock?path=/upload/example.png
主题可以在当前主题的模板目录中提供 safebox-unlock.html 覆盖插件内置解锁页;未提供时使用插件内置模板。
templates/
safebox-unlock.html
解锁表单必须使用 POST 提交到 unlockAction,并保留 path、password 两个字段:
<!doctype html>
<html lang="zh-CN" xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${displayName} + ' - 访问验证'">访问验证</title>
</head>
<body>
<main>
<h1 th:text="${displayName}">受保护资源</h1>
<p th:text="${description}">该资源已开启访问保护,请输入密码后继续访问。</p>
<p th:if="${error != null and error != ''}" th:text="${error}">密码不正确。</p>
<form method="post" th:action="${unlockAction}">
<input type="hidden" name="path" th:value="${path}">
<label for="password">访问密码</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
<button type="submit">解锁资源</button>
</form>
</main>
</body>
</html>
可用模板变量:
| 变量 | 说明 |
| --- | --- |
| displayName | 匹配规则名称;为空时为 受保护资源。 |
| description | 匹配规则说明;为空时为默认提示。 |
| path | 待解锁资源路径,验证通过后会跳回该路径。 |
| error | 错误文案;密码错误时为 密码不正确。。 |
| unlockAction | 解锁表单提交地址,当前为 /apis/public.safebox.muyin.site/v1alpha1/unlock。 |
| unlockPagePath | 固定解锁页路径,当前为 /safebox/unlock。 |
| rule | 匹配到的 SafeBoxRule 对象,可读取 rule.spec.displayName、rule.spec.description 等字段。 |
注意事项:
- 不要改表单字段名,
path和password少一个都无法解锁。 - 不需要 Finder API,也不需要主题自己调用校验接口。表单提交成功后,插件会写入 Cookie 并重定向回原资源。
- 如果资源被 CDN、对象存储或反向代理绕过 Halo 直接返回,插件无法拦截。资源必须经过 Halo 应用层访问。
- 整站私密场景下,建议为公开页面配置排除路径,避免把无需保护的入口也锁住。
开发环境
- Java 21+
- Node.js 18+
- pnpm 10+
- Halo 运行版本要求:
>= 2.20.14
开发
# 启动 Halo 开发服务器
./gradlew haloServer
# 修改后热重载插件
./gradlew reload
# 开发前端资源
cd ui
pnpm install
pnpm dev
构建
./gradlew build
构建完成后,可以在 build/libs 目录找到插件 JAR 文件。
测试
./gradlew test
cd ui
pnpm test:unit
pnpm type-check
许可证
GPL-3.0 © Lywq