GraphQL, как универсальный механизм запросов, открывает для клиента доступ к данным через единую точку входа — GraphQL API. Это упрощает архитектуру, но делает безопасность сложнее:
- Одиночная точка отказа. Если эта точка скомпрометирована, злоумышленник может получить доступ ко всей системе.
- Гибкость запросов. Пользователь может запрашивать слишком много данных (или слишком глубокие вложенности) за один запрос.
- Неограниченные мощности. GraphQL позволяет клиентам сами выбирать, сколько данных нужно, что может привести к злоупотреблению ресурсами сервера.
Соответственно, нам нужно защитить наши данные от несанкционированного доступа, предотвратить перегрузку сервера и обеспечить соблюдение политик доступов.
Потенциальные угрозы в GraphQL
- Денялы вне закона: например, атака на производительность, где клиент запрашивает огромные вложенные данные (или циклические запросы).
- Нарушение приватности данных: если пользователи могут получить доступ к данным, к которым они не имеют прав.
- Несанкционированные мутации: злоумышленник может попытаться изменить данные без авторизации.
- Инъекции через GraphQL-запросы: хотя GraphQL по своей природе защищает от SQL-инъекций, динамические запросы могут всё испортить.
Основные аспекты безопасности в GraphQL
- Аутентификация. Кто ты? Это процесс подтверждения личности пользователя.
- Авторизация. Что тебе можно делать? Это проверка прав для выполнения определённых действий.
- Ограничения запросов. Это инструменты, которые защищают сервер от перегрузки.
- Защита схемы. Устраняем утечки информации об архитектуре API.
Управление доступом в GraphQL
Основной подход — использование контекста (context) запроса. Контекст — это объект, который передаётся в каждый запрос. Он может содержать информацию о текущем пользователе, его роли, токене доступа и других параметрах.
Пример настройки контекста для GraphQL:
@Bean
public GraphQL graphQL(GraphQLSchema schema) {
return GraphQL.newGraphQL(schema)
.defaultDataFetcherExceptionHandler(new CustomExceptionHandler())
.instrumentation(new ContextSettingInstrumentation()) // Контекст для запросов
.build();
}
public class ContextSettingInstrumentation extends SimpleInstrumentation {
@Override
public InstrumentationContext<ExecutionInput> beginExecution(InstrumentationExecutionParameters parameters) {
ExecutionInput executionInput = parameters.getExecutionInput();
Map<String, Object> context = new HashMap<>();
context.put("currentUser", fetchCurrentUserFromSecurityContext()); // Добавляем пользователя
executionInput.transform(builder -> builder.context(context));
return super.beginExecution(parameters);
}
}
Аутентификация через JWT
Использование JWT (JSON Web Token) позволяет проверить, действительно ли пользователь имеет право на доступ.
1. Настраиваем SecurityContext в Spring Security:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
2. Проверяем пользователя в GraphQL Data Fetcher:
public DataFetcher<String> getSecretInfo() {
return environment -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !isUserAuthenticated(authentication)) {
throw new AccessDeniedException("Unauthorized");
}
return "Здесь ваш секретный контент!";
};
}
Защита данных и схемы
Ограничение глубины запросов
Одним из самых простых способов атак на GraphQL API является создание запросов с высокой вложенностью, например:
query {
user {
friends {
friends {
friends {
friends {
name
}
}
}
}
}
}
Для предотвращения этого используем библиотеку graphql-java-servlet, которая позволяет настраивать лимиты глубины запросов.
Пример добавления ограничения:
GraphQLSchema schema = SchemaGenerator.newBuilder()
.query(queryType)
.additionalDirective(graphql.schema.idl.SchemaPrinter.Options.defaultOptions().maxQueryDepth(10))
.build();
Ограничение сложности запросов
Сложность запроса — это сумма "весов" всех запрашиваемых полей. Чем больше вложенных данных запрашивается, тем выше сложность. Вес полей можно задать вручную:
public class CustomFieldComplexityCalculator implements FieldComplexityCalculator {
@Override
public int calculate(FieldComplexityEnvironment environment, int childComplexity) {
if ("user".equals(environment.getField().getName())) {
return 5 + childComplexity; // Вес поля "user" - 5
}
return 1 + childComplexity;
}
}
После этого конфигурируем GraphQL:
GraphQL.newGraphQL(schema)
.instrumentation(new MaxQueryComplexityInstrumentation(50)) // Лимит сложности запросов
.build();
Применение авторизации на уровне схемы
Используйте аннотации, чтобы ограничить доступ к определённым полям:
@GraphQLField
@PreAuthorize("hasRole('ADMIN')")
public String getAdminData() {
return "Только для администраторов!";
}
Spring Security при этом автоматически проверяет роль пользователя перед выполнением метода.
Практика: настройка безопасности GraphQL API
Шаг 1: Настройте Spring Security.
Добавьте зависимости в pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Шаг 2: Настройте JWT в Spring Boot.
Добавьте настройки в application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://your-issuer.com
Шаг 3: Добавьте проверку доступа в Data Fetchers.
Интегрируйте проверку прав пользователей в обработчики GraphQL-запросов:
public DataFetcher<String> getSecureInfo() {
return environment -> {
if (!environment.getContext().get("isAuthenticated")) {
throw new AccessDeniedException("Access Denied");
}
return "Секретные данные для авторизованных пользователей";
};
}
Шаг 4: Ограничьте глубину и сложность запросов.
Добавьте в конфигурацию GraphQL ограничения на глубину и сложность:
GraphQL.newGraphQL(schema)
.instrumentation(new MaxQueryDepthInstrumentation(10))
.instrumentation(new MaxQueryComplexityInstrumentation(50))
.build();
Заключение
Если бы безопасность была пирогом, то мы только что добавили достаточно ингредиентов, чтобы исполнить рецепт! Теперь ваш GraphQL API защищён от излишних запросов, несанкционированного доступа и возможных атак. Но помните, что безопасность — это не процесс типа "один раз сделал и забыл". Не забывайте регулярно пересматривать свои конфигурации, обновлять библиотеки и тестировать уязвимости.
Переходим к следующему шагу!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ