在GraphQL Spring Boot中如何获取查询选中字段的所有指令?
问题:GraphQL @auth指令如何校验用户请求字段的权限
指令定义
directive @auth(role: [String!]!) on FIELD_DEFINITION
示例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") }
需求说明
当用户权限为USER而非ADMIN时,未请求password字段的查询可正常执行;若请求password字段,查询应返回权限错误。
已实现的授权指令代码
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 }; } }
困惑点
- 如何在
buildAuthorizingDataFetcher方法中提取用户选中的字段及其@auth指令信息? - 已尝试通过
fetchingEnv.getSelection().getFields()获取选中字段,但返回的SelectedField对象不包含指令信息,该如何解决?
解决方案
1. 关联SelectedField与Schema中的字段定义
SelectedField本身不存储指令数据,但可以通过它的getFieldDefinition()方法获取对应的GraphQLFieldDefinition,后者包含该字段上定义的所有指令信息。
2. 递归遍历选中字段并校验权限
在buildAuthorizingDataFetcher中,递归遍历查询的选中字段树,对每个字段检查是否存在@auth指令,再对比当前用户角色是否符合要求。
修改后的buildAuthorizingDataFetcher实现示例:
private DataFetcher<Object> buildAuthorizingDataFetcher( SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> wiringEnv, DataFetcher<?> currentDataFetcher) { return fetchingEnv -> { // 获取当前用户角色(替换为你的实际认证逻辑) Set<String> userRoles = getCurrentUserRoles(fetchingEnv); // 校验当前选中字段及子字段的权限 checkSelectedFieldsAuthorization(fetchingEnv.getSelectionSet(), userRoles); // 执行原数据获取逻辑 return currentDataFetcher.get(fetchingEnv); }; } private void checkSelectedFieldsAuthorization(SelectionSet selectionSet, Set<String> userRoles) { for (Selection selection : selectionSet.getSelections()) { if (selection instanceof FieldSelection) { FieldSelection fieldSelection = (FieldSelection) selection; SelectedField selectedField = fieldSelection.getField(); GraphQLFieldDefinition fieldDef = selectedField.getFieldDefinition(); // 检查字段是否带有@auth指令 Directive authDirective = fieldDef.getDirective("auth"); if (authDirective != null) { List<String> requiredRoles = authDirective.getArgument("role").getValue(List.class); // 校验用户是否拥有至少一个所需角色 boolean hasPermission = requiredRoles.stream().anyMatch(userRoles::contains); if (!hasPermission) { throw new GraphQLException(String.format("无权访问字段: %s", selectedField.getName())); } } // 递归校验子字段权限 if (selectedField.getSelectionSet() != null) { checkSelectedFieldsAuthorization(selectedField.getSelectionSet(), userRoles); } } } } // 示例:从上下文获取用户角色的方法 private Set<String> getCurrentUserRoles(DataFetchingEnvironment env) { // 替换为实际获取角色逻辑,比如从SecurityContextHolder或请求上下文 return Set.of("USER"); }
3. 注意事项
- 需要自定义异常类(如
GraphQLException)并配置GraphQL异常处理器,将权限错误转为标准的GraphQL响应格式。 - 根字段(如Query中的
test)的权限校验可直接在当前指令逻辑中处理,无需在子字段遍历中重复校验。 - 递归遍历需覆盖所有嵌套字段(如
customer.password),确保所有选中字段都经过权限校验。
内容的提问来源于stack exchange,提问作者the thinker
相关产品推荐
相关产品推荐

