SpringCloud+Minio+Nginx实现分布式文件鉴权管理

最近需要使用Minio替代原来的SFTP服务器,在此记录下相关操作。

一、目的

对于整个系统需要有一个高可用且能进行复杂权限校验的文件管理功能,故此需要一个统一管理文件的文件管理服务,可以设置相关联文件上传格式、大小、权限等。

Minio作为一个非常成熟的分布式存储框架,拥有丰富的SDK支持,所以使用Minio主要实现方案。

二、Minio安装

我这次安装主要参考网上的博文教程

Minio:分布式数据存储搭建 - 知乎

三、Minio相关配置

            
                io.minio
                minio
                8.4.1
            
# Minio配置
minio:
  url: http://127.0.0.1:19000
  # 账号
  accessKey: xxxxx
  # 密码
  secretKey: xxxxx
  # MinIO桶名字
  bucketName: xxxxx


/**
 * Minio 配置信息
 */
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
    /**
     * 服务地址
     */
    private String url;

    /**
     * 用户名
     */
    private String accessKey;

    /**
     * 密码
     */
    private String secretKey;

    /**
     * 存储桶名称
     */
    private String bucketName;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public String getBucketName() {
        return bucketName;
    }

    public void setBucketName(String bucketName) {
        this.bucketName = bucketName;
    }

    @Bean
    public MinioClient getMinioClient() {
        return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
    }
}

minio工具类


@Slf4j
@Component
@RefreshScope
public class MinioUtil {
    @Autowired
    private MinioClient minioClient;

    @Value("${file.url.auth.origin:}")
    private String originAuthUrl;

    @Value("${file.url.auth.replace:}")
    private String replaceAuthUrl;

    @Value("${file.url.notauth.origin:}")
    private String originNotAuthUrl;

    @Value("${file.url.notauth.replace:}")
    private String replaceNotAuthUrl;

    /**
     * 上传文件流
     *
     * @param objectPath  文件路径+文件名
     * @param inputStream 文件流
     * @param bucket      桶名称
     */
    public void upload(String objectPath, InputStream inputStream, String bucket) {
        log.info("上传文件 objectPath = {}", objectPath);
        try {
            minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectPath).stream(inputStream, inputStream.available(), -1).build());
        } catch (Exception e) {
            log.error("上传文件出错 objectPath = {}", objectPath, e);
            throw new FilestoreException(FileResultEnum.FILE_UPLOAD_FAIL);
        }
    }

    /**
     * 上传文件字节数组
     *
     * @param objectPath 文件路径+文件名
     * @param byteArr    文件字节数组
     * @param bucket     桶名称
     * @return
     */
    public void upload(String objectPath, byte[] byteArr, String bucket) {
        upload(objectPath, new ByteArrayInputStream(byteArr), bucket);
    }

    /**
     * 判断文件是否存在
     *
     * @param objectPath 文件路径+文件名
     * @param bucket     桶名称
     * @return
     */
    public boolean isExists(String objectPath, String bucket) {
        boolean exist = true;
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(bucket).object(objectPath).build());
        } catch (Exception e) {
            log.info("文件{}不存在", objectPath);
            exist = false;
        }
        return exist;
    }

    /**
     * 下载文件字节数组
     *
     * @param objectPath 文件路径+文件名
     * @param bucket     桶名称
     * @return
     */
    public byte[] download(String objectPath, String bucket) {
        log.info("下载文件字节数组 filename = {}", objectPath);
        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucket)
                        .object(objectPath)
                        .build())) {
            return IOUtils.toByteArray(stream);
        } catch (Exception e) {
            log.error("下载文件出错 objectPath = {}", objectPath, e);
            throw new FilestoreException(FileResultEnum.DOWNLOAD_FILE_FAIL);
        }
    }

    /**
     * 下载文件到本地
     *
     * @param objectPath 文件路径+文件名
     * @param savePath   存储目录
     * @param fileName   文件名
     * @param bucket     桶名称
     */
    public void download(String objectPath, String savePath, String fileName, String bucket) {
        log.info("下载文件到本地 name = {}, savePath = {}", objectPath, savePath);
        try {
            // 路径遍历漏洞
            savePath = XssCleanRuleUtils.xssCleanFile(savePath);
            File file = FileUtils.getFile(savePath);
            if (!file.exists()) {
                if (file.mkdirs()) {
                    log.info("文件创建成功");
                } else {
                    log.info("文件创建失败");
                }
            }
            minioClient.downloadObject(DownloadObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectPath)
                    .filename(savePath + File.separator + fileName)
                    .build());
        } catch (Exception e) {
            log.error("下载文件到本地出错 objectPath = {}, savePath = {}", objectPath, savePath, e);
            throw new FilestoreException(FileResultEnum.DOWNLOAD_FILE_FAIL);
        }
    }

    public String getUrl(String objectPath, String bucket) {
        log.info("获取文件下载地址 name = {}", objectPath);
        try {
            String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucket)
                    .object(objectPath)
                    .build());
            if (FilePathConstant.AUTH_BUCKET.equals(bucket)) {
                return url.replace(originAuthUrl, replaceAuthUrl);
            } else {
                return url.replace(originNotAuthUrl, replaceNotAuthUrl);
            }
        } catch (Exception e) {
            log.error("获取文件地址出错 objectPath = {}", objectPath, e);
            throw new FilestoreException(FileResultEnum.DOWNLOAD_FILE_FAIL);
        }
    }

    public void delete(String objectPath, String bucket) {
        log.info("删除文件 name = {}", objectPath);
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectPath)
                    .build());
        } catch (Exception e) {
            log.error("删除文件出错 objectPath = {}", objectPath, e);
            throw new FilestoreException(FileResultEnum.DELETE_FILE_FAIL);
        }
    }

三、nginx配置

为了避免大文件在各个微服务之间的传输从而导致带宽被占用,所以使用nginx的内部重定向(X-Accel-Redirect)来获取文件。

文件类型主要分为两大类:

一、不需要鉴权的文件

任何人不需要任何校验,则可以通过链接直接下载访问相关的文件。

location ^~  /static {
           alias   /sftp/sftpuser/files/not/;
           try_files $uri $uri/ /index.html;
        }

二、需要鉴权的文件

用户先要经过后台的相关校验才能下载访问相关的文件。

nginx需要在 location 中加入 "internal", 声明仅限内部调用。

location /auth {
                internal;
                alias /sftp/sftpuser;
        }

java代码也需要在返回的response里面加上X-Accel-Redirect来重定向

public BaseResult downloadWeb(String number, String operationType, String password, HttpServletResponse response) throws IOException {
        // 下载-返回给前台文件流
        DownloadResponse downloadResponse = fileInfoService.downloadFile(number, operationType, password);
        log.info("下载返回参数 downloadResponse={}", downloadResponse);
        response = FileDownloadUtils.setResponseByDownload(response, downloadResponse.getFileName());
        response.setHeader("Content-Type", "application/octet-stream");
        //HTTP响应截断漏洞
        response.setHeader("X-Accel-Redirect", URLEncoder.encode(XssCleanRuleUtils.xssClean(downloadResponse.getFilePath().replace(baseUrl, "")),
                "UTF-8"));
        response.setHeader("X-Accel-Buffering", "yes");//是否使用Nginx缓存,默认yes
        return BaseResult.success(downloadResponse);
    }

你可能感兴趣的:(java,spring,cloud)