如何在GraphQL Spring Boot中访问查询选定字段上的所有指令?

huangapple go评论69阅读模式
英文:

How to access all directives on selected fields of a query, with GraphQL Spring Boot?

问题

我有一个认证指令用于限制字段的访问权限到特定的认证级别
```graphql
directive @auth(role: [String!]!) on FIELD_DEFINITION

例如,使用以下架构

type Query {
    test: TestResultType! @auth(role: ["USER", "ADMIN"]) 
}

type TestResultType {
    customer: Customer!
    seller: Seller!
}

type Customer {
    email: String!
    username: String!
    password: String! @auth(role: "ADMIN")
}

type Seller {
    brandName: String!
    email: String!
    username: String!
    password: String! @auth(role: "ADMIN")
}

查询test将受到限制,只能是"USER""ADMIN",而CustomerSellerpassword字段仅限于"ADMIN"

如果我具有"USER"的授权级别,但没有"ADMIN",那么以下查询应该可以正常进行,因为我没有请求受@auth(role: "ADMIN")指令保护的任何内容

query {
    test {
        customer {
            email
        }
        seller {
            brandName
            email
        }
    }
}

但是,如果我具有"USER"的授权级别,但没有"ADMIN",则以下查询应该返回错误,因为我在查询中选择了password字段,该字段受@auth(role: "ADMIN")指令保护

query {
    test {
        customer {
            email
            password
        }
        seller {
            brandName
            email
            password
        }
    }
}

要在Spring Boot GraphQL中使用指令,我必须注册一个SchemaDirectiveWiring,并使用RuntimeWiringConfigurer bean。我已经注册了AuthorizationDirective

public class AuthorizationDirective implements SchemaDirectiveWiring {

    @Override
    public GraphQLFieldDefinition onField(
            SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> wiringEnv) {
        // 获取当前数据提取器
        GraphQLFieldsContainer fieldsContainer = wiringEnv.getFieldsContainer();
        GraphQLFieldDefinition fieldDefinition = wiringEnv.getFieldDefinition();
        final DataFetcher<?> currentDataFetcher = wiringEnv
                .getCodeRegistry()
                .getDataFetcher(fieldsContainer, fieldDefinition);

        // 应用带有授权逻辑的数据提取器
        final DataFetcher<?> authorizingDataFetcher = buildAuthorizingDataFetcher(
                wiringEnv,
                currentDataFetcher);
        wiringEnv.getCodeRegistry()
                .dataFetcher(
                        fieldsContainer,
                        fieldDefinition,
                        authorizingDataFetcher);

        return fieldDefinition;
    }

    private DataFetcher<Object> buildAuthorizingDataFetcher(
            SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> wiringEnv,
            DataFetcher<?> currentDataFetcher) {
        return fetchingEnv -> {
            // 在此处实现逻辑
        };
    }
}

我迷失的地方是,如何从SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition>DataFetchingEnvironment对象中提取请求的字段和信息,这些对象在buildAuthorizingDataFetcher()函数中可用。我成功地从wiringEnv中提取了所有字段,通过执行类似于这样的广度优先遍历:

Queue<GraphQLSchemaElement> nodeQueue = new LinkedBlockingQueue<>(
        wiringEnv.getElement().getType().getChildren());

while (!nodeQueue.isEmpty()) {
    var node = nodeQueue.remove();
    if (GraphQLFieldDefinition.class.isAssignableFrom(node.getClass()))
        // 对GraphQL字段节点执行逻辑
        System.out.println(((GraphQLFieldDefinition) node).getName());
    nodeQueue.addAll(node.getChildren());
}

我还可以看到如何使用fetchingEnv执行类似的操作,但是,我不想要查询的所有字段,我只想要用户选择的字段。是否有一种方法可以访问这些信息?

编辑:
我找到了获取所有选择的字段的方法:

fetchingEnv.getSelection().getFields();

这会返回一个SelectedField的列表,这正是我想要的,然而,这些SelectedField对象缺乏有关指令的任何信息。

英文:

I have an authentication directive, used to restrict fields to certain authentication levels

directive @auth(role: [String!]!) on FIELD_DEFINITION

For example, with the following schema

type Query {
    test: TestResultType! @auth(role: ["USER", "ADMIN"]) 
}

type TestResultType {
    customer: Customer!
    seller: Seller!
}

type Customer {
    email: String!
    username: String!
    password: String! @auth(role: "ADMIN")
}

type Seller {
    brandName: String!
    email: String!
    username: String!
    password: String! @auth(role: "ADMIN")
}

The query test would be restricted to either "USER" or "ADMIN", and the password field of both Customer and Seller are restricted to only "ADMIN".

If I have the authorization level of "USER", but not "ADMIN", then the following query should go through just fine because I am not requesting anything that is protected with the @auth(role: "ADMIN") directive

query {
    test {
        customer {
            email
        }
        seller {
            brandName
            email
        }
    }
}

However, if I have the authorization level of "USER", but not "ADMIN", then the following query should return an error since I selected the password fields in the query, which is protected with the @auth(role: "ADMIN") directive

query {
    test {
        customer {
            email
            password
        }
        seller {
            brandName
            email
            password
        }
    }
}

To work with directives in Spring Boot GraphQL, I must register a SchemaDirectiveWiring with a RuntimeWiringConfigurer bean. I have registered AuthorizationDirective

public class AuthorizationDirective implements SchemaDirectiveWiring {

    @Override
    public GraphQLFieldDefinition onField(
            SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> wiringEnv) {
        // Get current data fetcher
        GraphQLFieldsContainer fieldsContainer = wiringEnv.getFieldsContainer();
        GraphQLFieldDefinition fieldDefinition = wiringEnv.getFieldDefinition();
        final DataFetcher<?> currentDataFetcher = wiringEnv
                .getCodeRegistry()
                .getDataFetcher(fieldsContainer, fieldDefinition);

        // Apply data fetcher with authorization logic
        final DataFetcher<?> authorizingDataFetcher = buildAuthorizingDataFetcher(
                wiringEnv,
                currentDataFetcher);
        wiringEnv.getCodeRegistry()
                .dataFetcher(
                        fieldsContainer,
                        fieldDefinition,
                        authorizingDataFetcher);

        return fieldDefinition;
    }

    private DataFetcher<Object> buildAuthorizingDataFetcher(
            SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> wiringEnv,
            DataFetcher<?> currentDataFetcher) {
        return fetchingEnv -> {
            // Implementation here
        };
    }
}

Where I am lost is, how do I extract the REQUESTED fields and information from either the SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> or DataFetchingEnvironment objects, that are available to me in the buildAuthorizingDataFetcher() function. I managed to extract ALL fields from wiringEnv by performing a breadth-first traversal like this:

Queue<GraphQLSchemaElement> nodeQueue = new LinkedBlockingQueue<>(
        wiringEnv.getElement().getType().getChildren());

while (!nodeQueue.isEmpty()) {
    var node = nodeQueue.remove();
    if (GraphQLFieldDefinition.class.isAssignableFrom(node.getClass()))
        // Perform logic on graphql field node
        System.out.println(((GraphQLFieldDefinition) node).getName());
    nodeQueue.addAll(node.getChildren());
}

And I could also see how I could do something similar with fetchingEnv, however, I don't want ALL fields of a query, I only want the ones selected by the user. Is there a way to access this information?

EDIT:
I found a way to get a list of all the selections:

fetchingEnv.getSelection().getFields();

This returns a list of SelectedField, which is exactly what I wanted, however, these SelectedField objects lack any information about directives.

答案1

得分: 0

I found a way to do it.

以下代码片段将返回一个类型为 List<SelectedField> 的对象

var selectionSet = fetchingEnv.getSelectionSet().getFields();

然后,您可以迭代此列表以从选择集中提取 List<GraphQLFieldDefinition> 对象。

var fieldDefs = selectionSet.stream()
        .flatMap(s -> s.getFieldDefinitions().stream())
        .toList()

最后,您可以从字段定义中提取 List<GraphQLDirective> 对象。

var directives = fieldDefs.stream()
        .map(f -> f.getDirective("name"))
        .filter(Objects::nonNull)
        .toList();

然后,您可以对需要的指令执行各种其他检查。

英文:

I found a way to do it.

The following code snippet will return an object of type List<SelectedField>

var selectionSet = fetchingEnv.getSelectionSet().getFields();

Then, you can iterate through this list to extract the List<GraphQLFieldDefinition> object from your selection set.

var fieldDefs = selectionSet.stream()
        .flatMap(s -> s.getFieldDefinitions().stream())
        .toList()

Finally, you can extract the List<GraphQLDirective> object from the field definitions.

var directives = fieldDefs.stream()
        .map(f -> f.getDirective("name"))
        .filter(Objects::nonNull)
        .toList();

And then you can perform all sorts of other checks on the directives that you need.

huangapple
  • 本文由 发表于 2023年6月29日 15:41:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76578978.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定