Как получить имя операции запроса / мутации

Я новичок в 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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
2 086
1

Ответы 1

Я думаю, вы здесь думаете об авторизации в терминах 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 покрывает это.

Спасибо @kaqqao за ответ. Дай мне попробовать.

Rahul 16.08.2018 12:26

В методе RoleBasedVisibility.isFieldAllowed DirectivesUtil.directiveWithArg(field.getDirectives(), "auth", "rolesRequired");. Как мы можем защитить поле, добавив директиву с «auth» и «rolesRequired»?

Rahul 22.08.2018 11:45

Привет, Kaqqao, хорошее решение, но мне не нравится перестраивать схему для каждого запроса. Вы думали об этом с тех пор, как ответили на это?

S.V. 16.04.2019 20:37

@ DrSatan1 К сожалению, GraphQLFieldVisibility входит в схему, поэтому для ее изменения необходимо преобразовать схему. Рассуждение можно найти здесь. Обратите внимание, что преобразование - это не перестройка и не дорогостоящая операция. Тем не менее, помните, что если вы используете Spring Security, ничего из этого не вызывает беспокойства, поскольку Spring хранит SecurityContext в ThreadLocal, который вы можете получить динамически из GraphQLFieldVisibility, поэтому преобразование не требуется.

kaqqao 16.04.2019 22:22

Тем, кто не нашел этот ответ через github.com/leangen/graphql-spqr/issues/…, вы должны знать, что сейчас лучше всего это сделать через ResolverInterceptor. Спасибо @kaqqao! См. github.com/leangen/graphql-spqr/issues/…

seanf 04.12.2019 06:13

Я забыл сказать: мой предыдущий комментарий был о подходе к безопасности, отличном от Spring, с использованием AuthInstrumentation, который сейчас лучше реализовать с помощью ResolverInterceptor, например AuthInterceptor.

seanf 04.12.2019 06:25

Кроме того, если вы используете graphql-java-servlet перед SPQR, AuthInterceptor может проверять роли напрямую, используя HttpServletRequest.isUserInRole(), потому что контекст выполнения (context.getResolutionEnvironment().dataFetchingEnvironment.‌​getContext()) - это GraphQLServletContext. Таким образом, нет необходимости создавать класс User - конечно, это означает, что перехватчик будет специфичным для HTTP-сервлетов.

seanf 04.12.2019 06:26

Другие вопросы по теме