如何获取查询/突变操作名称

Rah*_*hul 1 java graphql graphql-java

我是Spring Boot + GraphQL的新手。我需要在我的控制器类中获取查询/更改操作名称。

目的:需要某些用户的特定突变/查询操作的最高权限。在这里,用户类型将作为请求标头传递并得到验证,并检查是否允许用户访问该操作。

@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);
}
Run Code Online (Sandbox Code Playgroud)

建议任何有效的机制来执行针对特定查询/突变的授权

kaq*_*qao 9

我认为您在这里考虑的是REST术语中的授权,它与GraphQL的映射并不理想。您需要更精细的方法,而不是基于操作名称(或基于REST中的URL)的顶级决策。您需要知道允许哪些人在字段级别查看/执行操作,因为允许客户端进行临时选择。

有多种方法可以执行此操作,但是自从您提到Spring以来,您只需在服务级别使用Spring Security。如果每个受保护的字段都由服务方法支持(应该支持),则可以照常使用Spring Security保护这些方法。

更好的是,您还应该提供一个自定义GraphqlFieldVisibility实现,以使未经授权的客户端甚至无法了解他们在架构中不允许查看的字段的存在。您可以使用Spring SpelExpressionParser来基于Spring Security规则,为每个用户动态决定架构的哪些部分。

如果不能选择Spring Security,则可以实现一个自定义Instrumentation(例如,通过扩展SimpleInstrumentation)。在那里,您可以实现类似的回调beginExecuteOperation,这将使您能够访问已解析的查询(如果您只想仅执行REST样式的顶级身份验证,就足够了),或者begin(Deferred)Field(可以访问FieldDefinition)或beginFieldFetch/instrumentDataFetcher(可以给您提供访问权限)访问整个DataFetchingEnvironment)以按字段执行身份验证。

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

在所有情况下,建议在GraphqlFieldVisibility上下文中完全隐藏受保护字段的存在。

这是一个抽象示例,展示了使用该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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

然后注册此工具:

GraphQL graphQL = GraphQL.newGraphQL(schema)
    .instrumentation(new AuthInstrumentation())
    .build();
Run Code Online (Sandbox Code Playgroud)

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

//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()
Run Code Online (Sandbox Code Playgroud)

这样,即使不使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

GraphQLSchema schema = baseSchema.transform(
    schemaBuilder -> schemaBuilder.fieldVisibility(new RoleBasedVisibility(currentUser)));
GraphQL graphQL = GraphQL.newGraphQL(schema)
        .instrumentation(new AuthInstrumentation())
        .build();
Run Code Online (Sandbox Code Playgroud)

这样,您便拥有了完整的安全设置。如果未经授权的用户不允许,则甚至都不知道该字段存在。如果允许他们总体上看它,但是他们只能有条件地拿走它,AuthInstrumentation那就掩盖它。