fix: 修复反向代理跨域误判与中文附件名落盘失败
生产经 nginx/frp 反向代理时后端未识别 X-Forwarded-Proto,把 HTTPS 同源请求按 http 判定为跨域,而 CORS 白名单仅含内网 http,导致带 Origin 的请求(登录、加密握手等)返回 403 Invalid CORS request;同时部分运行环境未设置 UTF-8 locale,含中文的邮箱附件名落盘时抛 InvalidPathException 报「处理失败」——本次一并修复。
变更内容:
- WebConfig.addCorsMappings 放开全部来源(allowedOriginPatterns("*"),allowCredentials(true),后者不允许 allowedOrigins("*"))。
- application.yml 新增 server.forward-headers-strategy: framework,信任 X-Forwarded-Proto/Host,使 HTTPS 同源请求被正确识别。
- EmailBillStorage.sanitize 不再使用 Paths.get 解析文件名(改为字符串截取末段),并对非 ASCII 文件名做百分号编码,保证落盘名为纯 ASCII;附件原始名仍存 attachment_name,展示不受影响。
说明:
- 接口认证基于 JWT(Authorization 头存于 localStorage,跨站脚本无法自动携带),放开跨域风险可控;容器侧仍建议设置 LANG/LC_ALL=C.UTF-8。
This commit is contained in:
@@ -62,21 +62,16 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 配置跨域策略。
|
||||
* <p>仅放开本机与局域网来源,避免任意站点携带凭据发起跨域请求;支持常用 HTTP 方法并允许携带凭据。
|
||||
* 生产部署前后端同源,无需跨域;开发场景为 5173/5174,局域网访问为 192.168.*、10.*、172.* 网段。</p>
|
||||
* <p>放开全部来源:允许任意站点携带凭据发起跨域请求。因 {@code allowCredentials(true)}
|
||||
* 不支持 {@code allowedOrigins("*")},须使用 {@code allowedOriginPatterns("*")}(回显请求 Origin)。
|
||||
* 接口认证依赖 JWT(Authorization 头,存于 localStorage,跨站脚本无法自动携带),风险可控。</p>
|
||||
*
|
||||
* @param registry 跨域配置注册器
|
||||
*/
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns(
|
||||
"http://localhost:*",
|
||||
"http://127.0.0.1:*",
|
||||
"http://[::1]:*",
|
||||
"http://192.168.*.*:*",
|
||||
"http://10.*.*.*:*",
|
||||
"http://172.*.*.*:*")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package cn.rosercode.ledgerly.email;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -97,7 +98,12 @@ public class EmailBillStorage {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清洗附件文件名,去除路径分隔符与非法字符。
|
||||
* 清洗附件文件名,去除路径分隔符与非法字符,并保证结果可安全落盘。
|
||||
*
|
||||
* <p>仅用字符串截取末段而非 {@code Paths.get},避免在文件系统编码非 UTF-8
|
||||
* (如 locale 未设置、{@code sun.jnu.encoding=ANSI_X3.4-1968})的运行环境下,
|
||||
* 对含中文的文件名抛出 {@code InvalidPathException}(Malformed input or input
|
||||
* contains unmappable characters)。非 ASCII 名称统一转为百分号编码,确保纯 ASCII。</p>
|
||||
*
|
||||
* @param fileName 原始文件名
|
||||
* @return 安全的文件名
|
||||
@@ -106,8 +112,16 @@ public class EmailBillStorage {
|
||||
if (fileName == null || fileName.isBlank()) {
|
||||
return "attachment";
|
||||
}
|
||||
Path namePath = Paths.get(fileName).getFileName();
|
||||
String name = namePath == null ? fileName : namePath.toString();
|
||||
return name.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||
String name = fileName.replace('\\', '/');
|
||||
int slash = name.lastIndexOf('/');
|
||||
if (slash >= 0) {
|
||||
name = name.substring(slash + 1);
|
||||
}
|
||||
name = name.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||
// 含非 ASCII 字符时转为百分号编码,避免文件系统编码不支持中文导致保存失败
|
||||
if (!StandardCharsets.US_ASCII.newEncoder().canEncode(name)) {
|
||||
name = URLEncoder.encode(name, StandardCharsets.UTF_8).replace("+", "%20");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,3 +48,5 @@ knife4j:
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
# 信任前置 nginx/frp 透传的 X-Forwarded-*,使 HTTPS 同源请求被正确识别、避免被误判为跨域
|
||||
forward-headers-strategy: framework
|
||||
|
||||
Reference in New Issue
Block a user