解决MinIO 客户端直传架构在反向代理部署下预签名 URL 失效问题
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user