-
Notifications
You must be signed in to change notification settings - Fork 371
Description
Hi,
I am setting up oauth2 login for my project. Everything works fine until it comes to method-level security. The Reactor's Context seems not to be integrated, therefore PreAuthorize cannot determine the current user role. I also tried to use GraphQLContext to gain access to the Reactor's Context, but it also does not work. I have done some research but have not yet found any reliable information/examples regarding authorization for graphql-kotlin. It would be great if we could do method-level security as usual.
I'm using graphql-kotlin-spring-server 5.3.1 and spring boot 2.6.2
My simplified SecurityConfig looks like this:
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class SecurityConfiguration {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http
.oauth2Login()
.and().oauth2Client()
.and().authorizeExchange().anyExchange().authenticated()
.and()
.build()
}
}
My sample queries:
currentRole always throws IllegalStateException due to empty context
foo and bar always throw DeniedException
@Component
class FooQuery: Query {
fun currentRole(): Mono<String> {
return ReactiveSecurityContextHolder.getContext()
.switchIfEmpty(Mono.error(IllegalStateException("Context is empty!")))
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.cast(DefaultOAuth2User::class.java)
.map { user -> user.authorities.first().toString() }
}
// Reactive Method Security requires the return type of the method to be a
// org.reactivestreams.Publisher (i.e. Mono/Flux)
// or the function must be a Kotlin coroutine function.
// This is necessary to integrate with Reactor’s Context.
@PreAuthorize("hasRole('USER')")
fun foo(): Mono<String> = Mono.just("foo")
@PreAuthorize("hasRole('USER')")
suspend fun bar() = "bar"
}
My custom FunctionDataFetcher:
class CustomFunctionDataFetcher(
target: Any?,
fn: KFunction<*>,
objectMapper: ObjectMapper,
appContext: ApplicationContext
) : SpringDataFetcher(target, fn, objectMapper, appContext) {
override fun get(environment: DataFetchingEnvironment): Any? = when (val result = super.get(environment)) {
is Mono<*> -> result.toFuture()
else -> result
}
}
In contrast, Method-level security works in the context of Rest Controller, for example:
@RestController
class SomeClass {
@PreAuthorize("hasRole('USER')")
@GetMapping("/currentRole")
fun getCurrentRole(): Mono<String> {
return ReactiveSecurityContextHolder.getContext()
.switchIfEmpty(Mono.error(IllegalStateException("Context is empty")))
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.cast(DefaultOAuth2User::class.java)
.map { user -> user.authorities.first().toString() }
}
}