Chương 07: API Security Essentials
Bảo vệ toàn diện RESTful APIs: OAuth2, JWT Security, Bucket4j Rate Limiting, Jakarta Validation, BOLA/IDOR và phòng thủ DoS.
#01 API Authentication (Xác thực API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
API giống như một chiếc máy bán hàng tự động không có màn hình, chỉ có cổng cắm cáp giao tiếp dữ liệu. Máy không thể nhìn mặt bạn để biết bạn là ai. Nó đòi hỏi một chiếc 'Thẻ căn cước điện tử' chuẩn mực (như mTLS, OAuth2 Bearer Token) được mã hóa tại mỗi gói tin giao tiếp.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): API Authentication (Xác thực API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Gửi không có Token (Anonymous)Token hết hạn 1 ngàyXác thực mTLS hai chiều
⚠️ Thiếu xác thực API: Bất kỳ bot hay crawler nào trên mạng internet cũng gọi được API nội bộ của bạn!
⚖️ 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: Nhận token xác thực qua query parameter trên URL
@GetMapping("/api/v1/user/profile")
public ResponseEntity> getProfile(@RequestParam("token") String token) {
// Token hiển thị lộ liễu trên URL, bị ghi lại trong Browser History & Log Nginx!
Claims claims = jwtUtil.parseToken(token);
return ResponseEntity.ok(userService.getUser(claims.getSubject()));
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING SECURITY 6: Bearer Token qua Header Authorization
@Configuration
@EnableWebSecurity
public class ApiSecurityConfig {
@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.csrf(csrf -> csrf.disable()) // API Stateless không dùng Cookie
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Chuẩn mực xác thực nào hiện đang được sử dụng phổ biến và an toàn nhất cho các hệ thống RESTful API hiện đại?
📝 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 API Authorization (Phân quyền API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn có thẻ nhân viên bước vào công ty (Authentication), nhưng chiếc thẻ đó chỉ mở được cửa phòng làm việc của bạn, không thể mở cửa phòng Server hay phòng Kế toán (API Authorization). Một lỗi phổ biến trên API là xác thực thành công xong thì cấp luôn quyền gọi mọi hàm không hạn chế.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): API Authorization (Phân quyền API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Member gọi lệnh DELETEAdmin gọi lệnh DELETEUser A đọc dữ liệu User B
⚠️ Lỗi phân quyền API: Người dùng bình thường gọi API xóa người dùng khác và server vẫn thực thi lệnh thành cô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)
// NGUY HIỂM: Chỉ kiểm tra đã đăng nhập hay chưa (thiếu phân quyền chức năng)
@DeleteMapping("/api/v1/users/{id}")
public ResponseEntity> deleteUser(@PathVariable Long id) {
// Bất kỳ ai có tài khoản thường cũng có thể gọi endpoint này để xóa người khác!
userRepository.deleteById(id);
return ResponseEntity.ok("Deleted");
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Phân quyền phương thức với @PreAuthorize và Scope/Role
@DeleteMapping("/api/v1/users/{id}")
@PreAuthorize("hasAuthority('SCOPE_admin:write') and hasRole('SYSTEM_ADMIN')")
public ResponseEntity> deleteUser(@PathVariable Long id) {
userService.deleteUserWithAudit(id);
return ResponseEntity.noContent().build();
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Khái niệm 'Scope' trong OAuth2 đóng vai trò 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)
#03 API Rate Limiting & Throttling (Điều tiết lưu lượng API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
API của bạn giống như một vòi nước chữa cháy. Nếu bạn cho phép một người mở vòi hết cỡ với 10.000 lít/giây, cả khu phố sẽ bị mất nước và máy bơm sẽ cháy (Denial of Service). Rate Limiting là một chiếc đồng hồ đo lưu lượng thông minh: mỗi ứng dụng chỉ được lấy tối đa 60 lít mỗi phút.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): API Rate Limiting & Throttling (Điều tiết lưu lượng API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Spam 1000 req/s (DDoS)Token Bucket 60 req/minBurst traffic từ chối ngay lập tức
⚠️ Không có Rate Limit: Máy chủ bị quá tải CPU 100%, Database connection pool bị nghẽn làm tê liệt dịch vụ của người dù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)
// THẢM HỌA: Không giới hạn tốc độ gọi API -> Dễ bị DoS và Brute-force vét cạn tài nguyên
@PostMapping("/api/v1/otp/request")
public ResponseEntity> requestOtp(@RequestBody OtpRequest request) {
smsService.sendOtp(request.getPhoneNumber()); // Kẻ tấn công gọi 10.000 lần gây tốn tiền SMS!
return ResponseEntity.ok("OTP Sent");
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Dùng Bucket4j điều tiết request theo IP hoặc ClientId
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
private final Map cache = new ConcurrentHashMap<>();
private Bucket createNewBucket() {
return Bucket.builder()
.addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1))))
.build();
}
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
String clientIp = req.getRemoteAddr();
Bucket bucket = cache.computeIfAbsent(clientIp, k -> createNewBucket());
if (bucket.tryConsume(1)) {
return true;
} else {
res.setStatus(429);
res.setHeader("Retry-After", "60");
return false;
}
}
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Thuật toán nào sau đây thường được dùng phổ biến nhất để triển khai Rate Limiting trên các hệ thống phân tá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)
#04 API Input Validation (Kiểm định đầu vào API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
API nhận dữ liệu chủ yếu qua JSON Payload. Nếu bạn không kiểm tra cấu trúc của gói JSON, kẻ gian có thể gửi một gói JSON bị phình to vô tận (JSON Bomb) hoặc nhét vào các ký tự điều khiển phá vỡ cấu trúc của cơ sở dữ liệu NoSQL.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): API Input Validation (Kiểm định đầu vào API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Sai kiểu dữ liệuTrường lạ ngoài schemaJSON hợp lệ chuẩn Enterprise
⚠️ Sai lệch kiểu dữ liệu: API nhận chuỗi chữ vào trường số tiền, gây lỗi unhandled exception và làm sập tiến trình Node.js!
⚖️ 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: Nhận dữ liệu không kiểm tra ràng buộc kiểu và khoảng giá trị
@PostMapping("/api/v1/products")
public ResponseEntity> createProduct(@RequestBody ProductDto dto) {
// Nếu price âm hoặc name là chuỗi rác hàng triệu ký tự -> lỗi CSDL hoặc tràn RAM
Product p = new Product(dto.getName(), dto.getPrice());
return ResponseEntity.ok(productRepository.save(p));
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Jakarta Bean Validation với @Valid và Annotations
public record ProductDto(
@NotBlank(message = "Tên sản phẩm không được rỗng")
@Size(min = 2, max = 100, message = "Độ dài từ 2 đến 100 ký tự")
String name,
@NotNull(message = "Giá bắt buộc nhập")
@DecimalMin(value = "0.01", message = "Giá tối thiểu là 0.01")
BigDecimal price
) {}
@PostMapping("/api/v1/products")
public ResponseEntity> createProduct(@Valid @RequestBody ProductDto dto) {
Product p = productService.create(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(p);
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Tính năng .strict() trong thư viện xác thực Schema Zod có tác dụng 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)
#05 API Key Security (Bảo mật khóa API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
API Key giống như chiếc chìa khóa két sắt nhỏ giao cho đối tác tích hợp hệ thống. Nếu bạn để đối tác nhúng chiếc chìa khóa này vào mã nguồn JavaScript chạy trên trình duyệt (Frontend) hoặc đẩy lên kho mã nguồn công khai GitHub, bất kỳ ai cũng có thể lấy chiếc chìa đó để tiêu xài hết hạn mức tín dụng đám mây của bạn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): API Key Security (Bảo mật khóa API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Hardcode API Key trong source ReactĐọc từ process.env + Secret ManagerAPI Key có giới hạn IP và Scope
⚠️ Lộ API Key trên Frontend: Khóa bí mật 'sk_live_...' bị lộ công khai qua file bundle.js cho toàn thế giới!
⚖️ 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: Lưu API Key thô trong DB và so sánh chuỗi bằng equals thông thường
public boolean validateApiKey(String clientKey) {
ApiKey entity = apiKeyRepository.findByKey(clientKey); // Lộ key nếu DB bị leak
return entity != null && entity.getKey().equals(clientKey); // Dễ bị Timing Attack!
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Lưu chuỗi băm SHA-256 và so khớp an toàn với MessageDigest.isEqual
public boolean validateApiKey(String clientKey) {
if (clientKey == null) return false;
byte[] clientKeyHash = DigestUtils.sha256(clientKey);
ApiKey entity = apiKeyRepository.findByKeyPrefix(clientKey.substring(0, 8));
if (entity == null) return false;
return MessageDigest.isEqual(clientKeyHash, entity.getHashedSecret());
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Vì sao các Secret Key của dịch vụ thanh toán (như Stripe, PayPal) tuyệt đối KHÔNG ĐƯỢC phép nhúng vào ứng dụng di động hoặc web frontend?
📝 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 JWT Security for APIs (Bảo mật JWT trên API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn cấp một tấm thẻ thông hành có ghi hạn sử dụng 1 năm. Nếu nhân viên đó bị sa thải vào ngày hôm sau, họ vẫn ung dung cầm chiếc thẻ đó ra vào cổng công ty suốt 364 ngày còn lại vì bảo vệ chỉ soi chữ ký trên thẻ chứ không có danh sách thu hồi (Blacklist).
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): JWT Security for APIs (Bảo mật JWT trên API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
JWT hạn sống 1 năm (Không thu hồi được)JWT hạn sống 10 phút + Refresh TokenKý bằng Secret Key ngắn: '123456'
⚠️ JWT vô thời hạn: Không thể thu hồi khi người dùng bị hack hoặc đổi quyền hạn, trừ khi thay đổi Master Key của toàn bộ 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)
// THẢM HỌA: Tự parse token bằng chuỗi hoặc chấp nhận thuật toán 'none'
public Claims parseToken(String jwt) {
// Không chỉ định rõ thuật toán chữ ký -> Hacker đổi alg sang 'none' để vượt rào!
return Jwts.parser().parseClaimsJwt(jwt).getBody();
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Sử dụng NimbusJwtDecoder với Khóa bí mật cố định và thuật toán bắt buộc
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder = NimbusJwtDecoder.withSecretKey(secretKey)
.macAlgorithm(MacAlgorithm.HS256) // Khóa chặt thuật toán HMAC SHA-256
.build();
OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer("https://auth.company.com");
decoder.setJwtValidator(withIssuer);
return decoder;
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Ưu điểm lớn nhất của việc sử dụng thuật toán Bất đối xứng (RS256 / ES256) thay vì Đối xứng (HS256) cho hệ thống Microservices 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)
#07 Cross-Origin Resource Sharing — CORS (Cấu hình CORS)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
CORS giống như người lễ tân của tòa nhà văn phòng. Lễ tân có một danh sách các công ty đối tác được phép vào họp. Nếu người lễ tân dễ dãi dán biển: 'Ai đến từ bất kỳ đâu trên thế giới cũng được vào lục lọi hồ sơ (Access-Control-Allow-Origin: *) kèm quyền lấy chìa khóa phòng (Allow-Credentials: true)', thì kẻ gian từ trang web độc hại có thể mượn phiên đăng nhập của bạn để đọc sạch dữ liệu API của bạn.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Cross-Origin Resource Sharing — CORS (Cấu hình CORS)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Access-Control-Allow-Origin: *Origin: nullAllowlist Domain chính xác
⚠️ Cấu hình CORS lỏng lẻo (*): Mọi website lạ đều có thể dùng JavaScript gửi request và đọc nội dung phản hồi từ API nội bộ của bạn!
⚖️ 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ỰC KỲ NGUY HIỂM: Cho phép wildcard '*' đi kèm với Credentials (Cookie/Token)
@CrossOrigin(origins = "*", allowCredentials = "true")
@RestController
public class ProfileController { ... }
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN SPRING SECURITY: Chỉ định rõ danh sách domain được cấp phép
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://dashboard.company.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
config.setMaxAge(Duration.ofHours(1));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Cấu hình nào sau đây bị các trình duyệt hiện đại coi là BẤT HỢP LỆ và tự động chặn lại vì lý do an ninh?
📝 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)
#08 Mass Assignment (Gán dữ liệu hàng loạt)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn điền đơn xin việc gồm 2 mục: Tên và Số điện thoại. Nhưng bạn lén ghi thêm một mục vào góc dưới tờ giấy: 'Mức lương: 1 tỷ/tháng, Chức vụ: Tổng Giám Đốc'. Nếu phòng nhân sự lười biếng, quét toàn bộ tờ giấy vào máy vi tính và tự động lưu tất cả các trường bạn viết vào cơ sở dữ liệu mà không lọc lại, bạn bỗng nhiên trở thành Tổng Giám Đốc với lương 1 tỷ.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Mass Assignment (Gán dữ liệu hàng loạt)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
Thêm role: 'admin'Thêm balance: 999999Chỉ gửi các trường cho phép
⚠️ Mass Assignment: Client gửi thêm { "role": "admin" }, server gán thẳng toàn bộ req.body vào Model và cấp quyền Quản trị viên tối cao cho hacker!
⚖️ 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: Dùng thẳng Entity JPA làm RequestBody trong Controller
@PutMapping("/api/v1/profile")
public ResponseEntity> updateProfile(@RequestBody User userEntity) {
// Hacker gửi payload {"role": "ADMIN", "balance": 9999999}
// Lệnh save() sẽ cập nhật toàn bộ vào CSDL khiến hacker thành Admin!
userRepository.save(userEntity);
return ResponseEntity.ok("Updated");
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Dùng DTO riêng biệt, chỉ nhận các trường được phép sửa
public record UpdateProfileRequest(
@Size(min = 2, max = 50) String displayName,
@Size(max = 200) String bio
) {}
@PutMapping("/api/v1/profile")
public ResponseEntity> updateProfile(@Valid @RequestBody UpdateProfileRequest dto,
@AuthenticationPrincipal UserPrincipal user) {
User current = userRepository.findById(user.getId()).orElseThrow();
current.setDisplayName(dto.displayName());
current.setBio(dto.bio());
userRepository.save(current); // Thuộc tính role và balance hoàn toàn an toàn
return ResponseEntity.ok("Profile updated");
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Biện pháp lập trình nào đơn giản và hiệu quả nhất để ngăn chặn hoàn toàn lỗ hổng Mass Assignment?
📝 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)
#09 BOLA / IDOR on API (Lỗ hổng phân quyền đối tượng trên API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
API di động tải danh sách tin nhắn: GET /api/v1/chats/123. Hacker chỉ cần sửa số 123 thành 124, 125, 126... và đọc sạch nội dung chat riêng tư của hàng ngàn cặp đôi khác. Vì API giao tiếp bằng dữ liệu JSON không có giao diện, nhiều lập trình viên bỏ quên việc kiểm tra xem tài khoản đang gửi request có quyền sở hữu ID đó hay không.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): BOLA / IDOR on API (Lỗ hổng phân quyền đối tượng trên API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
GET /api/v1/orders/8842 (User khác)GET /api/v1/patients/001/recordsXác thực kèm tenant_id
⚠️ Lỗ hổng BOLA (OWASP API Top 10 #1): API trả về toàn bộ thông tin đơn hàng của khách hàng khác chỉ bằng cách thay đổi ID trên endpoint!
⚖️ 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: Tra cứu trực tiếp theo ID từ URL mà không kiểm tra chủ sở hữu
@GetMapping("/api/v1/invoices/{invoiceId}")
public ResponseEntity> getInvoice(@PathVariable Long invoiceId) {
// Nạn nhân sở hữu invoice 123, kẻ tấn công thay ID thành 124 là xem được hóa đơn người khác!
Invoice invoice = invoiceRepository.findById(invoiceId).orElseThrow();
return ResponseEntity.ok(invoice);
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Ràng buộc truy vấn với ID của tài khoản đang đăng nhập
@GetMapping("/api/v1/invoices/{invoiceId}")
public ResponseEntity> getInvoice(@PathVariable Long invoiceId,
@AuthenticationPrincipal UserPrincipal user) {
Invoice invoice = invoiceRepository.findByIdAndUserId(invoiceId, user.getId())
.orElseThrow(() -> new ResourceNotFoundException("Hóa đơn không tồn tại"));
return ResponseEntity.ok(invoice);
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Vì sao khi một người dùng cố tình truy cập vào tài nguyên của người khác, ta nên trả về mã lỗi 404 Not Found thay vì 403 Forbidden?
📝 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)
#10 Unrestricted Resource Consumption (Tiêu thụ tài nguyên không giới hạn)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Bạn mở một tiệm buffet miễn phí và bảo khách: 'Muốn lấy bao nhiêu thì lấy'. Một vị khách bê luôn 500 chiếc xe tải đến xúc sạch toàn bộ thức ăn trong bếp mang về nhà. Trên API, nếu endpoint tìm kiếm cho phép truyền tham số ?limit=10000000, database của bạn sẽ bị vét cạn bộ nhớ và sập ngay lập tức.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): Unrestricted Resource Consumption (Tiêu thụ tài nguyên không giới hạn)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
?limit=10000000Upload file zip dung lượng 10GBGraphQL truy vấn lồng 50 cấp
⚠️ Lỗi tiêu thụ tài nguyên: Truy vấn 1 triệu bản ghi làm nghẽn RAM máy chủ và gây treo Database trong 15 phút!
⚖️ 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: Trả về toàn bộ bản ghi hoặc cho phép Client tự chọn size tùy ý
@GetMapping("/api/v1/audit-logs")
public List getLogs(@RequestParam int size) {
// Kẻ gian truyền ?size=5000000 khiến JVM tràn bộ nhớ Heap OutOfMemoryError!
return auditRepository.findAll(PageRequest.of(0, size)).getContent();
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Bắt buộc phân trang và giới hạn trần kích thước trang
@GetMapping("/api/v1/audit-logs")
public ResponseEntity> getLogs(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
// Giới hạn cứng kích thước tối đa 50 phần tử / trang
int safeSize = Math.min(Math.max(size, 1), 50);
Pageable pageable = PageRequest.of(page, safeSize, Sort.by("createdAt").descending());
return ResponseEntity.ok(auditService.getLogs(pageable));
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Đối với các API sử dụng GraphQL, kỹ thuật nào là bắt buộc để ngăn chặn tấn công từ chối dịch vụ (DoS)?
📝 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)
#11 API Inventory Management & Shadow APIs (Quản lý kho API)
💡 BƯỚC 1: ẨN DỤ ĐỜI THƯỜNG (FEYNMAN ANALOGY)
Trong ngôi nhà cổ của bạn có một cánh cửa bí mật cũ kỹ từ 5 năm trước (API phiên bản v1 bị bỏ quên). Cánh cửa này không có khóa và không ai canh giữ. Dù cửa chính hiện đại (API v3) được bảo vệ bằng vân tay và camera tối tân, kẻ trộm chỉ cần đi vòng ra sau vườn và bước qua cánh cửa v1 cũ kỹ là vào thẳng phòng khách.
📊 HÌNH HỌA MINH HỌA (VISUAL FLOW): API Inventory Management & Shadow APIs (Quản lý kho API)
🧪 BƯỚC 2: PHÒNG THÍ NGHIỆM TƯƠNG TÁC (SANDBOX)
Mẫu thử nghiệm:
/api/v1/ (Chưa vá lỗi)/api/debug/Tài liệu OpenAPI / Swagger cập nhật
⚠️ Shadow API / Zombie API: Endpoint cũ /api/v1/ vẫn hoạt động ngầm, không có MFA và không có Rate Limit, bị hacker khai thác thâm nhập!
⚖️ 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: Để lộ API dùng để test/debug nội bộ trên môi trường Production
@RestController
public class DebugController {
@GetMapping("/api/v1/debug/dump-env")
public Map dumpEnv() {
return System.getenv(); // Lộ toàn bộ mật khẩu, token, biến môi trường!
}
}
✅ Secure Java Code (Chuẩn bảo mật)
// CHUẨN AN TOÀN: Giới hạn route theo Spring Profile và quản lý qua OpenAPI
@RestController
@Profile("dev | test") // Tuyệt đối không nạp Controller này trên môi trường Prod!
@Hidden // Ẩn khỏi tài liệu OpenAPI công khai nếu không cần thiết
public class DevDebugController {
@GetMapping("/api/internal/health-check")
public String healthCheck() {
return "DEV_OK";
}
}
🎯 BƯỚC 4: THỬ TÀI THẤU HIỂU (ACTIVE CHECK)
Thuật ngữ 'Zombie API' ám chỉ điều gì trong bảo mật 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)