如何获取 Query/Mutation 操作名称

Posted

技术标签:

【中文标题】如何获取 Query/Mutation 操作名称【英文标题】:How to get Query/Mutation operation name 【发布时间】:2019-01-16 17:54:37 【问题描述】:

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

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

@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);

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

【问题讨论】:

【参考方案1】:

我认为您在这里考虑的是 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)来执行每个字段的身份验证。

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

在所有情况下,建议提供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);
    

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

然后注册这个工具:

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 会覆盖它。

【讨论】:

感谢@kaqqao 的回复。让我试试这个。 在 RoleBasedVisibility.isFieldAllowed DirectivesUtil.directiveWithArg(field.getDirectives(), "auth", "rolesRequired"); 方法中。我们如何通过添加带有“auth”和“rolesRequired”的指令来保护字段? 嗨,Kaqqao,不错的解决方案,但每次请求都重新构建架构感觉不太好。自从你回答这个问题后,你有没有想过这个问题? @DrSatan1 不幸的是,GraphQLFieldVisibility 进入了架构,因此要更改它,必须转换架构。推理可以找到here。请注意,转换不是重建,也不是昂贵的操作。不过,请记住,如果您使用的是 Spring Security,这些都不是问题,因为 Spring 将 SecurityContext 保存在 ThreadLocal 中,您可以从 GraphQLFieldVisibility 动态获取它,因此不需要转换。 对于没有通过github.com/leangen/graphql-spqr/issues/… 找到此答案的任何人,您应该知道现在最好通过ResolverInterceptor 完成此操作。谢谢@kaqqao!见github.com/leangen/graphql-spqr/issues/…

以上是关于如何获取 Query/Mutation 操作名称的主要内容,如果未能解决你的问题,请参考以下文章

Python如何通过字符或数字动态获取对象的名称或者属性?

如何获得“友好”的操作系统版本名称?

如何获得“友好”的操作系统版本名称?

如何在 EditText 框中获取指定的产品名称?

如何在 iOS 7 中使用 iBeacons 获取设备详细信息,如 UUID 或设备名称

如何获取 .apk 文件详细信息(如:名称、id、vercode ..etc)存储在 android 设备的外部存储中