I'm mirroring a question asked directly on GitHub here.
I'm up to the point where if I authorize("/subscriptions",permitAll)
while configuring my SecurityWebFilterChain
then I can successfully use my subscription queries. However, that removes all the security. I would have liked to do: authorize("/subscriptions",hasAuthority("access"))
Anyway, now I need to make sur that the user is properly authenticated and authorized. I use ReactiveMetodSecurity
with @PreAuthorize("hasAuthority('read')")
or hasPermission(#id, 'entity', 'read:restricted')
directly on the @DgsSubscription
method.
This works in a way: hasAuthority
is triggered although it responds with false. As far as I know that's because the Authentication
object has not been initialized with the token.
It's most likely because there is no default behavior to intercept the connection_init
message that contains the token.
Thus I'm wondering: how can I fetch that connection_init
message and set the Authentication
so that it's picked up by spring ?
Thanks
EDIT 1: I've been able to successfully authenticate from a SecurityWebFilterChain
point of view. The Authentication object however is not available in the @DgsSubscription
. I can access it though using the workaround explained here. That means I now have a solution although this won't be working with a connection_init
provided Authorization
. Also it will require me to add a level to my objects to have the PreAuthorization work.
Here is was I was able to do with the auth header in the handshake request:
@Component
class DgsSecurityContextBuilder : DgsReactiveCustomContextBuilderWithRequest<Authentication> {
override fun build(
extensions: Map<String, Any>?,
headers: HttpHeaders?,
serverRequest: ServerRequest?
): Mono<Authentication> = ReactiveSecurityContextHolder.getContext().map { it.authentication }
}
@DgsComponent
class AggregateSubscriptions(val handler: AggregateSubscriptionsHandler) {
@DgsSubscription
fun aggregateNotifications(
@InputArgument id: UUID,
@InputArgument notificationTypes: List<NotificationType>?,
dfe: DataFetchingEnvironment
) = let {
val auth = DgsContext.getCustomContext<Authentication>(dfe)
handler.aggregateNotifications(id, notificationTypes, auth)
}
}
@Component
class AggregateSubscriptionsHandler {
@PreAuthorize("@myPermissionEvaluator.hasAuthority(#auth, '$AGGREGATE_READ')")
fun aggregateNotifications(id: UUID, notificationTypes: List<NotificationType>?, auth: Authentication) =
Flux.interval(Duration.ofMillis(1000))
.map {
Notification.Builder()
.withAggregateId(id.toString())
.withNotificationType(NotificationType.AdvertiserAdded)
.withPayload(listOf(KeyValue.Builder().withKey("t").withValue(it.toString()).build()))
.build()
}
}
That's the only way I found to currently handle authz in subscription requests
Hopefully we'll get a fix soon that will allow us to use the connection_init message.
See: https://github.com/Netflix/dgs-framework/issues/1442 and https://github.com/Netflix/dgs-framework/issues/450