Module 05 — Tổng kết & cheat sheet
Recap, cheat sheet 1 trang, glossary Spring Security, pitfall tổng hợp, self-assessment outcomes. 1 trang để bookmark khi debug 401/403 bất ngờ, JWT validation fail, CORS preflight bị chặn, hay @PreAuthorize không chạy.
TL;DR: Module 05 đã xây đủ Security Layer production-grade: filter chain 3 tầng intercept request trước DispatcherServlet, Spring Security 6 lambda DSL config SecurityFilterChain, UserDetailsService + BCrypt password hashing với cost 12, JWT stateless auth theo RFC 7519 với HS256/RS256 signing, method security @PreAuthorize AOP proxy + SpEL, và CORS/CSRF config đúng chuẩn cho SPA client. Capstone TaskFlow v3 thêm JWT Bearer token + role-based endpoint protection. Đây là trang bookmark — quay lại khi debug 401 không hiểu filter nào reject, @PreAuthorize annotation không có hiệu lực, hay CORS preflight bị browser chặn.
Đã đi qua những gì
Hành trình bắt đầu từ câu hỏi: vì sao thêm spring-boot-starter-security là mọi endpoint đột ngột trả 401? Bài 01 trả lời bằng kiến trúc filter chain: Tomcat không thấy 15+ filter riêng lẻ — chỉ thấy DelegatingFilterProxy duy nhất, lazy-fetch FilterChainProxy bean khi request đầu tiên đến, rồi route vào SecurityFilterChain config của bạn. Authentication flow: filter extract credential → AuthenticationManager → AuthenticationProvider per token type → UserDetailsService query DB → SecurityContextHolder ThreadLocal per-thread. ExceptionTranslationFilter map: AuthenticationException → 401, AccessDeniedException → 403 — phân biệt này quan trọng khi debug.
Bài 02 bóc DSL config Spring Security 6: WebSecurityConfigurerAdapter bị remove hoàn toàn, thay bằng @Bean SecurityFilterChain. requestMatchers() thay antMatchers() — thông minh hơn, nhận diện AntPath vs MVC pattern. Thứ tự rule quyết định: .anyRequest() catch-all phải cuối, không thì nuốt mọi request trước rule cụ thể. Multiple chain với @Order + securityMatcher() cho nhiều nhóm endpoint khác security policy. Lambda DSL bắt buộc — chain .and() deprecated.
Bài 03 bóc traditional auth: DaoAuthenticationProvider gọi UserDetailsService.loadUserByUsername() query DB, sau đó BCryptPasswordEncoder.matches() verify hash. BCrypt là adaptive function: mỗi hash chứa salt ngẫu nhiên + work factor — tăng cost làm brute force tốn kém hơn. Cost 12 minimum production (~250ms per hash). HTTP Basic gửi base64(user:password) — không phải encryption, chỉ encoding — bắt buộc HTTPS. Password upgrade strategy: re-hash khi user login thành công với DelegatingPasswordEncoder.
Bài 04 là trung tâm của REST API auth 2026: JWT (RFC 7519) format header.payload.signature base64url — payload chứa standard claims (iss, sub, aud, exp, jti), không encrypt, anyone decode được. HS256 symmetric (1 secret cho cả sign + verify) vs RS256 asymmetric (private sign, public verify) — blast radius khác nhau: leak HS256 secret = breach toàn hệ thống, leak RS256 public key = vô hại. Spring Security oauth2ResourceServer.jwt() setup BearerTokenAuthenticationFilter + JwtDecoder tự động. JwtAuthenticationConverter extract custom claim thành GrantedAuthority. Refresh token rotation và jti blocklist cho revocation.
Bài 05 bóc method security tầng thứ 2: @EnableMethodSecurity bật @PreAuthorize / @PostAuthorize. Cơ chế AOP proxy: Spring wrap bean trong CGLIB proxy, intercept method call, invoke AuthorizationManager kiểm tra SpEL expression trước khi delegate. Self-call this.method() bypass proxy — annotation không chạy — pitfall number 1. SpEL cho phép rule động: authentication.name, #methodArg, returnObject. Custom PermissionEvaluator cho ownership check @PreAuthorize("hasPermission(#id, 'project', 'write')"). Defense in depth: URL rule fail-fast + method security backstop.
Bài 06 đóng vòng browser security: Same-Origin Policy (RFC 6454) định nghĩa origin là tuple (scheme, host, port). CORS là opt-in server cho cross-origin đọc response — preflight OPTIONS request gửi trước mọi non-simple request. Forbidden combination: allowCredentials(true) + allowedOrigins("*") vi phạm spec, browser từ chối. CSRF attack lợi dụng cookie auto-attach cross-site — chống bằng Synchronizer Token Pattern, Double-Submit Cookie, hoặc SameSite=Strict. Stateless JWT API: không cần CSRF token vì Bearer header không tự đính kèm bởi browser.
Bài 07 mini-challenge TaskFlow v3: User entity + BCrypt password, UserDetailsService query DB, POST /api/auth/login issue JWT, BearerTokenAuthenticationFilter validate token, @PreAuthorize("hasRole('ADMIN')") bảo vệ write endpoint, anonymous read public endpoint, và CORS config cho SPA frontend.
🗺️ Cheat sheet
| Annotation/Config | Khi nào dùng | Pitfall thường gặp |
|---|---|---|
@EnableWebSecurity | Trên @Configuration class Security | Thiếu annotation → Boot không kích hoạt custom filter chain |
SecurityFilterChain bean | Config filter chain Spring Security 6 | WebSecurityConfigurerAdapter đã removed — không extend được nữa |
requestMatchers("/path/**") | Match URL cho authorization rule | Thứ tự quan trọng: cụ thể trước, .anyRequest() cuối — sai thứ tự = rule bị bỏ qua |
.sessionManagement(s -> s.STATELESS) | JWT REST API — không tạo session | Quên set STATELESS → Spring tạo session + JSESSIONID cookie dù dùng JWT |
UserDetailsService | Load user từ DB cho form login / Basic | UsernameNotFoundException thay vì return null — phải throw |
BCryptPasswordEncoder(12) | Hash password production | Cost 4 (default dev) quá yếu — brute force GPU vài giây. Cost 12 minimum |
passwordEncoder.matches(raw, hash) | Verify password login | So sánh == với hash string là sai (timing attack + salt mismatch) |
oauth2ResourceServer.jwt(...) | App validate JWT Bearer token | Quên config → BearerTokenAuthenticationFilter không được register |
JwtDecoder bean | Customize JWT validation (issuer, audience) | Default decoder không check aud claim — phải explicit nếu cần |
JwtAuthenticationConverter | Extract roles từ custom JWT claim | Spring mặc định đọc scope claim prefix SCOPE_ — phải override cho roles claim custom |
HS256 (HMAC-SHA256) | JWT signing đơn giản, single-service | Secret phải share cho cả sign + verify — microservices dùng RS256 thay |
RS256 (RSA-SHA256) | JWT signing microservices, IdP tách biệt | Key RSA 2048-bit minimum (NIST khuyến nghị 3072-bit đến 2030+) |
@EnableMethodSecurity | Bật @PreAuthorize, @PostAuthorize | Thiếu annotation → @PreAuthorize im lặng không chạy, không báo lỗi |
@PreAuthorize("hasRole('X')") | Check role trước khi method chạy | Self-call this.method() bypass AOP proxy → annotation vô hiệu |
@PostAuthorize("returnObject.owner == authentication.name") | Check sau khi method return | Load toàn bộ object trước khi check — chậm với large dataset |
hasRole("ADMIN") | Shortcut kiểm tra role | Khác hasAuthority("ADMIN"): hasRole auto-prefix ROLE_ → check ROLE_ADMIN |
@CorsConfiguration / CorsFilter | Cho phép cross-origin frontend gọi API | allowedOrigins("*") + allowCredentials(true) vi phạm spec — browser từ chối |
csrf.disable() | REST API stateless JWT | Phải đảm bảo thực sự stateless — disable CSRF với session-based auth là security hole |
SameSite=Strict cookie | Defense CSRF cho browser session app | Strict block cross-site request hoàn toàn — có thể break OAuth2 redirect flow |
SecurityContextHolder.getContext() | Lấy auth trong non-controller code | Cross-thread (Async, executor) ThreadLocal không propagate — NPE |
@AuthenticationPrincipal | Inject user trong controller method | Type mismatch nếu custom UserDetails implementation không match param type |
📖 Glossary module
| Thuật ngữ | Định nghĩa 1 câu | Nguồn |
|---|---|---|
| Authentication (authn) | Chứng minh danh tính: "tôi là ai" — username/password, JWT token, certificate | Bài 01 |
| Authorization (authz) | Kiểm tra quyền: "user đã xác thực có được làm hành động X không" — role, permission, custom rule | Bài 01 |
| Principal | Đối tượng đại diện danh tính sau khi xác thực — thường là UserDetails chứa username + authorities | Bài 01 |
| Credentials | Bằng chứng xác thực (password, token) — Spring Security xoá khỏi memory sau auth thành công | Bài 01 |
| GrantedAuthority | Quyền được cấp cho user — 2 convention: ROLE_X coarse-grained hoặc resource:action fine-grained | Bài 01 |
| RBAC (Role-Based Access Control) | Phân quyền theo role: user có role gì → được làm gì — phù hợp app có ít hơn 10 role | Bài 01 |
| ABAC (Attribute-Based Access Control) | Phân quyền theo thuộc tính context (thời gian, IP, tenant, ownership) — mạnh hơn RBAC nhưng phức tạp hơn | Bài 05 |
| DelegatingFilterProxy | Bridge từ Tomcat sang Spring bean — lazy-fetch FilterChainProxy khi request đầu đến, giải quyết timing issue Tomcat vs Spring context | Bài 01 |
| FilterChainProxy | Spring bean match URL pattern → chọn đúng SecurityFilterChain để delegate | Bài 01 |
| SecurityFilterChain | Spring bean (config của bạn) chứa list filter cho specific URL pattern | Bài 01, 02 |
| AuthenticationManager | Interface 1 method authenticate() — entry point cho toàn bộ authentication flow | Bài 01 |
| AuthenticationProvider | Pluggable strategy: mỗi provider handle 1 token type (DaoAuthenticationProvider cho form, JwtAuthenticationProvider cho Bearer) | Bài 01 |
| UserDetailsService | Interface load UserDetails từ DB theo username — implement để integrate với user store của app | Bài 01, 03 |
| SecurityContext | Container chứa Authentication object cho request hiện tại | Bài 01 |
| ThreadLocal | Java mechanism store giá trị per-thread — SecurityContextHolder dùng ThreadLocal để cô lập context giữa các request đồng thời | Bài 01 |
| BCrypt | Adaptive password hashing function với salt ngẫu nhiên + work factor điều chỉnh được — industry standard cho password storage | Bài 03 |
| Salt | Chuỗi random thêm vào password trước hash — mỗi user 1 salt khác nhau, chống rainbow table attack | Bài 03 |
| JWT (JSON Web Token, RFC 7519) | Token tự đóng gói format header.payload.signature base64url — mang claims self-contained, server không cần nhớ state | Bài 04 |
| JWS (JSON Web Signature, RFC 7515) | Chuẩn ký JWT — Compact Serialization là format 3 phần phân tách dấu chấm dùng cho JWT phổ biến | Bài 04 |
| JWK (JSON Web Key, RFC 7517) | Chuẩn biểu diễn cryptographic key dạng JSON | Bài 04 |
| JWKS (JWK Set) | Endpoint công khai (/.well-known/jwks.json) issuer expose public key — Resource Server fetch để validate token, auto rotate | Bài 04 |
| Claims | Cặp key-value trong JWT payload — standard: iss, sub, aud, exp, iat, jti, nbf | Bài 04 |
| Bearer scheme (RFC 6750) | Client gửi token qua Authorization: Bearer <token> — "ai cầm token thì có quyền", bắt buộc HTTPS | Bài 04 |
| OAuth2 (RFC 6749) | Framework uỷ quyền — cho service A access resource user trên service B không share password. 4 vai: Resource Owner, Client, Authorization Server, Resource Server | Bài 04 |
| Resource Server | App nhận request có Bearer token, validate signature + expiry, extract claims — cấu hình qua oauth2ResourceServer.jwt() trong Spring | Bài 04 |
| AOP (Aspect-Oriented Programming) | Pattern thêm behavior (security check, log) vào method không sửa code method — Spring implement qua CGLIB proxy | Bài 05 |
| SpEL (Spring Expression Language) | DSL evaluate expression runtime — dùng trong @PreAuthorize để access authentication, method args #param, returnObject | Bài 05 |
| Self-call bypass | Method gọi this.method() trong cùng class → bypass AOP proxy → @PreAuthorize/@Transactional không có hiệu lực | Bài 05 |
| Same-Origin Policy (RFC 6454) | Browser policy: JavaScript chỉ đọc response cùng origin (scheme, host, port) — chặn ăn cắp dữ liệu cross-site | Bài 06 |
| CORS | Cơ chế server opt-in cho cross-origin đọc response qua Access-Control-Allow-Origin header — không phải attack, là policy | Bài 06 |
| CSRF (Cross-Site Request Forgery) | Attack lợi dụng cookie auto-attach cross-site: site độc submit request đến site nạn nhân, browser tự kèm session cookie | Bài 06 |
| SameSite cookie | RFC 6265bis attribute (Strict/Lax/None) chặn cookie gửi cross-site — modern defense CSRF cho session-based auth | Bài 06 |
⚠️ Pitfall tổng hợp
1. Filter chain order — anyRequest() đặt sai chỗ
// SAI: anyRequest authenticated truoc rule cu the — rule admin bi bo qua
http.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated() // catch-all truoc!
.requestMatchers("/api/admin/**").hasRole("ADMIN") // NEVER reached
);
// DUNG: cu the truoc, catch-all cuoi
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated() // catch-all cuoi
);
Lý do: Spring duyệt rule từ trên xuống, rule đầu tiên match được áp dụng. anyRequest() match mọi URL — đặt trước rule cụ thể sẽ nuốt tất cả request trước khi đến rule admin.
2. JWT signing key commit lên source control
# SAI: hardcode trong application.properties (bi commit)
app.jwt.secret=mySecretKey123
# DUNG: environment variable
app.jwt.secret=${JWT_SECRET}
# Generate random key: openssl rand -base64 32
Lý do: Signing key trong Git = breach. Bất kỳ ai clone repo đều có thể issue JWT hợp lệ giả mạo bất kỳ user. GitHub secret scanning detect trong vài phút — nhưng token đã issue vẫn valid đến exp. Phải rotate key + force logout toàn bộ user.
3. BCrypt cost factor quá thấp
// SAI: cost 4 (minimum valid) — crack nhanh voi GPU
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(4);
}
// DUNG: cost 12 minimum production
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
Lý do: BCrypt cost 4 hash trong ~1ms. GPU hiện đại crack hàng tỷ hash/giây với cost thấp. Cost 12 ~ 250ms per hash — đủ chậm để brute force không khả thi, đủ nhanh để UX login không bị ảnh hưởng (OWASP khuyến nghị hash phải mất ít nhất 100ms).
4. @PreAuthorize self-call bypass AOP proxy
// SAI: self-call bypass proxy, annotation vo hieu
@Service
public class ProjectService {
public void importAll(List<Request> reqs) {
reqs.forEach(req -> this.createProject(req)); // self-call!
}
@PreAuthorize("hasRole('ADMIN')")
public Project createProject(Request req) {
return projectRepo.save(new Project(req.getName()));
}
}
// createProject duoc goi truc tiep, khong qua proxy
// bat ky user nao cung tao duoc project
// DUNG option 1: inject self proxy
@Service
public class ProjectService {
@Autowired
private ProjectService self;
public void importAll(List<Request> reqs) {
reqs.forEach(req -> self.createProject(req)); // qua proxy
}
@PreAuthorize("hasRole('ADMIN')")
public Project createProject(Request req) { ... }
}
// DUNG option 2: tach class
@Service
public class ProjectImportService {
@Autowired
private ProjectService projectService; // inject other bean
public void importAll(List<Request> reqs) {
reqs.forEach(req -> projectService.createProject(req));
}
}
Lý do: AOP proxy wrap object từ bên ngoài. Call this.method() đi thẳng vào object thật, không qua proxy — @PreAuthorize không intercept. Security hole không có error message — annotation im lặng bị bỏ qua.
5. CORS allowCredentials(true) + wildcard origin
// SAI: vi pham spec — browser tu choi
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("*")); // wildcard
config.setAllowCredentials(true); // credentials
// Fetch spec: forbidden combination!
...
}
// DUNG: explicit origin khi can credentials
config.setAllowedOrigins(List.of("https://olhub.org"));
config.setAllowCredentials(true);
Lý do: Fetch spec (kế thừa RFC 6454) nghiêm cấm Access-Control-Allow-Origin: * kết hợp với Access-Control-Allow-Credentials: true. Nếu wildcard được phép với credentials, site độc hại đọc được response có chứa sensitive data của user. Browser enforce: nếu server gửi cả 2, browser reject response với CORS error dù server đã respond.
6. CSRF disable với session-based auth
// SAI: session auth + CSRF disabled = attack vector
http
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.csrf(csrf -> csrf.disable()); // NGUY HIEM!
// DUNG cho session-based app: enable CSRF
http
.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
...;
// DUNG cho JWT stateless API: disable la OK
http
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable()); // OK vi Bearer header khong auto-attach
Lý do: CSRF attack phụ thuộc vào cookie auto-attach. JWT Bearer token trong Authorization header không tự đính kèm bởi browser khi cross-site — không cần CSRF token. Nhưng session cookie (JSESSIONID) tự đính kèm — disable CSRF với session auth = CSRF attack vector mở.
7. SecurityContextHolder cross thread pool
// SAI: ThreadLocal khong propagate vao Async thread
@Async
public CompletableFuture<Void> processAsync() {
return CompletableFuture.runAsync(() -> {
// SecurityContextHolder.getContext() = null tren async thread!
String user = SecurityContextHolder.getContext()
.getAuthentication().getName(); // NullPointerException
});
}
// DUNG: capture user truoc, pass vao lambda
@Async
public CompletableFuture<Void> processAsync(
@AuthenticationPrincipal UserDetails currentUser
) {
String username = currentUser.getUsername(); // capture tren main thread
return CompletableFuture.runAsync(() -> {
processForUser(username); // pass by param
});
}
Lý do: SecurityContextHolder mặc định dùng ThreadLocalSecurityContextHolderStrategy — store per-thread. Khi switch sang thread pool (executor, @Async, CompletableFuture), ThreadLocal không được propagate — context null, getAuthentication() trả null → NPE.
8. Password logged hoặc serialized
// SAI: log Authentication object co the include credentials
log.debug("Auth: {}", authentication); // credentials la trong toString()!
// SAI: serialize UserDetails ra JSON response
@GetMapping("/me")
public UserDetails getCurrentUser(@AuthenticationPrincipal UserDetails user) {
return user; // password hash trong response!
}
// DUNG: log chi username, return DTO
log.debug("Auth: user={}", authentication.getName());
@GetMapping("/me")
public UserDto getCurrentUser(@AuthenticationPrincipal UserDetails user) {
return new UserDto(user.getUsername(), user.getAuthorities());
}
Lý do: UserDetails.getPassword() trả BCrypt hash — dù đã hash nhưng hash trong log = attacker có thể crack offline. Serialize UserDetails thành JSON response expose hash cho client — không bao giờ trả password field (kể cả hashed) ra API.
9. hasRole vs hasAuthority nhầm lẫn
// User co authority "ADMIN" (khong co prefix "ROLE_")
Authentication auth = ...;
auth.getAuthorities(); // [SimpleGrantedAuthority("ADMIN")]
// SAI: hasRole auto-prefix ROLE_
@PreAuthorize("hasRole('ADMIN')") // check "ROLE_ADMIN" — FAIL
// DUNG: hasAuthority check chinh xac
@PreAuthorize("hasAuthority('ADMIN')") // check "ADMIN" — PASS
// Nguoc lai: User co "ROLE_ADMIN"
@PreAuthorize("hasRole('ADMIN')") // check "ROLE_ADMIN" — PASS
@PreAuthorize("hasAuthority('ADMIN')") // check "ADMIN" — FAIL
Lý do: hasRole("X") tự động prefix ROLE_ → kiểm tra ROLE_X. hasAuthority("X") kiểm tra exact string X. Nhầm convention = security check luôn fail (hoặc luôn pass nếu logic ngược). Dùng 1 convention nhất quán toàn app.
10. JWT alg: none attack
// SAI: accept token voi algorithm "none" (no signature)
// Attacker craft JWT: {"alg":"none"} + any payload + empty signature
// Neu JwtDecoder khong enforce algorithm, token duoc accept!
// DUNG: explicit whitelist algorithm
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder = NimbusJwtDecoder
.withSecretKey(secretKey)
.macAlgorithm(MacAlgorithm.HS256) // enforce HS256 only
.build();
// Them validator cho issuer, audience
decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer("https://olhub.org"));
return decoder;
}
Lý do: JWT spec (RFC 7519) cho phép alg: none — no signature, anyone craft được. Attacker tạo token với {"alg":"none"} và any payload. Một số thư viện cũ accept none nếu không whitelist algorithm. Spring Security NimbusJwtDecoder enforce algorithm mặc định — nhưng phải explicit để tránh downgrade attack.
11. @PostFilter với large dataset
// SAI: load all, filter in-memory — chậm tuyến tính với N
@PostFilter("filterObject.ownerId == authentication.principal.id")
public List<Project> findAllProjects() {
return projectRepo.findAll(); // load 100k row, filter 99.9% là throw away
}
// DUNG: filter at DB
public List<Project> findAllProjects(@AuthenticationPrincipal UserDetails user) {
return projectRepo.findByOwnerId(user.getId()); // 1 SQL với WHERE
}
Lý do: @PostFilter nhận kết quả method, lọc từng phần tử bằng SpEL. Method vẫn load toàn bộ data từ DB — N phần tử vào, M phần tử ra, N-M phần tử bị throw away. Với table lớn = DB query tốn kém + memory overhead. Filter at DB query luôn tốt hơn.
12. SecurityFilterChain không set stateless session + dùng JWT
// SAI: default session created -> JSESSIONID cookie + SecurityContext persist
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
return http.build();
}
// DUNG: stateless session mandatory cho JWT REST API
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
return http.build();
}
Lý do: Không set STATELESS → Spring tạo HTTP session, lưu SecurityContext vào session giữa request. Với JWT, mỗi request tự authenticate qua token — không cần session. Session tồn tại thừa: memory leak ở server, JSESSIONID cookie gửi kèm response (CSRF exposure nếu không cấu hình đúng).
✅ Self-assessment outcomes
Tick được hết các ô sau, bạn sẵn sàng Module 06 (Testing strategy). Nếu chưa: re-read bài tương ứng trước khi tiếp tục.
- Explain cơ chế filter chain của Spring Security: DelegatingFilterProxy → FilterChainProxy → SecurityFilterChain và vai trò của từng layer.
- Nếu chưa: re-read bài 01 section 3-5 ("FilterChainProxy", "Authentication flow"). Vẽ sơ đồ 3 tầng từ trí nhớ không nhìn tài liệu. Giải thích tại sao cần
DelegatingFilterProxythay vì register trực tiếp 15 filter vào Tomcat.
- Nếu chưa: re-read bài 01 section 3-5 ("FilterChainProxy", "Authentication flow"). Vẽ sơ đồ 3 tầng từ trí nhớ không nhìn tài liệu. Giải thích tại sao cần
- Compare session-based stateful auth vs JWT stateless: trade-off scalability, revocation, CSRF exposure.
- Nếu chưa: re-read bài 03 section "Session vs JWT" và bài 04 section "Trade-off". Viết bảng so sánh 5 dimension: scalability, revocation, CSRF risk, mobile-friendly, server-side memory. Giải thích vì sao REST API 2026 chọn JWT nhưng vẫn cần CSRF config cho browser session app.
- Implement BCrypt password hashing với cost factor đúng chuẩn production và UserDetailsService query DB.
- Nếu chưa: re-read bài 03 section 2-4 ("UserDetailsService", "BCrypt", "password lifecycle"). Implement từ scratch:
UserDetailsServicequery DB,BCryptPasswordEncoder(12), register endpoint, verify login với wrong password → 401. ThêmDelegatingPasswordEncodercho upgrade strategy.
- Nếu chưa: re-read bài 03 section 2-4 ("UserDetailsService", "BCrypt", "password lifecycle"). Implement từ scratch:
- Diagnose 401 Unauthorized vs 403 Forbidden: AuthenticationException vs AccessDeniedException trong ExceptionTranslationFilter.
- Nếu chưa: re-read bài 01 section 8 ("Exception flow") và bài 02 section "exceptionHandling". Tạo 2 test case: 1 request không có token (expect 401) và 1 request có token nhưng thiếu role (expect 403). Bật
logging.level.org.springframework.security=DEBUG, trace log xem filter nào throw exception nào.
- Nếu chưa: re-read bài 01 section 8 ("Exception flow") và bài 02 section "exceptionHandling". Tạo 2 test case: 1 request không có token (expect 401) và 1 request có token nhưng thiếu role (expect 403). Bật
- Choose CORS strategy phù hợp: allowedOrigins, allowCredentials, preflight caching cho từng use case SPA/mobile/microservice.
- Nếu chưa: re-read bài 06 section 1-4 ("Same-Origin Policy", "Preflight", "Spring CORS config"). Test CORS với
curl -v -X OPTIONS -H "Origin: https://yourapp.com" -H "Access-Control-Request-Method: POST" http://localhost:8080/api/projects. Verify response headerAccess-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Max-Age.
- Nếu chưa: re-read bài 06 section 1-4 ("Same-Origin Policy", "Preflight", "Spring CORS config"). Test CORS với
- Design JWT authentication flow: issue token, validate signature, extract claims, refresh token rotation, revocation strategy.
- Nếu chưa: re-read bài 04 section 3-7 ("JWT issue", "oauth2ResourceServer", "refresh token"). Implement đầy đủ:
POST /api/auth/loginissue JWT (HS256, exp 15 min),GET /api/mevalidate token, refresh endpoint rotate access + refresh token. Test với expired token (expect 401), tampered token (expect 401).
- Nếu chưa: re-read bài 04 section 3-7 ("JWT issue", "oauth2ResourceServer", "refresh token"). Implement đầy đủ:
🚀 What's next — Module 06: Testing strategy
Module 05 đã có security layer đầy đủ: filter chain, JWT auth, method security, CORS/CSRF. Module 06 thêm testing strategy: unit test với @WebMvcTest + @WithMockUser, integration test với @SpringBootTest + Testcontainers, test security config (ensure endpoint trả đúng 401/403), test @PreAuthorize self-call scenario, và test JWT issue + validate flow.
→ Đi tới Module 06: Testing strategy
📚 Tài liệu mở rộng
Spring Security:
- Spring Security Reference — full guide. Section "Servlet Applications" → "Authentication" + "Authorization" + "OAuth2".
- Spring Security 6 Migration Guide — từ 5.x, cover
WebSecurityConfigurerAdapter→SecurityFilterChain.
RFC / Spec chính thức:
- RFC 6749 — OAuth 2.0 Authorization Framework — 4 grant type, roles, token endpoint.
- RFC 7519 — JSON Web Token — claims, encoding, validation rules.
- RFC 7515 — JSON Web Signature — JWS Compact Serialization, header, signing.
- RFC 7517 — JSON Web Key — JWK/JWKS format, key rotation endpoint.
- RFC 6454 — The Web Origin Concept — định nghĩa origin tuple, Same-Origin Policy foundation.
- Fetch Living Standard — CORS Protocol — preflight algorithm, forbidden header, credentials spec.
Security reference:
- OWASP Top 10 — A01 Broken Access Control, A02 Cryptographic Failures, A05 Security Misconfiguration (CORS/CSRF).
- OWASP Authentication Cheat Sheet — password storage, session management.
- OWASP JWT Security Cheat Sheet —
alg:noneattack, key confusion, expiry validation.
Sách:
- Spring Security in Action — Laurentiu Spilca (Manning, 2024 ed.). Cover Spring Security 6 từ đầu — filter chain, OAuth2, method security, test. Cuốn tham khảo toàn diện nhất cho Spring Security 6.
- OAuth 2.0 Simplified — Aaron Parecki (2020). Giải thích OAuth2 grant types bằng ngôn ngữ đơn giản, nhiều diagram. Đọc trước khi dùng Keycloak/Auth0.
Blog / Tool:
- Baeldung — Spring Security — 200+ bài tutorial từng feature riêng lẻ.
- jwt.io — decode/verify JWT trực tuyến. Dùng để debug claims, verify signature.
- OWASP ZAP — web application security scanner. Test CORS, CSRF, auth bypass tự động.
Chúc mừng — bạn đã hoàn thành Module 05. Security layer đã sẵn sàng: filter chain hiểu được, JWT issue + validate, BCrypt password hash đúng cost, method security không self-call bypass, CORS/CSRF config production-grade. Nghỉ 1 ngày cho các concept lắng xuống — đặc biệt JWT signing và AOP proxy self-call. Rồi vào Module 06 — test toàn bộ security layer vừa xây.
Bài này có giúp bạn hiểu bản chất không?