Spring Boot bài 16: Upload và download tài liệu có giới hạn
- 18-10-2026
- Toanngo92
- 0 Comments
Giáo trình Spring Boot MVC và REST API · Chương 7/12: Tìm kiếm, phân trang và tài liệu · Bài 16/28 · Biên soạn tháng 10/2026.
Mục lục
Mục tiêu bài học
Nhận multipart, kiểm tra tệp và tải xuống qua endpoint được bảo vệ thay vì công khai thư mục upload.
Thiết kế phạm vi nhỏ, rõ ràng
Bài này chỉ nhận tệp .txt UTF-8 tối đa 1 MB tại /admin/files. Giới hạn loại tệp giúp tập trung vào luồng upload; không phải bộ xử lý ảnh, PDF hay bộ quét mã độc tổng quát. Chỉ teacher được tải lên hoặc tải xuống trong mẫu, vì tài liệu là tài nguyên quản trị.
Form cần method POST và enctype multipart/form-data. Input có name=file để khớp @RequestParam MultipartFile file. accept=.txt chỉ gợi ý cho hộp chọn tệp, không phải kiểm tra server. th:action giúp form nhận CSRF token giống các form POST khác.
<form th:action="@{/admin/files}" method="post" enctype="multipart/form-data">
<input type="file" name="file" accept=".txt" required>
<button>Tải lên</button>
</form>
Tên file từ client không phải đường dẫn tin cậy
Không ghép getOriginalFilename() vào thư mục lưu rồi ghi trực tiếp. Tên có thể chứa đường dẫn, ký tự lạ hoặc trùng với tệp hiện có. FileStore chỉ dùng tên gốc để kiểm tra đuôi, kiểm tra độ lớn, giải mã UTF-8 nghiêm ngặt, từ chối byte NUL rồi tạo tên UUID mới. MIME do client gửi không được xem là bằng chứng nội dung.
Thư mục hvc.upload-dir ở ngoài static và templates. Khi tải xuống, service chỉ chấp nhận mã UUID.txt và kiểm tra tệp thường, không đi theo symlink. Việc giới hạn đường dẫn phải thực hiện ở server dù URL download chỉ do chính ứng dụng sinh ra.
Tệp trong mã nguồn mẫu: src/main/java/com/hocvietcode/academy/FileStore.java. Đoạn dưới được trích từ tệp này; xem bản tải về để có đủ import và phần còn lại.
public Path load(String id){
if(!id.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[.]txt"))throw new IllegalArgumentException("Mã tệp không hợp lệ");
Path path=root.resolve(id).normalize();
if(!path.startsWith(root) || !Files.isRegularFile(path,LinkOption.NOFOLLOW_LINKS))throw new CourseNotFound();
return path;
}
Download qua Controller
FileController trả Resource với Content-Disposition: attachment và kiểu text/plain; trình duyệt tải tài liệu thay vì tự coi nó là HTML. Mã file do server sinh được dùng làm tên tải về. Security tiếp tục kiểm tra teacher trên đường dẫn /admin/**.
Spring multipart đặt max-file-size=1MB và max-request-size=2MB; FileStore kiểm tra lại giới hạn nghiệp vụ. Tệp vượt giới hạn tầng multipart có thể bị chặn trước khi Controller chạy. Nếu xây sản phẩm, nên thêm trang báo lỗi dung lượng riêng, quota theo người dùng và cơ chế dọn tệp không còn được sử dụng.
Tệp trong mã nguồn mẫu: src/main/java/com/hocvietcode/academy/FileController.java. Đoạn dưới được trích từ tệp này; xem bản tải về để có đủ import và phần còn lại.
@GetMapping("/admin/files/{id}") ResponseEntity<Resource> download(@PathVariable String id){
Resource file=new FileSystemResource(files.load(id));
return ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN)
.header(HttpHeaders.CONTENT_DISPOSITION,ContentDisposition.attachment().filename(id).build().toString())
.header("X-Content-Type-Options","nosniff").body(file);
}
Lưu trữ và mở rộng
Upload vào ổ đĩa cục bộ chỉ phù hợp một instance hoặc volume được quản lý. Khi container bị thay thế, dữ liệu trong lớp ghi có thể mất; bài Docker sẽ gắn volume. Nhiều instance thường cần object storage hoặc hệ lưu trữ chung cùng metadata về chủ sở hữu và quyền truy cập.
Kiểm thử gồm tệp hợp lệ, tệp rỗng, quá dung lượng, sai đuôi, UTF-8 hỏng và mã chứa ../. Với ảnh/PDF, cần pipeline xác minh định dạng, giới hạn tài nguyên và chính sách bảo mật khác; không chỉ đổi danh sách đuôi trong ví dụ rồi coi là xử lý đầy đủ.
Lỗi thường gặp và lưu ý
- Không lưu file người dùng dưới static để ai biết URL cũng truy cập được ngoài quy tắc quyền.
- UUID tránh trùng tên, nhưng quyền download vẫn phải kiểm tra ở endpoint.
Ghi nhớ
Upload là một luồng dữ liệu và lưu trữ có giới hạn, tên do server quản lý và quyền truy cập được kiểm tra khi download.

