java - 如何获取查询/变异操作名称

标签 java graphql graphql-java

我是 Spring boot + GraphQL 的新手。 我需要在我的 Controller 类中获取 Query/Mutation 操作名称。

目的:需要授予某些用户特定变异/查询操作的权限。 此处用户类型将作为请求 header 传递,并将进行验证并检查是否允许用户访问该操作。

@PostMapping
public ResponseEntity<Object> callGraphQLService(@RequestBody String query, @RequestHeader("user") String userName) {
    ExecutionResult result = graphService.getGraphQL().execute(ExecutionInput.newExecutionInput()
            .query(query)
            .context(userName)
            .build());
    return new ResponseEntity<>(result, HttpStatus.OK);
}

Suggest any efficient mechanism to perform authorization for specific Query/Mutation

最佳答案

我认为您在这里考虑的是 REST 术语中的授权,它不能很好地映射到 GraphQL。您需要一种更精细的方法,而不是基于操作名称(或基于 REST 中的 URL)在顶层做出单一决策。您需要知道谁可以在现场级别查看/执行操作,因为允许客户进行临时选择。

有多种方法可以做到这一点,但由于您提到了 Spring,您可以简单地在服务级别使用 Spring Security。如果每个 protected 字段都由服务方法支持(并且应该如此),您可以像往常一样使用 Spring Security 保护这些方法。

更好的是,您还应该提供自定义的 GraphqlFieldVisibility 实现,这样未经授权的客户端甚至无法知道他们不允许访问的字段的存在在架构中查看。你可以使用例如Spring 的 SpelExpressionParser 根据 Spring Security 规则为每个用户决定模式的哪些部分是动态可见的。

如果 Spring Security 不是一个选项,您可以实现自定义 Instrumentation(例如,通过扩展 SimpleInstrumentation)。在那里你可以实现像 beginExecuteOperation 这样的回调,这会让你访问解析的查询(如果你真的只想做 REST 风格的顶级认证就足够了),或者 begin( Deferred)Field(允许您访问 FieldDefinition)或 beginFieldFetch/instrumentDataFetcher(允许您访问整个 DataFetchingEnvironment ) 对每个字段执行身份验证。

如果采用这种方式,您可以在字段定义本身中保留身份验证信息(例如所需的角色)作为指令。并将当前登录的用户保留在共享上下文中。这样,您始终拥有在每个级别进行身份验证所需的一切。

在所有情况下,建议提供 GraphqlFieldVisibility 以根据上下文完全隐藏 protected 字段的存在。

这是一个抽象示例,显示了使用 Instrumentation 方法的要点(因为您不需要 Spring Security 方法的任何特殊内容,只需像往常一样使用 Spring Security):

//Checks if the current user has the needed roles for each field
public class AuthInstrumentation extends SimpleInstrumentation {
    @Override
    public DataFetcher<?> instrumentDataFetcher(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters parameters) {
        GraphQLFieldDefinition fieldDefinition = parameters.getEnvironment().getFieldDefinition();
        //Each protected field is expected to have a directive called "auth" with an argument called "rolesRequired" that is a list of strings representing the roles
        Optional<GraphQLArgument> rolesRequired = DirectivesUtil.directiveWithArg(fieldDefinition.getDirectives(), "auth", "rolesRequired");
        if (rolesRequired.isPresent()) {
            List<String> roles = (List<String>) rolesRequired.get().getValue();
            User currentUser = parameters.getEnvironment().getContext(); //get the user from context
            if (!currentUser.getRoles().containsAll(roles)) {
                //Replace the normal resolution logic with the one that always returns null (or throws an exception) when the user doesn't have access
                return env -> null;
            }
        }
        return super.instrumentDataFetcher(dataFetcher, parameters);
    }
}

您不必在指令中存储所需的角色,这只是一个方便的地方。如果合适,您可以从外部来源获取相同的信息。

然后注册这个工具:

GraphQL graphQL = GraphQL.newGraphQL(schema)
    .instrumentation(new AuthInstrumentation())
    .build();

并且在执行查询时,将当前用户放入上下文中:

//Get the current user's roles however you normally do
User user = loadUser(userName);
ExecutionInput input = ExecutionInput.newExecutionInput()
    .query(operation)
    .context(user) //put the user into context so the instrumentation can get it
    .build()

通过这种方式,即使不使用 Spring Security,您也可以将所有内容整齐地分开(解析器中没有身份验证逻辑,不需要外部上下文)和每个字段的上下文。

让我们更进一步,制作自定义 GraphqlFieldVisibility:

public class RoleBasedVisibility implements GraphqlFieldVisibility {

    private final User currentUser;

    public RoleBasedVisibility(User currentUser) {
        this.currentUser = currentUser;
    }

    @Override
    public List<GraphQLFieldDefinition> getFieldDefinitions(GraphQLFieldsContainer fieldsContainer) {
        return fieldsContainer.getFieldDefinitions().stream()
                .filter(field -> isFieldAllowed(field, currentUser))
                .collect(Collectors.toList());
    }

    @Override
    public GraphQLFieldDefinition getFieldDefinition(GraphQLFieldsContainer fieldsContainer, String fieldName) {
        GraphQLFieldDefinition fieldDefinition = fieldsContainer.getFieldDefinition(fieldName);
        return fieldDefinition == null || !isFieldAllowed(fieldDefinition, currentUser) ? null : fieldDefinition;
    }

    private boolean isFieldAllowed(GraphQLDirectiveContainer field, User user) {
        //Same as above, extract this into a common function
        Optional<GraphQLArgument> rolesRequired = DirectivesUtil.directiveWithArg(field.getDirectives(), "auth", "rolesRequired");
        List<String> roles = (List<String>) rolesRequired.get().getValue();
        return currentUser.getRoles().containsAll(roles);
    }
}

如您所见,可见性取决于用户,这次您无法从上下文中获取,因此您必须根据请求 实例化它。这意味着您还需要转换模式并根据请求实例化 GraphQL。其余相同。

GraphQLSchema schema = baseSchema.transform(sch ->
        sch.codeRegistry(baseSchema.getCodeRegistry().transform(code ->
                code.fieldVisibility(new RoleBasedVisibility(currentUser)))));

GraphQL graphQL = GraphQL.newGraphQL(schema)
        .instrumentation(new AuthInstrumentation())
        .build();

这样,您就拥有了完整的安全设置。如果不允许,未经授权的用户甚至不知道字段的存在。如果一般情况下允许他们查看它,但他们只能有条件地获取它,AuthInstrumentation 会覆盖它。

关于java - 如何获取查询/变异操作名称,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51764092/

相关文章:

java - 使用 Select Case 进行 JPA 条件查询

java - 向 JFreeChart 添加水平滚动

java - Android 模拟 GPS 卫星(不仅仅是位置)

java - 将 Groovy 动态方法添加到 Spring Controller

graphql - 如何在 ApolloClient.query() 上使用 'cache-and-network' 策略

spring - Graphql工具:将实体类型映射到graphql类型

java - 如何将apollographographll客户端实现到Maven项目中以激活订阅?

reactjs - 如何修复 `Warning: Text content did not match. Server: "一些数据“客户端: "Loading..."`

spring - 带有 graphql-spring 的 LazyInitializationException

java - GraphQL SPQR 扩展输入对象的突变参数