
Security Reactive อย่างลึกด้วย Spring Security WebFlux
เวลาอ่าน ≈ 20 นาที — คู่มือเชิงลึกสำหรับตั้งค่าความปลอดภัย Reactive บน Spring Security WebFlux ครบทุกเรื่องตั้งแต่ Authentication-WebFilter, SecurityWebFilterChain, JWT, OAuth2 Login, ไปจนถึง Reactive AuthorizationManager และการทดสอบ
สารบัญ
- Reactive Security ทำไมถึงต่าง?
- โครงสร้าง SecurityWebFilterChain
- AuthenticationWebFilter & ReactiveAuthenticationManager
- JWT กับ Reactive Authentication
- Reactive OAuth2 Login Flow
- Reactive AuthorizationManager
- บริหาร ReactiveSecurityContextHolder
- CSRF & CORS ใน WebFlux
- การทดสอบ Security ด้วย WebTestClient
- Best Practice & Checklist
1. Reactive Security ทำไมถึงต่าง?
บน Stack Servlet (Spring MVC) Spring Security ทำงานผ่าน javax.servlet.Filter
หลายตัว. แต่ WebFlux รันบน Netty event-loop และใช้ org.springframework.web.server.WebFilter
ที่เป็น non-blocking. ทุกส่วนจึงต้องคืนค่า Mono
/Flux
เพื่อหลีกเลี่ยงการบล็อก Thread
2. โครงสร้าง SecurityWebFilterChain

@Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { return http .csrf(csrf -> csrf.disable()) .authorizeExchange(ex -> ex .pathMatchers("/public/**").permitAll() .anyExchange().authenticated()) .authenticationManager(reactiveAuthManager()) .securityContextRepository(securityContextRepo()) .build(); }
SecurityWebFilterChain
– ประกอบ WebFilter ตาม configAuthenticationWebFilter
– รับ Credential สร้างAuthentication
AuthorizationWebFilter
– ใช้ReactiveAuthorizationManager
ตรวจสิทธิ์
3. AuthenticationWebFilter & ReactiveAuthenticationManager
3.1 กำหนดวิธีอ่าน Credentials
@Bean public AuthenticationWebFilter authenticationWebFilter() { AuthenticationWebFilter filter = new AuthenticationWebFilter(reactiveAuthManager()); filter.setServerAuthenticationConverter(exchange -> { return Mono.zip( exchange.getRequest().getHeaders().getFirst("X-USER"), exchange.getRequest().getHeaders().getFirst("X-PASS")) .map(tuple -> new UsernamePasswordAuthenticationToken(tuple.getT1(), tuple.getT2())); }); return filter; }
3.2 ReactiveAuthenticationManager
@Bean public ReactiveAuthenticationManager reactiveAuthManager(UserDetailsService svc, PasswordEncoder pwd) { UserDetailsRepositoryReactiveAuthenticationManager mgr = new UserDetailsRepositoryReactiveAuthenticationManager(svc); mgr.setPasswordEncoder(pwd); return mgr; }
4. JWT กับ Reactive Authentication

4.1 สร้าง JWT TokenFilter
public class JwtAuthWebFilter implements WebFilter { private final JwtDecoder decoder; public JwtAuthWebFilter(JwtDecoder decoder) { this.decoder = decoder; } @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { return Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION)) .filter(h -> h.startsWith("Bearer ")) .map(h -> h.substring(7)) .flatMap(decoder::decode) .map(jwt -> new UsernamePasswordAuthenticationToken( jwt.getSubject(), jwt, AuthorityUtils.NO_AUTHORITIES)) .flatMap(auth -> chain.filter(exchange) .contextWrite(withSecurityContext(auth))) .switchIfEmpty(chain.filter(exchange)); } private Context withSecurityContext(Authentication auth) { return ReactiveSecurityContextHolder.withAuthentication(auth); } }
4.2 ผูก Filter เข้า Chain
http.addFilterAt(new JwtAuthWebFilter(jwtDecoder), SecurityWebFiltersOrder.AUTHENTICATION);
5. Reactive OAuth2 Login Flow

@Bean public SecurityWebFilterChain oauth2LoginChain(ServerHttpSecurity http) { return http .authorizeExchange(ex -> ex.anyExchange().authenticated()) .oauth2Login(oauth2 -> oauth2.loginPage("/oauth2/authorization/github")) .oauth2Client(Customizer.withDefaults()) .build(); }
6. Reactive AuthorizationManager
@Bean public SecurityWebFilterChain chain(ServerHttpSecurity http) { return http .authorizeExchange(ex -> ex .pathMatchers(HttpMethod.POST, "/admin/**") .access((mono, ctx) -> mono .map(Authentication::getAuthorities) .flatMapMany(Flux::fromIterable) .any(a -> a.getAuthority().equals("ROLE_ADMIN")) .map(AuthorizationDecision::new)) .anyExchange().permitAll()) .build(); }
7. บริหาร ReactiveSecurityContextHolder
- ใช้
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth))
- ในการสลับ Scheduler (เช่น
publishOn
) ค่าContext
จะไหลไปด้วย - เปิด
DelegatingSecurityContextReactiveOperator
ใน lib spring-security-core สำหรับงาน Batch
8. CSRF & CORS ใน WebFlux
http .csrf(csrf -> csrf .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())) .cors(cors -> cors.configurationSource(exchange -> { CorsConfiguration cfg = new CorsConfiguration(); cfg.addAllowedOrigin("https://example.com"); cfg.addAllowedMethod("*"); cfg.setAllowCredentials(true); return cfg; }));
9. การทดสอบ Security ด้วย WebTestClient
@Import(SecurityConfig.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class JwtSecurityTest { @Autowired WebTestClient client; @Test void shouldRejectWhenNoToken() { client.get().uri("/protected") .exchange() .expectStatus().isUnauthorized(); } @Test void shouldAllowWithValidToken() { String token = JwtUtil.generate("userA"); client.get().uri("/protected") .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .exchange() .expectStatus().isOk(); } }
10. Best Practice & Checklist
- ทุก WebFilter ต้อง non-blocking 100% (ตรวจ BlockHound)
- ใช้
ServerSecurityContextRepository
เก็บ/เรียก Auth เพื่อลด latency - ตั้งค่า
spring.security.oauth2.client.provider.*.user-name-attribute
ให้ตรง field - เปิด Micrometer metric:
spring.security
prefix ติดตาม auth error rate - ทดสอบ Load 2× ภาระงานจริง เพื่อจับ race condition ใน Reactive Context
สรุป
Spring Security WebFlux มอบเครื่องมือครบเครื่องสำหรับ Reactive Authentication, Authorization, และ Context Propagation. หากออกแบบ SecurityWebFilterChain
, เลือก AuthenticationManager, และจัดการ ReactiveSecurityContextHolder
อย่างถูกวิธี คุณจะได้ระบบที่ รวดเร็ว, ไม่บล็อก, และ ปลอดภัยระดับสากล รองรับทั้ง JWT, OAuth2, และ Custom Auth Provider ได้สบาย ๆ