解决MinIO 客户端直传架构在反向代理部署下预签名 URL 失效问题

This commit is contained in:
2026-03-18 11:27:52 +08:00
parent b9c83617ad
commit ff9b5918fd

View File

@@ -1,7 +1,7 @@
import 'server-only'
import { Client } from 'minio';
// 初始化 MinIO 客户端
// 内部客户端:用于 statObject、removeObject 等服务端直连操作,以及 presignedPostPolicy
export const minioClient = new Client({
endPoint: process.env.MINIO_ENDPOINT || 'localhost',
port: parseInt(process.env.MINIO_API_PORT || '9000'),
@@ -10,6 +10,32 @@ export const minioClient = new Client({
secretKey: process.env.MINIO_ROOT_PASSWORD || '',
});
/**
* 公网客户端:用于生成 presigned GET/PUT URL
*
* AWS Signature V4 会将 Host 头纳入签名计算,因此 presigned GET/PUT URL
* 必须使用与浏览器访问一致的公网地址生成,否则签名校验失败。
* 而 POST Policy 不含 Host 签名,只需替换返回的 URL 地址即可。
*/
function createPresignClient(): Client {
const serverUrl = process.env.MINIO_SERVER_URL;
if (!serverUrl) return minioClient;
const parsed = new URL(serverUrl);
const useSSL = parsed.protocol === 'https:';
const port = parsed.port ? parseInt(parsed.port) : (useSSL ? 443 : 80);
return new Client({
endPoint: parsed.hostname,
port,
useSSL,
accessKey: process.env.MINIO_ROOT_USER || '',
secretKey: process.env.MINIO_ROOT_PASSWORD || '',
});
}
const presignClient = createPresignClient();
export const BUCKET_NAME = process.env.MINIO_BUCKET || 'app-files';
/**
@@ -166,6 +192,9 @@ export async function generatePresignedPostPolicy(
// 精确匹配
policy.setContentType(allowedContentType);
}
} else {
// 未指定类型限制时,允许任意 Content-Type客户端上传时会设置此字段Policy 中必须声明)
policy.policy.conditions.push(['starts-with', '$Content-Type', '']);
}
if (allowOriginalFilename) {
@@ -255,8 +284,8 @@ export async function generatePresignedGetObject(
}
}
// 生成预签名 URL
const url = await minioClient.presignedGetObject(
// 使用公网客户端生成预签名 URL,确保 V4 签名中的 Host 与浏览器访问地址一致
const url = await presignClient.presignedGetObject(
BUCKET_NAME,
objectName,
expirySeconds,
@@ -264,7 +293,7 @@ export async function generatePresignedGetObject(
);
return {
url: replaceUrlBase(url),
url,
expiresIn: expirySeconds,
};
} catch (error) {