Tư duy Feynman • Trực quan hình họa • Java / Spring Boot

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)
Client Request Gọi /admin/dashboard Spring Security Filter SecurityContextHolder @PreAuthorize Kiểm tra hasRole('ADMIN') Controller Method Thực thi nghiệp vụ 🛡️ Kiểm soát truy cập theo vai trò (RBAC) chặt chẽ ở tầng Backend
🧪 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ì?
A. Bùng nổ số lượng vai trò (Role Explosion) khi cần phân quyền chi tiết theo ngữ cảnh cụ thể.
B. Không thể lưu trữ được trên cơ sở dữ liệu quan hệ.
C. RBAC không tương thích với JWT.
D. Tốc độ xử lý của RBAC luôn chậm hơn 1 giây.
📝 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
Doctor Request Bác sĩ khoa Da Liễu ABAC Policy Engine Đánh giá đa thuộc tính Dept Mismatch Da liễu != Tim mạch Access Denied Từ chối an toàn 🛡️ ABAC: Đánh giá quyền hạn theo ngữ cảnh đa thuộc tính (User, Resource, Environment)
🧪 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?
A. Thời gian truy cập trong ngày và dải địa chỉ IP nội bộ của công ty.
B. Vai trò của người dùng (Admin/User).
C. Định dạng file PDF của tài liệu.
D. Họ và tên của người dù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)

#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)
GET /order/501 User ID = 12 JPA Query WHERE id=:id AND user.id=:uid Ownership Check Khớp chủ sở hữu Order Data Trả về an toàn 🛡️ Bảo vệ mức đối tượng: Ràng buộc ID tài nguyên với ID người dùng sở hữu
🧪 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?
A. Vị trí số 1 (Lỗ hổng nguy hiểm và phổ biến nhất trên API).
B. Vị trí số 10.
C. Không nằm trong bảng xếp hạng OWASP.
D. Vị trí số 5.
📝 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)
Hidden Frontend Button Nút bị ẩn trên web Postman Direct Call POST /api/admin/reset Spring Security Filter Chặn đứng HTTP 403 Protected Function Chức năng an toàn 🛡️ Function-level Auth: Bảo vệ từng endpoint chức năng ở Backend Controller
🧪 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?
A. Vì kẻ tấn công có thể dễ dàng mở DevTools, trích xuất URL của API và gửi HTTP request trực tiếp mà không cần bấm qua giao diện.
B. Vì mã CSS có thể bị lỗi giao diện.
C. Vì người dùng có thể dùng màn hình cảm ứng.
D. Vì trình duyệt sẽ tự động hiển thị lại nút sau 10 giây.
📝 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)
Client Request Truy cập /admin/config Deny-by-default Rule Mặc định từ chối tất cả Explicit Whitelist Chỉ mở khi có permitAll/hasRole Enforced Security Chống Broken Access 🛡️ Deny-by-default: Mặc định từ chối mọi truy cập trừ khi được cấp quyền rõ ràng
🧪 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?
A. Nguyên tắc từ chối theo mặc định (Deny-by-default) - Mọi tài nguyên đều bị khóa trừ khi được cấp quyền rõ ràng.
B. Mặc định cho phép tất cả, chỉ chặn các URL có trong danh sách đen.
C. Chỉ dùng phương thức HTTP POST.
D. Đặt tên file thật dài và khó đoán.
📝 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)
User 12 Request GET /api/order/1002 Tampered ID Sửa thành ID của User 99 Spring Security Check user_id == current_user Block Unauthorized HTTP 404/403 🛡️ Chống IDOR: Ràng buộc người dùng đăng nhập trong câu truy vấn CSDL
🧪 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?
A. Không, UUID chỉ làm ID khó đoán hơn (Security by Obscurity), nếu ID bị lộ thì bất kỳ ai có link vẫn mở được nếu thiếu bước kiểm tra quyền sở hữu ở Backend.
B. Có, UUID là giải pháp bảo mật tuyệt đối không cần kiểm tra quyền nữa.
C. Có, vì hacker không thể gõ được UUID.
D. UUID làm cho hệ thống không thể bị tấn công SQLi.
📝 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)
Spring Boot App Kết nối Database App DB User Chỉ SELECT, INSERT, UPDATE Chặn DDL Cấm DROP, ALTER, GRANT Database Dữ liệu an toàn 🛡️ Nguyên tắc đặc quyền tối thiểu: Thu hồi hoàn toàn quyền DDL nguy hiểm
🧪 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?
A. Áp dụng xuyên suốt tất cả các tầng: Tài khoản người dùng, Quyền kết nối Database, Quyền tiến trình OS/Docker, Quyền IAM Cloud API.
B. Chỉ áp dụng cho tài khoản người dùng cuối (End-user).
C. Chỉ áp dụng khi ứng dụng đã đưa vào chạy thật (Production).
D. Chỉ áp dụng cho máy chủ Windows.
📝 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)