解决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 'server-only'
import { Client } from 'minio'; import { Client } from 'minio';
// 初始化 MinIO 客户端 // 内部客户端:用于 statObject、removeObject 等服务端直连操作,以及 presignedPostPolicy
export const minioClient = new Client({ export const minioClient = new Client({
endPoint: process.env.MINIO_ENDPOINT || 'localhost', endPoint: process.env.MINIO_ENDPOINT || 'localhost',
port: parseInt(process.env.MINIO_API_PORT || '9000'), port: parseInt(process.env.MINIO_API_PORT || '9000'),
@@ -10,6 +10,32 @@ export const minioClient = new Client({
secretKey: process.env.MINIO_ROOT_PASSWORD || '', 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'; export const BUCKET_NAME = process.env.MINIO_BUCKET || 'app-files';
/** /**
@@ -166,6 +192,9 @@ export async function generatePresignedPostPolicy(
// 精确匹配 // 精确匹配
policy.setContentType(allowedContentType); policy.setContentType(allowedContentType);
} }
} else {
// 未指定类型限制时,允许任意 Content-Type客户端上传时会设置此字段Policy 中必须声明)
policy.policy.conditions.push(['starts-with', '$Content-Type', '']);
} }
if (allowOriginalFilename) { if (allowOriginalFilename) {
@@ -255,8 +284,8 @@ export async function generatePresignedGetObject(
} }
} }
// 生成预签名 URL // 使用公网客户端生成预签名 URL,确保 V4 签名中的 Host 与浏览器访问地址一致
const url = await minioClient.presignedGetObject( const url = await presignClient.presignedGetObject(
BUCKET_NAME, BUCKET_NAME,
objectName, objectName,
expirySeconds, expirySeconds,
@@ -264,7 +293,7 @@ export async function generatePresignedGetObject(
); );
return { return {
url: replaceUrlBase(url), url,
expiresIn: expirySeconds, expiresIn: expirySeconds,
}; };
} catch (error) { } catch (error) {