Spring Security는 Reactor의 Context를 통한 메서드 보안을 지원합니다.
- Reactor의 Context는
ReactiveSecurityContextHolder를 통해 설정됩니다. - 예를 들어, 현재 로그인한 사용자의 메시지를 검색하는 방법을 보여줍니다.
- 이 방식은 메서드 반환 타입을
Mono나Flux와 같은 스트림으로 지정해야 합니다. - 코틀린의 경우
coroutine함수 또한 반환할 수 있습니다.
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.flatMap(this::findMessageByUsername)
// WebFlux 앱에서 `subscriberContext`는 `ReactorContextWebFilter`를 통해 자동 설정됩니다.
.subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
StepVerifier.create(messageByUsername)
.expectNext("Hi user")
.verifyComplete();Mono<String> findMessageByUsername(String username) {
return Mono.just("Hi " + username);
}Reactive 앱에서 메서드 보안을 위한 최소 보안 구성은 다음과 같습니다.
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build();
UserDetails admin = userBuilder.username("admin")
.password("admin")
.roles("USER","ADMIN")
.build();
return new MapReactiveUserDetailsService(rob, admin);
}
}다음 클래스를 살펴봅시다.
@Component
public class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {
return Mono.just("Hello World!");
}
}위의 구성과 함께 확인해봅시다.
@PreAuthorize("hasRole('ADMIN'))는findByMessage()의 호출 권한을ADMIN으로 한정합니다.@EnableReactiveMethodSecurity는 모든 표준 메서드 보안 표현식에 사용할 수 있습니다.- 그러나 현재는 식의 부울 또는 부울 반환 타입만 지원하며 표현식이 block되지 않아야 함을 의미합니다.
WebFlux Security와 통합하면 인증된 사용자에 따라 Spring Security에서 Reactor Context가 자동으로 설정됩니다.
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
// 메서드 보안 구성이 동작하는 것을 확인할 수 있습니다.
// Best practice to use both for defense in depth
.authorizeExchange(exchanges -> exchanges
.anyExchange().permitAll()
)
.httpBasic(withDefaults())
.build();
}
@Bean
MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob")
.password("rob")
.roles("USER")
.build();
UserDetails admin = userBuilder.username("admin")
.password("admin")
.roles("USER","ADMIN")
.build();
return new MapReactiveUserDetailsService(rob, admin);
}
}명령형 프로그래밍에서 반응형(Reactive) 프로그래밍으로 관점을 전환할 때 마주치는 큰 기술적 과제 중 하나는 스레드를 처리하는 방법에 있습니다.
- 반응형 프로그래밍에서는 스레드를 사용하여 거의 동시에 실행되는 여러 비동기 시퀀스를 처리할 수 있습니다
- 논블로킹 락스텝에서 실행됩니다.
- 시퀀스의 실행은 한 스레드에서 다른 스레드로 쉽게 이동할 수도 있습니다.
락스텝(Lockstep)은 동일한 작업 세트를 동시에 병렬로 실행하는 내결함성 컴퓨터 시스템입니다. 이중화는 오류 감지 및 오류 수정을 허용합니다.이런 형태는 ThreadLocal과 같이 스레드 모델에 의존하는 기능을 안정적으로 사용하는 개발자에게 문제가 됩니다.
이에 대한 ThreadLocal 사용에 대한 일반적인 해결 방법 사례는 다음과 같습니다.
- Tuple2<T, C>
- 비즈니스 데이터 T
- 컨텍스트 데이터 C
String key = "message";
Mono<String> r = Mono.just("Hello")
.flatMap(s -> Mono.deferContextual(
ctx -> Mono.just(s + " " + ctx.get(key))
)
).contextWrite(ctx -> ctx.put(key, "World"));
StepVerifier.create(r)
.expectNext("Hello World")
.verifyComplete();