Module 05 — Spring Security cơ bản: tổng quan
Học gì, vì sao học, học xong làm được gì. Filter chain architecture, SecurityFilterChain DSL Spring 6, UserDetailsService + BCrypt, JWT stateless auth, method security @PreAuthorize, CORS/CSRF — nền tảng security production-grade.
TL;DR: Module 05 thêm Security Layer cho TaskFlow: servlet filter chain intercept mọi HTTP request trước DispatcherServlet, Spring Security 6 lambda DSL config SecurityFilterChain bean thay thế WebSecurityConfigurerAdapter cũ, UserDetailsService query DB + BCrypt password hashing với cost factor production, JWT stateless authentication theo RFC 7519 với HS256/RS256 signing, method-level security qua @PreAuthorize AOP proxy, và CORS/CSRF config đúng chuẩn cho SPA/mobile client. Capstone TaskFlow v3 thêm JWT Bearer token, role-based endpoint protection, và BCrypt password storage — foundation security production-grade cho mọi Spring Boot REST API.
Vì sao module này tồn tại
Thứ Hai tuần trước, alert từ security team:
"Endpoint DELETE /api/admin/tasks không cần login — production đang expose."
Bạn mở log. Thấy 3 incident tuần rồi:
Incident 1 — 401 không hiểu filter nào reject. Frontend báo "tất cả request 401 sau khi deploy". Bạn mở filter chain log. Không biết BearerTokenAuthenticationFilter hay BasicAuthenticationFilter reject trước. Không hiểu thứ tự filter — debug 3 giờ mới tìm ra SecurityContextHolderFilter không load context vì session config sai.
Incident 2 — JWT signing key commit lên GitHub. Dev hard-code secret = "mySecret123" trong application.properties. GitHub scan alert sau 2 phút push. Tất cả token đã issue phải invalidate — rotate key, force logout toàn bộ user, 4 giờ recovery.
Incident 3 — BCrypt cost factor 4 khiến attacker brute force. Security audit phát hiện app dùng new BCryptPasswordEncoder(4) (minimum strength). Với GPU hiện đại, cost 4 crack được trong vài giây. Production cần ít nhất cost 12.
Incident 4 — CORS disable toàn bộ để pass test. Dev thêm cors.allowedOrigins("*") để fix local dev. Code vào production. Access-Control-Allow-Origin: * kết hợp Access-Control-Allow-Credentials: true vi phạm spec CORS — browser từ chối. Frontend production hỏng. Rollback khẩn.
Incident 5 — CSRF disabled + session cookie = token theft vector. App dùng session-based auth cho admin panel nhưng csrf.disable() vì "REST API không cần". Attacker submit form cross-site, browser tự đính cookie session → admin action bị thực thi mà không có CSRF token bảo vệ.
Module 05 xây đúng security layer từ ngày đầu — không phải retrofit sau incident.
Sau module này bạn sẽ
- Explain cơ chế filter chain của Spring Security: DelegatingFilterProxy → FilterChainProxy → SecurityFilterChain và vai trò của từng layer
- Compare session-based stateful auth vs JWT stateless: trade-off scalability, revocation, CSRF exposure
- Implement BCrypt password hashing với cost factor đúng chuẩn production và UserDetailsService query DB
- Diagnose 401 Unauthorized vs 403 Forbidden: AuthenticationException vs AccessDeniedException trong ExceptionTranslationFilter
- Choose CORS strategy phù hợp: allowedOrigins, allowCredentials, preflight caching cho từng use case SPA/mobile/microservice
- Design JWT authentication flow: issue token, validate signature, extract claims, refresh token rotation, revocation strategy
Lộ trình module
Bài 01 đặt nền kiến trúc: filter chain là cơ chế Spring Security intercept HTTP request trước khi đến DispatcherServlet. Tomcat không thấy 15+ filter riêng lẻ — chỉ thấy DelegatingFilterProxy duy nhất, bridge sang FilterChainProxy Spring bean, rồi vào SecurityFilterChain config của bạn. Authentication flow: filter extract credential → gọi AuthenticationManager → delegate đến AuthenticationProvider (per token type) → UserDetailsService query DB → SecurityContextHolder ThreadLocal lưu kết quả. GrantedAuthority với 2 convention: ROLE_X coarse-grained vs resource:action fine-grained. Exception mapping: AuthenticationException → 401, AccessDeniedException → 403.
Bài 02 bóc SecurityFilterChain DSL Spring 6 lambda style: HttpSecurity API, requestMatchers() thay thế antMatchers() cũ, thứ tự rule quan trọng (catch-all .anyRequest() phải cuối), multiple SecurityFilterChain với @Order + securityMatcher(), Customizer.withDefaults(), exceptionHandling custom entry point, sessionManagement stateless cho JWT. Spring Security 5 WebSecurityConfigurerAdapter bị remove hoàn toàn — bài này cover migration pattern và lý do thiết kế mới.
Bài 03 bóc form login + HTTP Basic + UserDetailsService + password encoding: flow UsernamePasswordAuthenticationFilter → DaoAuthenticationProvider → UserDetailsService.loadUserByUsername() → BCryptPasswordEncoder.matches(). BCrypt là adaptive hash function với work factor điều chỉnh được — cost 12 minimum production. Bài bóc salt mechanism, rainbow table attack, Argon2id alternative, và password upgrade strategy (re-hash khi user login).
Bài 04 là bài cốt lõi cho REST API 2026: JWT stateless authentication theo RFC 7519. Structure header.payload.signature base64url, standard claims (iss, sub, exp, jti). HS256 symmetric vs RS256 asymmetric — blast radius và use case. Spring Security oauth2ResourceServer.jwt(...) config app làm Resource Server, JwtDecoder validate signature + expiry, JwtAuthenticationConverter extract roles từ custom claim. Refresh token rotation pattern, token revocation với jti blocklist, và signing key rotation zero-downtime.
Bài 05 bóc method security — tầng bảo vệ thứ 2 sau URL rule: @EnableMethodSecurity, @PreAuthorize / @PostAuthorize, SpEL expression evaluate runtime (authentication.name, #userId, returnObject). Cơ chế AOP proxy: Spring wrap bean trong CGLIB proxy, intercept method call, invoke AuthorizationManager trước khi delegate. Self-call bypass: this.method() không qua proxy → annotation không có hiệu lực — pitfall classic. Custom PermissionEvaluator cho ownership check.
Bài 06 đóng vòng browser security: CORS (Cross-Origin Resource Sharing) — same-origin policy RFC 6454, tại sao preflight OPTIONS tồn tại, allowedOrigins vs allowedOriginPatterns, forbidden combination allowCredentials(true) + wildcard *. CSRF — tấn công lợi dụng cookie auto-attach, Synchronizer Token Pattern, Double-Submit Cookie pattern, và SameSite cookie attribute (Strict/Lax/None) là modern defense. Khi nào disable CSRF (stateless JWT API) và khi nào phải enable (session + browser).
Bài 07 mini-challenge TaskFlow v3: migrate TaskFlow v2 thêm user authentication, JWT issue endpoint (POST /api/auth/login), BearerTokenAuthenticationFilter validate token, @PreAuthorize("hasRole('ADMIN')") bảo vệ write endpoint, anonymous read cho public project list, BCrypt password storage, và UserDetailsService query DB.
Bài 08 tổng kết: cheat sheet 20 annotation/config pattern, glossary 25+ thuật ngữ, pitfall tổng hợp 10 lỗi thường gặp, self-assessment 6 outcomes.
Yêu cầu trước khi bắt đầu
- Spring Core (Module 01): AOP proxy mechanism — bài 05 method security dựa hoàn toàn vào AOP proxy. Module 01 bài 04 là prerequisite cứng.
- Spring Boot Foundations (Module 02): autoconfig,
@ConditionalOnMissingBean, externalized config. Boot tự wireSecurityAutoConfiguration,UserDetailsServiceAutoConfigurationkhi cóspring-boot-starter-security. - REST API Spring MVC (Module 03):
@RestController,HttpSecurity, request lifecycle. Bài 02 và 04 assume bạn biết request đi qua filter chain đếnDispatcherServlet. - Module 04 (JPA): không bắt buộc cho bài 01-02, nhưng cần cho bài 03 (
UserDetailsServicequery DB) và bài 07 (TaskFlow v3 capstone).
Time budget
| Bài | Chủ đề | Phút |
|---|---|---|
| 00 | Tổng quan (đang đọc) | 10 |
| 01 | Filter chain architecture — DelegatingFilterProxy, AuthenticationManager, SecurityContext | 26 |
| 02 | SecurityFilterChain DSL Spring 6 — requestMatchers, lambda DSL, multiple chain | 24 |
| 03 | Form login & Basic auth — UserDetailsService, BCrypt, password lifecycle | 24 |
| 04 | JWT authentication — RFC 7519, HS256 vs RS256, oauth2ResourceServer, refresh token | 26 |
| 05 | Method security — @PreAuthorize, AOP proxy, SpEL, self-call bypass | 28 |
| 06 | CORS & CSRF — SOP, preflight, Synchronizer Token, SameSite cookie | 24 |
| 07 | Mini-challenge: TaskFlow v3 — JWT + role-based API (capstone) | 45 |
| 08 | Tổng kết & cheat sheet | 17 |
| Tổng | ~3.7h đọc + 0.5h lab + buffer |
Khuyến nghị: chia làm 3 ngồi:
- Ngồi 1 (~1.5h): bài 00 → 02 (orient + filter chain architecture + DSL config — nền tảng trước khi viết dòng code nào).
- Ngồi 2 (~1.5h): bài 03 → 05 (form login + JWT + method security — 3 bài cốt lõi production, JWT và self-call trap nằm ở đây).
- Ngồi 3 (~1.5h): bài 06 (CORS/CSRF) + bài 07 (lab). Lab cần ngồi liên tục — bật
logging.level.org.springframework.security=DEBUGvà trace từng filter trong chain.
Cách học module này hiệu quả
-
Bật
logging.level.org.springframework.security=DEBUGngay từ bài 01. Quan sát từng filter trong chain log ra khi request đến — đây là cách duy nhất để biết filter nào reject, ở đâu trong chain, vì lý do gì. Không có log debug, security incident rất khó trace. -
Không bao giờ commit JWT signing key. Dùng environment variable hoặc secret manager. Test với key random generate lúc startup (
Keys.secretKeyFor(SignatureAlgorithm.HS256)), không hardcode. Rule cứng: nếu signing key lên Git, coi như leaked — phải rotate và invalidate tất cả token đã issue. -
BCrypt cost 12 minimum production.
new BCryptPasswordEncoder(4)đủ cho test nhanh, nhưng không bao giờ dùng production. Cost 12 ~ 250ms per hash — đủ chậm để brute force không khả thi, đủ nhanh để UX login không bị ảnh hưởng. -
CORS preflight test với
curl -X OPTIONS, không chỉ browser. Browser ẩn preflight — bạn không thấy OPTIONS request trong tab Network theo mặc định. Dùngcurl -v -X OPTIONS -H "Origin: https://yourapp.com" -H "Access-Control-Request-Method: POST" http://localhost:8080/api/projectsđể verify response header trước khi deploy. -
Bài 05 (method security) — luôn trace AOP proxy trong đầu. Mỗi khi thấy
@PreAuthorize, hỏi: "method này được gọi từ đâu? Từ bean khác qua proxy hay self-callthis.method()?" Self-call = annotation không có hiệu lực = security hole. 80% bug@PreAuthorizekhông chạy là self-invocation.
Module 05 là module security — mọi pitfall đều có thể là security vulnerability, không chỉ là bug. Kiên nhẫn với bài 04 (JWT signing/validation) và bài 05 (AOP proxy) — đầu tư thời gian ở đây tránh được incident production.
Bài này có giúp bạn hiểu bản chất không?