Secure File Upload and Preview with Spring Boot, Minio, and KKFile
This article demonstrates how to securely upload files to Minio, stream them for preview via KKFile without exposing the Minio endpoint, and protect download URLs with token validation, providing complete Java code examples for upload, download, and preview URL generation.
The author, a senior architect, presents a solution that uses Spring Boot, Minio, and KKFile to upload files, generate preview URLs, and download files securely without exposing the Minio address, adding token validation for added safety.
Preface
The previous article introduced kkfile for file preview but exposed the Minio address; this guide improves the approach by using file streams and token authentication.
1. File Upload
Upload service implementation:
public void uploadFile(MultipartFile file) throws Exception {
String fileName = System.currentTimeMillis() + "-" + file.getOriginalFilename();
PutObjectArgs args = PutObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build();
client.putObject(args);
}Upload controller:
@PostMapping("upload")
public RestResult
upload(MultipartFile file) {
try {
sysFileService.uploadFile(file);
} catch (Exception e) {
log.error("上传文件失败", e);
return RestResult.fail(e.getMessage());
}
}2. File Download
Download service implementation (streaming the file and setting response headers):
public void download(String filename, HttpServletResponse response) throws ServiceException {
try {
InputStream inputStream = client.getObject(
GetObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.object(filename)
.build());
response.setContentType("application/octet-stream;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
OutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[4096];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
inputStream.close();
outputStream.close();
} catch (Exception e) {
log.error("文件下载失败:" + e.getMessage());
throw new ServiceException("文件下载失败");
}
}Download controller (token must be the last path variable):
@ApiOperation("文件下载")
@GetMapping("/download/{token}/{filename}")
public void getDownload(@PathVariable("token") String token,
@PathVariable("filename") String filename,
HttpServletResponse response) {
tokenUtils.validateToken(token);
sysFileService.download(filename, response);
}3. File Preview URL Generation
Service that creates a preview URL (the token is validated before the request reaches this method):
public String getPreviewUrl(String filename) throws UnsupportedEncodingException {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
if (request == null || StringUtils.isBlank(request.getHeader(TokenConstants.AUTHENTICATION))) {
throw new ServiceException("未获取到有效token");
}
String previewUrl = filePreviewUrl + FileUploadUtils.base64UrlEncode(
fileDownloadUrl + "/" + token + "/" + filename);
return previewUrl + "&fullfilename=" + URLEncoder.encode(filename, "UTF-8");
}Utility for Base64 URL encoding:
public static String base64UrlEncode(String url) throws UnsupportedEncodingException {
String base64Url = Base64.getEncoder().encodeToString(url.getBytes(StandardCharsets.UTF_8));
return URLEncoder.encode(base64Url, "UTF-8");
}Preview URL controller:
@GetMapping("/getPreviewUrl")
public RestResult
getPreviewUrl(String filename) throws UnsupportedEncodingException {
return RestResult.ok(sysFileService.getPreviewUrl(filename));
}Test
Assuming:
File service address: http://file-server
KKFile service address: http://kkfile-server
File name: xxxx.docx
The generated preview URL looks like:
http://kkfile-server/onlinePreview?url=aHR0cDovLzE3Mi4xNi41MC4y....&fullfilename=xxxx.docx
Where the aHR0cDovLzE3Mi4xNi41MC4y.... part is the Base64‑encoded string produced by FileUploadUtils.base64UrlEncode("http://file-server/" + token + "/" + filename) .
Two important notes:
The @GetMapping("/download/{token}/{filename}") mapping requires the filename parameter to be placed last.
The token validation must be allowed through any interceptor so that the KKFile service can retrieve the file stream without additional authentication.
Overall, the solution provides a secure, token‑protected file preview mechanism that avoids exposing Minio endpoints while leveraging Spring Boot’s MVC framework.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.