fix(security): 文件内联预览默认需登录,公开目录改为显式白名单
- file.public-preview 默认值由 true 改为 false(application.yml 与 @Value 同步),
上传一律落在 /yyyyMMdd/ 日期目录,默认公开等于把所有上传文件(含盖章证明材料)
变成匿名可读;公开页面引用的文件必须把所在目录列进 file.public-dirs
- 注释写清「公开页面引用的文件必须列进白名单,否则匿名访问会被拒」,同步更新
FileAccessRules 类注释
- 删掉只被测试调用的 FileAccessRules.isPublicFileRequest(测试钉住的是没上线的
平行判定);测试改为断言 downloadPatterns / permitAllPatterns /
authenticatedPatterns 的内容与顺序
- 顺带把 gxmu.module-registry.strict 改成 ${GXMU_MODULE_REGISTRY_STRICT:true},
与相邻配置一致
refs #58
This commit is contained in:
@@ -3,7 +3,6 @@ package com.gxwebsoft.common.core.security;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -17,12 +16,15 @@ import java.util.List;
|
||||
* <ul>
|
||||
* <li>下载 {@code /api/file/download/**}:一律需要登录——"取走原件"不再是匿名动作;</li>
|
||||
* <li>内联预览 {@code /api/file/{dir}/{name}} 与缩略图 {@code /api/file/thumbnail/**}:
|
||||
* 默认公开({@code file.public-preview=true}),否则现有公开页面的配图会整片变成需要 token;</li>
|
||||
* <li>其余文件接口:默认需要登录;命中 {@code file.public-dirs} 的目录额外公开。</li>
|
||||
* <b>默认也需要登录</b>({@code file.public-preview} 默认 false)。上传一律落在
|
||||
* {@code /yyyyMMdd/} 日期目录,日期目录正好是两段路径,所以默认公开等于把所有上传文件公开;</li>
|
||||
* <li>公开页面引用的文件必须把所在目录列进 {@code file.public-dirs} 白名单,
|
||||
* 否则匿名访问会被拒(表现为 401/403 的空图)。白名单是显式声明,默认空。</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>规则以字符串模式产出,交给 {@link SecurityConfig} 注册;类本身不碰请求,
|
||||
* 因此可以在单元测试里直接验证。
|
||||
* 因此可以在单元测试里直接验证模式的<b>内容与顺序</b>——顺序恰恰是这条改造的关键:
|
||||
* 下载必须在公开模式之前注册,公开模式必须在 {@code GET /**} 兜底之前注册。
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-11-03
|
||||
@@ -50,20 +52,18 @@ public class FileAccessRules {
|
||||
*/
|
||||
private static final String THUMBNAIL_PATTERN = FILE_PATH_PREFIX + "/thumbnail/**";
|
||||
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
/**
|
||||
* 允许匿名读取的目录(相对 {@code /api/file}),已去空白与首尾斜杠
|
||||
*/
|
||||
private final List<String> publicDirs;
|
||||
|
||||
/**
|
||||
* 内联资源(预览 + 缩略图)是否默认公开
|
||||
* 内联资源(预览 + 缩略图)是否默认公开;默认 false,只有显式打开才整体放行
|
||||
*/
|
||||
private final boolean publicPreview;
|
||||
|
||||
public FileAccessRules(@Value("${file.public-dirs:}") String publicDirs,
|
||||
@Value("${file.public-preview:true}") boolean publicPreview) {
|
||||
@Value("${file.public-preview:false}") boolean publicPreview) {
|
||||
this.publicDirs = normalize(publicDirs);
|
||||
this.publicPreview = publicPreview;
|
||||
}
|
||||
@@ -71,7 +71,8 @@ public class FileAccessRules {
|
||||
/**
|
||||
* 匿名放行的文件路径模式。
|
||||
*
|
||||
* <p>内联公开时是预览 + 缩略图;否则是 {@code file.public-dirs} 里的目录(原图与缩略图)。
|
||||
* <p>默认({@code public-preview=false})只有 {@code file.public-dirs} 白名单里的目录;
|
||||
* 显式打开内联公开时才额外放行预览与缩略图,且排在白名单之前。
|
||||
*
|
||||
* @return antMatchers 模式,没有任何公开项时返回空列表
|
||||
*/
|
||||
@@ -104,31 +105,6 @@ public class FileAccessRules {
|
||||
return Collections.singletonList(DOWNLOAD_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这条文件请求是否按公开资源处理(匿名可读)。
|
||||
*
|
||||
* @param path 请求路径,可带查询串
|
||||
* @return true 表示命中公开规则;下载、未命中目录、非文件路径一律 false
|
||||
*/
|
||||
public boolean isPublicFileRequest(String path) {
|
||||
if (StrUtil.isBlank(path)) {
|
||||
return false;
|
||||
}
|
||||
String clean = StrUtil.subBefore(path, "?", false);
|
||||
if (!clean.startsWith(FILE_PATH_PREFIX + "/")) {
|
||||
return false;
|
||||
}
|
||||
if (pathMatcher.match(DOWNLOAD_PATTERN, clean)) {
|
||||
return false;
|
||||
}
|
||||
for (String pattern : permitAllPatterns()) {
|
||||
if (pathMatcher.match(pattern, clean)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<String> publicDirPatterns() {
|
||||
if (publicDirs.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
|
||||
Reference in New Issue
Block a user