How to handle security (authn/authz) with DGS Subscriptions and spring security?

244 Views Asked by At

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.

Anonymous authentication

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.

1

There are 1 best solutions below

0
Crystark On BEST ANSWER

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