-
Notifications
You must be signed in to change notification settings - Fork 371
Closed
Description
First, let's a big thank you for you great support and library 👏
I implemented a directive-based authentication with Spring Security.
This issue related to
#663 #692 where the code/examples were taking as a starting point
Factory for the Security Context:
/**
* Provide GraphQLSecurityContext
*/
@Component
class GraphQLSecurityContextFactory : GraphQLContextFactory<GraphQLSecurityContext> {
override suspend fun generateContext(
request: ServerHttpRequest,
response: ServerHttpResponse
): GraphQLSecurityContext {
val reactorContext =
coroutineContext[ReactorContext]?.context ?: throw RuntimeException("Reactor Context unavailable")
val securityContext = reactorContext.getOrDefault<Mono<SecurityContext>>(
SecurityContext::class.java,
Mono.error(AccessDeniedException("Security Context unavailable"))
)!!
return GraphQLSecurityContext(securityContext = securityContext)
}
}
class GraphQLSecurityContext(val securityContext: Mono<SecurityContext>) : GraphQLContextAn important difference to the example from previous issues:
securityContext is a Mono<SecurityContext>)
The implementation for the Authorisation checking:
/**
* Wraps the original Data Fetcher
* Check the Security Context for the required roles
*/
class AuthorizationDataFetcher(
/**
* Original Data fetcher which should be executed
*/
private val originalDataFetcher: DataFetcher<Any>,
/**
* Required Roles for this Data Fetcher Field
*/
private val requiredRoles: Collection<String>
) : DataFetcher<Any> {
override fun get(environment: DataFetchingEnvironment): Any {
if (requiredRoles.isEmpty()) {
return originalDataFetcher.get(environment)
}
val context: GraphQLSecurityContext? = environment.getContext<GraphQLSecurityContext>()
val securityContext: Mono<SecurityContext> =
context?.securityContext ?: throw AccessDeniedException("SecurityContext not present")
val accessCheck = checkRoles(securityContext)
return accessCheck
.filter { it.isGranted }
.map { createResult(originalDataFetcher.get(environment)) }
.switchIfEmpty(Mono.just(createGraphQLError(environment)))
.toFuture() // FIXME: how to avoid .block() also not allowed
}
private fun checkRoles(securityContext: Mono<SecurityContext>): Mono<AuthorizationDecision> {
val voter = hasAnyAuthority<Any>(*requiredRoles.toTypedArray())
return voter.check(securityContext.map { it.authentication }, null).defaultIfEmpty(AuthorizationDecision(false))
}
private fun createGraphQLError(environment: DataFetchingEnvironment): DataFetcherResult<Any> {
val error = SimpleKotlinGraphQLError(
AccessDeniedException("Role(s) ${requiredRoles.joinToString(separator = ",")} required"),
listOf(environment.field.sourceLocation),
environment.executionStepInfo.path.toList()
)
return DataFetcherResult.newResult<Any>()
.error(error)
.build()
}
private fun createResult(data: Any): DataFetcherResult<*> {
return DataFetcherResult.newResult<Any>()
.data((data as? CompletableFuture<*>)?.get() ?: data) // FIXME: how to avoid
.build()
}
}To problem/thing I don't like:
How to properly Map the ´Mono` (which could be a mono of any type to indicate Authorization result.
- Instead of
.toFuture().block()would return the actual value of original data fetcher => which throwsblock()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2", - If the orginalDataFetcher is FunctionDataFetcher it return CompletableFuture
Metadata
Metadata
Assignees
Labels
No labels