그림 출처: Hands-On Spring Security 5 for Reactive Applications
그림을 보면 Spring Security 개발진들이 기존 WebMVC 와 동일하게 환경을 구성하기 위해 노력한 과정을 볼 수 있다. Filter 기반의 인증과정, AuthenticationManager 와 UserDetailService 를 사용한 인증과정이 기존 WebMVC 와 유사한것을 확인할 수 있다.
spring-boot-starter-security 모듈 역시 WebFlux 를 지원할 수 있도록 업데이트 되었다.
SecurityContext from Reactor Context
Webflux 와 WebMVC 에서 Spring Security 의 가장 큰 차이점은 기존의 SecurityContext 의 개념이 사용되지 않는 것.
WebMVC Spring Security 는 ThreadLocal 에 SecurityContext 를 저장해 한번의 연결동안 Authentication 인증객체를 계속 유지했지만, Webflux 에선 하나의 연결에 여러개의 Thread 꼬여있을 수 있어 Reactor Context 를 사용한다. (하나의 연결에대해 하나의 스레드가 담당하여 처리한다는 보장이 없다)
WebMVC 에선 SecurityContextHolder 에서 SecurityContext 를 가져왔다면 WebFlux 에선 ReactiveSecurityContextHolder 에서 SecurityContext 를 가져온다.
// ReactorContextWebFilter 에 적용할 시큐리티 필터 // 기존 mvc 에서 사용하던 HttpSecurity 에서 webflux 용으로 정의된 ServerHttpSecurity // 기존 spring security 사용 방식과 크게 다르지 않다. @Bean public SecurityWebFilterChain securityFilterChainConfigurer(ServerHttpSecurity httpSecurity) { return httpSecurity .exceptionHandling() //.authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() -> // 미 로그인 // swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED))) //.accessDeniedHandler((swe, e) -> Mono.fromRunnable(() -> // 미 로그인 // swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN))) .authenticationEntryPoint((swd, e) -> Mono.error(newAuthenticationCredentialsNotFoundException(""))) .accessDeniedHandler((swe, e) -> Mono.error(newAccessDeniedException(""))) .and() .authenticationManager(authenticationManager) .securityContextRepository(securityContextRepository) .csrf().disable() .cors().disable() .httpBasic().disable() // Basic Authentication disable .formLogin().disable() .authorizeExchange() .pathMatchers("/member/join", "/member/login").permitAll() .pathMatchers("/member/**", "/rent/**").authenticated() .pathMatchers("/admin/**").hasAnyRole("ROLE_ADMIN") .anyExchange().permitAll() .and() .build(); }
마지막으로 ServerHttpSecurity 에 커스터마이징한 SecurityContextRepository, AuthenticationManager 를 등록하고 별도의 에러 핸들링 처리를 한다면 에러를 반환해 핸들링, 없다면 HttpStatus.UNAUTHORIZED 로 단순 HTTP Status 만 반환할 수 있다.