英文:
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"
,而Customer
和Seller
的password
字段仅限于"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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论