Я новичок в Spring boot + GraphQL. Мне нужно получить имя операции запроса / мутации внутри моего класса контроллера.
Цель : Требуется грандиозное разрешение для некоторых пользователей на определенные операции изменения / запроса. Здесь тип пользователя будет передан как заголовок запроса и будет проверен и проверит, разрешен ли пользователю доступ к этой операции.
@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




Я думаю, вы здесь думаете об авторизации в терминах REST, и это плохо согласуется с GraphQL. Вместо единственного решения на верхнем уровне, основанного на имени операции (или на основе URL-адреса в REST), вам нужен более детальный подход. Вам необходимо знать, кому разрешено видеть / делать что на полевом уровне, поскольку клиенту разрешено делать выборочный выбор.
Есть несколько способов сделать это, но, поскольку вы упомянули Spring, вы можете просто использовать Spring Security на уровне обслуживания. Если каждое защищенное поле поддерживается методом службы (а так и должно быть), вы можете защитить эти методы, используя Spring Security, как обычно.
Более того, вы также должны предоставить настраиваемую реализацию GraphqlFieldVisibility, чтобы неавторизованные клиенты не могли даже знать о существование полей, которые им не разрешено видеть в схеме. Вы можете использовать, например, SpelExpressionParser Spring для принятия решений о том, какие части схемы видны динамически для каждого пользователя на основе правил безопасности Spring.
Если 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 покрывает это.
В методе RoleBasedVisibility.isFieldAllowed DirectivesUtil.directiveWithArg(field.getDirectives(), "auth", "rolesRequired");. Как мы можем защитить поле, добавив директиву с «auth» и «rolesRequired»?
Привет, Kaqqao, хорошее решение, но мне не нравится перестраивать схему для каждого запроса. Вы думали об этом с тех пор, как ответили на это?
@ DrSatan1 К сожалению, GraphQLFieldVisibility входит в схему, поэтому для ее изменения необходимо преобразовать схему. Рассуждение можно найти здесь. Обратите внимание, что преобразование - это не перестройка и не дорогостоящая операция. Тем не менее, помните, что если вы используете Spring Security, ничего из этого не вызывает беспокойства, поскольку Spring хранит SecurityContext в ThreadLocal, который вы можете получить динамически из GraphQLFieldVisibility, поэтому преобразование не требуется.
Тем, кто не нашел этот ответ через github.com/leangen/graphql-spqr/issues/…, вы должны знать, что сейчас лучше всего это сделать через ResolverInterceptor. Спасибо @kaqqao! См. github.com/leangen/graphql-spqr/issues/…
Я забыл сказать: мой предыдущий комментарий был о подходе к безопасности, отличном от Spring, с использованием AuthInstrumentation, который сейчас лучше реализовать с помощью ResolverInterceptor, например AuthInterceptor.
Кроме того, если вы используете graphql-java-servlet перед SPQR, AuthInterceptor может проверять роли напрямую, используя HttpServletRequest.isUserInRole(), потому что контекст выполнения (context.getResolutionEnvironment().dataFetchingEnvironment.getContext()) - это GraphQLServletContext. Таким образом, нет необходимости создавать класс User - конечно, это означает, что перехватчик будет специфичным для HTTP-сервлетов.
Спасибо @kaqqao за ответ. Дай мне попробовать.