Chương 02: Authorization (Kiểm soát quyền truy cập)
Làm chủ nghệ thuật phân quyền với Java & Spring Security: Đủ 7 khái niệm gồm RBAC @PreAuthorize, ABAC PermissionEvaluator, Object-level Authorization, Function-level Authorization, Broken Access Control, IDOR/BOLA và Đặc quyền tối thiểu Least Privilege.
#01 Role-Based Access Control — RBAC (@PreAuthorize in Spring Security)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
RBAC giống như phân quyền trong bệnh viện: Bác sĩ được kê đơn, Y tá được phát thuốc, Bệnh nhân chỉ được xem bệnh án của mình. Trong Spring Boot, ta dùng chú thích @PreAuthorize("hasRole('ADMIN')") ở tầng Controller hoặc Service để bảo vệ từng phương thức.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Role-Based Access Control — RBAC (@PreAuthorize in Spring Security)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Role USER gọi /adminRole ADMIN gọi /adminSửa role trên request header
⚠️ Lỗi phân quyền: Client tự gửi header X-Role: ADMIN và server tin tưởng hoàn toàn mà không xác minh lại từ database/token!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// NGUY HIỂM: Không kiểm tra quyền ở Controller
@GetMapping("/admin/users")
public List getAllUsers() {
return userRepository.findAll();
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING SECURITY: @PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public List getAllUsers() {
return userService.getAllUsers();
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Điểm yếu lớn nhất của mô hình RBAC thuần túy khi hệ thống phát triển phức tạp là gì?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#02 Permission-based & Attribute-based Access Control — PBAC / ABAC
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Nếu RBAC là tấm thẻ 'Bác sĩ', thì ABAC/PBAC giống như một người bảo vệ thông minh kiểm tra nhiều yếu tố: Bạn là Bác sĩ (Ai), muốn mở bệnh án (Hành động gì), thuộc khoa Tim mạch (Thuộc tính tài nguyên), vào lúc 2 giờ sáng từ mạng nội bộ bệnh viện (Ngữ cảnh môi trường). Mọi điều kiện khớp thì cửa mới mở.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Permission-based & Attribute-based Access Control — PBAC / ABAC
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Đúng khoa + Trong giờ làm việcKhác khoa chuyên mônTruy cập từ IP ngoài cơ quan
⚠️ Nếu chỉ check role: Bác sĩ da liễu vẫn xem được hồ sơ mật của bệnh nhân tim mạch vì cả hai đều có vai trò 'Doctor'!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// RBAC ĐƠN ĐIỆU: Cứ là Doctor thì xem được hết
if (user.hasRole("DOCTOR")) {
return medicalRecordRepo.findById(id);
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN ABAC TRONG SPRING BOOT: Dùng PermissionEvaluator tùy biến
@Component
public class MedicalRecordPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
CustomUserDetails doctor = (CustomUserDetails) auth.getPrincipal();
MedicalRecord record = (MedicalRecord) targetDomainObject;
boolean isAssignedDoctor = record.getDoctorId().equals(doctor.getId());
boolean isSameDept = record.getDepartment().equals(doctor.getDepartment());
return isAssignedDoctor || (isSameDept && doctor.isOnDuty());
}
}
// Áp dụng trong Controller:
// @PreAuthorize("hasPermission(#record, 'READ')")
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Yếu tố nào sau đây là một ví dụ về thuộc tính ngữ cảnh môi trường (Environment Attribute) trong ABAC?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#03 Object-level Authorization (Phân quyền mức đối tượng)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Tưởng tượng bạn vào khách sạn và nhận chìa khóa phòng số 201. Bạn có phải là khách thuê phòng hợp lệ không? Có. Nhưng nếu bạn lấy chiếc chìa đó sang cắm vào cửa phòng 202 và mở được, đó là lỗi phân quyền mức đối tượng. Hệ thống biết bạn là ai, nhưng quên kiểm tra xem bạn có quyền sở hữu chiếc phòng đó hay không.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Object-level Authorization (Phân quyền mức đối tượng)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
GET /api/orders/501GET /api/invoices/9999Thay đổi ID trên URL
⚠️ Lỗ hổng BOLA/IDOR: User 12 thay đổi ID tài liệu thành 501 trên URL và tải về toàn bộ hợp đồng mật của User 99!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// THẢM HỌA: findById không kiểm tra người yêu cầu
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Ràng buộc ID người dùng đăng nhập
@GetMapping("/orders/{orderId}")
public OrderResponse getOrder(@PathVariable Long orderId, @AuthenticationPrincipal CustomUserDetails user) {
return orderRepository.findByIdAndUserId(orderId, user.getId())
.map(orderMapper::toDto)
.orElseThrow(() -> new ResourceNotFoundException("Đơn hàng không tồn tại"));
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
BOLA (Broken Object Level Authorization) nằm ở vị trí nào trong danh sách OWASP API Top 10?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#04 Function-level Authorization in Spring Security (Phân quyền mức chức năng)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Nhà hàng có cửa trước đón khách và cửa sau dành cho nhân viên tiếp tế hàng. Nếu nhà hàng chỉ khóa cửa trước khi hết giờ mà quên khóa cửa sau, bất kỳ vị khách nào lách qua cửa sau cũng có thể bước thẳng vào kho tiền. Đây là lỗi ẩn nút bấm trên giao diện người dùng nhưng quên bảo vệ API endpoint ở Backend Controller.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Function-level Authorization in Spring Security (Phân quyền mức chức năng)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
POST /api/admin/users/deleteGET /admin/export-all-dataĐổi HTTP Method: GET -> POST
⚠️ Giao diện đã ẩn nút Xóa. Tuy nhiên hacker dùng Postman gửi thẳng POST /api/admin/delete-database và lệnh vẫn được thực thi!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// Frontend giấu nút:
// Nhưng Controller không kiểm tra:
@PostMapping("/api/admin/delete-user")
public void deleteUser(@RequestParam Long id) {
userRepo.deleteById(id); // Ai gọi cũng xóa được!
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN: Bảo vệ chặt chẽ ở Backend
@PreAuthorize("hasAuthority('USER_DELETE')")
@PostMapping("/api/admin/delete-user")
public ResponseEntity> deleteUser(@RequestParam Long id) {
userService.deleteUser(id);
return ResponseEntity.ok().build();
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Vì sao việc chỉ ẩn nút chức năng (Hide UI elements) trên giao diện Frontend không thể coi là một biện pháp bảo mật?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#05 Broken Access Control (Kiểm soát truy cập bị phá vỡ - OWASP #1)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Giống như tòa nhà chung cư có thang máy nhưng không lắp khóa thẻ từ tầng. Ai bước vào thang máy cũng bấm được lên tầng Penthouse của chủ tịch và mở cửa bước vào phòng khách mà không gặp bất kỳ chướng ngại vật nào. Nguyên tắc Deny-by-default tuyên bố: Mọi cánh cửa mặc định bị khóa trừ khi có chìa khóa cấp phép.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Broken Access Control (Kiểm soát truy cập bị phá vỡ - OWASP #1)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
/config/database.json/backup/dump.sql/api/v2/debug/keys
⚠️ File sao lưu nhạy cảm đặt công khai trong thư mục web root, ai gõ URL cũng tải về được!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// CẤU HÌNH LỖI: Cấu hình mặc định cho phép tất cả (Permit All)
http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING SECURITY: anyRequest().authenticated()
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated() // Mặc định BẮT BUỘC phải đăng nhập!
);
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Nguyên tắc cốt lõi nào trong kiến trúc bảo mật giúp ngăn ngừa Broken Access Control hiệu quả nhất?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#06 IDOR / BOLA Prevention (Tham chiếu đối tượng trực tiếp không an toàn)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn đi gửi đồ ở siêu thị và nhận thẻ số 45. Thay vì nhân viên siêu thị yêu cầu bạn xuất trình vé khớp với nhận diện khuôn mặt, họ chỉ nhìn vào con số trên thẻ. Bạn lấy bút sửa số 45 thành 46 và đưa cho nhân viên, nhân viên liền mở tủ 46 và giao toàn bộ túi xách của người khác cho bạn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): IDOR / BOLA Prevention (Tham chiếu đối tượng trực tiếp không an toàn)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
/api/order/5402/api/chat/messages?chatId=12UUID thay cho Integer ID
⚠️ Thay đổi con số ID tuần tự trên URL cho phép duyệt qua xem trộm toàn bộ đơn hàng của tất cả khách hàng khác!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
// LỖ HỔNG IDOR KINH ĐIỂN
@GetMapping("/invoices/{id}")
public Invoice getInvoice(@PathVariable Long id) {
return invoiceRepo.findById(id).orElseThrow();
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN: Dùng UUID + Ràng buộc Session User trong JPA
@GetMapping("/invoices/{uuid}")
public InvoiceResponse getInvoice(@PathVariable UUID uuid, @AuthenticationPrincipal CustomUserDetails user) {
return invoiceRepo.findByUuidAndCustomerId(uuid, user.getId())
.map(invoiceMapper::toDto)
.orElseThrow(() -> new ResourceNotFoundException("Hóa đơn không tồn tại"));
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Sử dụng UUID v4 (chuỗi ngẫu nhiên 128-bit) thay cho ID tự tăng (1, 2, 3...) có giải quyết TRIỆT ĐỂ lỗi IDOR hay không?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)
#07 Principle of Least Privilege in Java (Đặc quyền tối thiểu)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Nếu bạn thuê thợ đến sơn tường phòng khách, bạn chỉ đưa chìa khóa mở cổng và cửa phòng khách. Bạn tuyệt đối không đưa chùm chìa khóa vạn năng mở được cả két sắt, phòng ngủ và tài khoản ngân hàng của bạn. Nếu người thợ đó sơ ý đánh mất chìa khóa, ngôi nhà bạn vẫn được bảo vệ an toàn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Principle of Least Privilege in Java (Đặc quyền tối thiểu)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
DB User: 'root' (Full đặc quyền)DB User: 'app_user' (Chỉ SELECT, INSERT)Chạy container Docker dưới quyền root
⚠️ Ứng dụng dùng tài khoản 'root' của MySQL: Nếu dính lỗi SQLi, kẻ tấn công có quyền DROP DATABASE và đọc file hệ thống!
⚖️ BƯỚC 3: SO SÁNH CODE LỖI VS CODE CHUẨN (JAVA / SPRING BOOT)
❌ Vulnerable Java Code (Không an toàn)
# application.properties dùng user root tối cao
spring.datasource.username=root
spring.datasource.password=root_password
✅ Secure Java Code (Chuẩn bảo mật)
# application.properties dùng user nghiệp vụ riêng biệt
spring.datasource.username=app_order_service
spring.datasource.password=${DB_APP_PASSWORD}
# Trên MySQL:
# GRANT SELECT, INSERT, UPDATE ON shop_db.orders TO 'app_order_service'@'%';
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Nguyên tắc Đặc quyền tối thiểu (Least Privilege) nên được áp dụng ở những tầng nào trong kiến trúc phần mềm?
📝 Tóm tắt nhanh bằng lời của bạn (Feynman Notebook):
(Tự động lưu vào trình duyệt)