当我们要在前端页面直接完成文件上传时,往往需要通过后端服务来获取带有到期时间和自定义请求参数的预签名URL。使用minio客户端(MinioClient)的getPresignedObjectUrl(GetPresignedObjectUrlArgs args)方法可以创建预签名URL。

在实际的环境中可能存在以下情况

Minio客户端创建支持代理地址的预签名地址_对象存储

1)对象存储服务、后端服务和前端服务都部署在K8S环境或者某个内网中

2)后端服务在配置文件中配置的是对象存储的”内网地址“

3)后端服务如果配置存储服务的外网代理地址,则不允许访问(网络回环)

那么当需要在客户端直接上传文件到对象存储服务,而且不能在客户端暴露存储服务的密钥信息,则需要创建预签名的URL来进行文件的上传。可默认情况下返回给客户端的预签名URL是对象存储服务的内网地址,客户端根本访问不了。

解决该问题的两种方案

1)使用域名

上面的代理地址(183.69.122.1)统一使用域名,在后端服务中也配置对象存储服务的域名地址。客户端访问的时候,通过DNS解析出的地址是183.69.122.1, 而后端服务上解析出的DNS为192.168.1.3,这样返回给前端页面的预签名地址也是域名。

  • 如果部署环境是K8S,则通过coreDNS配置即可
  • 如果是服务器或虚拟机,则配置操作系统的hosts文件即可

2)扩展minio的客户端代码

通过扩展代码,可以使用配置的”代理端点“来创建预签名地址,这样代理端点则可配置成外网代理地址

<dependency>
	<groupId>io.minio</groupId>
  <artifactId>minio</artifactId>
  <version>8.5.2</version>
</dependency>

创建可变端点的Minio异步客户端


/**
 * @description 可变端点的Minio异步客户端
 */
public class MutableMinioAsyncClient extends MinioAsyncClient {

    // 代理端点
    private String proxyEndpoint ;

    protected MutableMinioAsyncClient(MinioAsyncClient client,String proxyEndpoint) {
        super(client);
        this.proxyEndpoint = proxyEndpoint ;
    }

    @Override
    public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args)
            throws ErrorResponseException, InsufficientDataException, InternalException,
            InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException,
            XmlParserException, ServerException {
        checkArgs(args);

        byte[] body =
                (args.method() == Method.PUT || args.method() == Method.POST) ? HttpUtils.EMPTY_BODY : null;

        Multimap<String, String> queryParams = newMultimap(args.extraQueryParams());
        if (args.versionId() != null) queryParams.put("versionId", args.versionId());

        String region = null;
        try {
            region = getRegionAsync(args.bucket(), args.region()).get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throwEncapsulatedException(e);
        }

        if (provider == null) {
            HttpUrl url = buildUrl(args.method(), args.bucket(), args.object(), region, queryParams);
            return url.toString();
        }

        Credentials creds = provider.fetch();
        if (creds.sessionToken() != null) queryParams.put("X-Amz-Security-Token", creds.sessionToken());
        HttpUrl url = buildUrl(args.method(), args.bucket(), args.object(), region, queryParams);
        // 重点:修改url
        if (StringUtils.isNotBlank(proxyEndpoint)){
            url = url.newBuilder(url.toString().replace(baseUrl.toString(), proxyEndpoint)).build();
        }
        Request request =
                createRequest(
                        url,
                        args.method(),
                        args.extraHeaders() == null ? null : httpHeaders(args.extraHeaders()),
                        body,
                        0,
                        creds);
        url = Signer.presignV4(request, region, creds.accessKey(), creds.secretKey(), args.expiry());
        return url.toString();
    }
}

创建客户端


/**
 * @description 可变端点的Minio客户端
 */
public class MutableMinioClient extends MinioClient {

    private MutableMinioAsyncClient asyncClient ;

    public MutableMinioClient(MinioClient client,MutableMinioAsyncClient asyncClient) {
        super(client);
        this.asyncClient = asyncClient ;
    }

    @Override
    public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args)
            throws ErrorResponseException, InsufficientDataException, InternalException,
            InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException,
            XmlParserException, ServerException {
        return asyncClient.getPresignedObjectUrl(args);
    }
}

客户端配置


/**
 * @description minio配置类
 */
@Configuration
public class MutableMinioConfiguration {

    @Value("${application.storage.minio.proxy-endpoint:}")
    private String proxyEndpoint ;

    @Bean
    public MinioClient mutableMinioClient(ApplicationProperties applicationProperties){
        StorageProperties.MinioProperties minioProperties = applicationProperties.getStorage().getMinio() ;
        MinioClient minioClient = MinioClient.builder()
                .endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();
        return new MutableMinioClient(minioClient,createMinioAsyncClient(minioProperties)) ;
    }

    private MutableMinioAsyncClient createMinioAsyncClient(StorageProperties.MinioProperties minioProperties){
        MinioAsyncClient.Builder asyncClientBuilder  = MinioAsyncClient.builder();;
        asyncClientBuilder.endpoint(minioProperties.getEndpoint()) ;
        asyncClientBuilder.credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey());
        MinioAsyncClient minioAsyncClient = asyncClientBuilder.build() ;
        return new MutableMinioAsyncClient(minioAsyncClient,proxyEndpoint) ;
    }
}

如此,在需要的地方,直接引入MinioClient实例Bean即可。



附件:

1)minio的windows安装:https://min.io/docs/minio/windows/index.html

2)minio客户的预签名地址创建:Java Client API Reference — MinIO Object Storage for Linux