graphql-java/graphql-java-spring

Is it possible to support Mono return type in DataFetcher?

jaggerwang opened this issue · 1 comments

Right now, graphql java webflux only support CompletableFuture return type, the mono returned need to be converted to CompletableFuture by calling toFuture() method.

package net.jaggerwang.scip.gateway.adapter.graphql.datafetcher.query;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import net.jaggerwang.scip.gateway.adapter.graphql.datafetcher.AbstractDataFetcher;
import org.springframework.stereotype.Component;

@Component
public class QueryUserInfoDataFetcher extends AbstractDataFetcher implements DataFetcher {
    @Override
    public Object get(DataFetchingEnvironment env) {
        var id = Long.valueOf((Integer) env.getArgument("id"));
        return userAsyncService.info(id)
                .subscriberContext(context -> env.getContext())
                .toFuture();
    }
}

But if I want to use spring security to protect method by @EnableReactiveMethodSecurity, spring security will require the method to return Mono or Flux. The two are conflicted.

@Component
public class QueryUserInfoDataFetcher extends AbstractDataFetcher implements DataFetcher {
    @Override
    @PreAuthorize("isAuthenticated()")
    public Object get(DataFetchingEnvironment env) {
        var id = Long.valueOf((Integer) env.getArgument("id"));
        return monoWithContext(userAsyncService.info(id), env);
    }
}

How can I solve this problem?

Finally, I use a aspect to convert Mono to CompletableFuture and do authentication.

package net.jaggerwang.scip.gateway.api.security;

...

/**
 * Check authentication and authorization of all GraphQL datafetchers, and convert Mono returned
 * by datafetcher to CompletableFuture, as GraphQL not support reactor's Mono and Flux.
 */
@Component
@Aspect
public class SecureGraphQLAspect {
    @Around("allDataFetchers() && isInApplication()")
    public Object doSecurityCheck(ProceedingJoinPoint joinPoint) {
        var args = joinPoint.getArgs();
        var env = (DataFetchingEnvironment) args[0];
        return ReactiveSecurityContextHolder.getContext()
                .doOnSuccess(securityContext ->  {
                    var method = ((MethodSignature) joinPoint.getSignature()).getMethod();
                    var permitAll = method.getAnnotation(PermitAll.class);
                    if (permitAll == null) {
                        var auth = securityContext.getAuthentication();
                        if (auth == null || auth instanceof AnonymousAuthenticationToken ||
                                !auth.isAuthenticated()) {
                            throw new UnauthenticatedException("未认证");
                        }
                    }
                })
                .flatMap(securityContext -> {
                    Object result;
                    try {
                        result = joinPoint.proceed();
                    } catch (Throwable e) {
                        return Mono.error(new RuntimeException(e));
                    }
                    return result instanceof Mono ? (Mono) result : Mono.just(result);
                })
                .subscriberContext(context -> env.getContext())
                .toFuture();
    }

    @Pointcut("target(graphql.schema.DataFetcher)")
    private void allDataFetchers() {
    }

    @Pointcut("within(net.jaggerwang.scip.gateway.adapter.graphql..*)")
    private void isInApplication() {
    }
}