如何获取查询/变异操作名称 [英] How to get Query/Mutation operation name

查看:14
本文介绍了如何获取查询/变异操作名称的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

目的:需要对某些用户进行特定变异/查询操作的大许可.在这里,用户类型将作为请求标头传递并进行验证并检查是否允许用户访问该操作.

@PostMapping公共响应实体<对象>callGraphQLService(@RequestBody String query, @RequestHeader("user") String userName) {ExecutionResult 结果 = graphService.getGraphQL().execute(ExecutionInput.newExecutionInput().query(查询).context(用户名).建造());返回新的响应实体(结果,HttpStatus.OK);}

<块引用>

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

解决方案

我认为您在这里考虑的是 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):

//检查当前用户是否具有每个字段所需的角色公共类 AuthInstrumentation 扩展 SimpleInstrumentation {@覆盖公共 DataFetcherinstrumentDataFetcher(DataFetcher<?> dataFetcher, InstrumentationFieldFetchParameters 参数) {GraphQLFieldDefinition fieldDefinition = parameters.getEnvironment().getFieldDefinition();//每个受保护的字段都应该有一个名为auth"的指令;带有一个名为rolesRequired"的参数;这是代表角色的字符串列表可选的rolesRequired = DirectivesUtil.directiveWithArg(fieldDefinition.getDirectives(), auth", rolesRequired");如果(rolesRequired.isPresent()){列表<字符串>角色 = (List) rolesRequired.get().getValue();用户 currentUser = parameters.getEnvironment().getContext();//从上下文中获取用户如果 (!currentUser.getRoles().containsAll(roles)) {//用用户无权访问时总是返回null(或抛出异常)的逻辑替换正常的解析逻辑返回环境 ->空值;}}返回 super.instrumentDataFetcher(dataFetcher, 参数);}}

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

然后注册这个检测:

GraphQL graphQL = GraphQL.newGraphQL(schema).instrumentation(新的AuthInstrumentation()).建造();

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

//获取当前用户的角色,但您通常会这样做用户用户 = loadUser(userName);ExecutionInput 输入 = ExecutionInput.newExecutionInput().query(操作).context(user)//将用户置于上下文中,以便检测可以获取它.建造()

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

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

public class RoleBasedVisibility 实现了 GraphqlFieldVisibility {私有最终用户 currentUser;公共 RoleBasedVisibility(用户当前用户){this.currentUser = currentUser;}@覆盖公共列表getFieldDefinitions(GraphQLFieldsContainer fieldsContainer){返回 fieldsContainer.getFieldDefinitions().stream().filter(field -> isFieldAllowed(field, currentUser)).collect(Collectors.toList());}@覆盖公共 GraphQLFieldDefinition getFieldDefinition(GraphQLFieldsContainer fieldsContainer, String fieldName) {GraphQLFieldDefinition fieldDefinition = fieldsContainer.getFieldDefinition(fieldName);返回字段定义 == null ||!isFieldAllowed(fieldDefinition, currentUser) ?null : 字段定义;}私有布尔值 isFieldAllowed(GraphQLDirectiveContainer 字段,用户用户){//同上,把this提取成一个普通函数可选的rolesRequired = DirectivesUtil.directiveWithArg(field.getDirectives(), auth", rolesRequired");列表<字符串>角色 = (List) rolesRequired.get().getValue();返回 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(新的AuthInstrumentation()).建造();

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

I'm new to Spring boot + GraphQL. I need to get the Query/Mutation operation name inside my controller class.

Purpose : Need to grand permission to some users to specific mutation/Query operations. Here the user type will be passed as a request header and will be validated and check whether the user is allowed to access that operation.

@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

解决方案

I think you're thinking of authorization in REST terms here, and it doesn't map well to GraphQL. Instead of a single decision at the top level based on the operation name (or based on the URL in REST), you need a more granular approach. You need to know who's allowed to see/do what at the field level, as the client is allowed make ad-hoc selections.

There's multiple ways to do this, but since you mentioned Spring, you can simply use Spring Security at the service level. If each protected field is backed by a service method (and it should be), you can protect those methods using Spring Security as usual.

Even better, you should also provide a custom GraphqlFieldVisibility implementation, so that unauthorized clients can't even know about the the existence of fields they're not allowed to see in the schema. You can use e.g. Spring's SpelExpressionParser to make decisions on what parts of the schema are visible dynamically, for each user, based on Spring Security rules.

If Spring Security is not an option, you can implement a custom Instrumentation (e.g. by extending SimpleInstrumentation). There you can implement the callbacks like beginExecuteOperation, that will give you access to the parsed query (enough if you really just want to do REST-style top-level auth only), or begin(Deferred)Field (which gives you access to the FieldDefinition) or beginFieldFetch/instrumentDataFetcher (which gives you access to the entire DataFetchingEnvironment) to perform auth per-field.

If you go this way, you can keep the auth information (e.g. the required roles) in the field definition itself as directives. And keep the currently logged in user in the shared context. This way you always have everything you need to do authentication at each level.

In all cases, it's advisable to provide GraphqlFieldVisibility to completely hide the existence of the protected fields contextually.

Here's an abstract example showing the major points using the Instrumentation approach (as you need nothing special for the Spring Security approach, just use Spring Security as usual):

//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);
    }
}

You don't have to store the required roles in the directives, it's just a convenient place. You can get the same info from an external source if it's appropriate.

Then register this instrumentation:

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

And when executing a query, put the current user into the context:

//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()

This way you have everything neatly separated (no auth logic in resolvers, no external context needed) and contextual per field, even without using Spring Security.

Let's go further and make a custom 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);
    }
}

As you see, visibility depends on the user, which this time you can not get from the context, so you have to instantiate it per request. This means you need to transform the schema and instantiate GraphQL per request as well. The rest is the same.

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

With that, you have a full security setup. Unauthorized users won't even know a field exists if they're not allowed to. If they're allowed to see it in general, but they can only fetch it conditionally, the AuthInstrumentation covers it.

这篇关于如何获取查询/变异操作名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆