GraphQL, як універсальний механізм запитів, відкриває клієнту доступ до даних через єдину точку входу — GraphQL API. Це спрощує архітектуру, але ускладнює безпеку:
- Єдина точка відмови. Якщо ця точка скомпрометована, зловмисник може отримати доступ до всієї системи.
- Гнучкість запитів. Користувач може запитувати надто багато даних (або надто глибокі вкладення) в одному запиті.
- Необмежені ресурси. GraphQL дозволяє клієнтам самим обирати, скільки даних потрібно, що може призвести до зловживання ресурсами сервера.
Відповідно, нам потрібно захистити наші дані від несанкціонованого доступу, запобігти перевантаженню сервера і забезпечити дотримання політик доступу.
Потенційні загрози в GraphQL
- Відмови в обслуговуванні (DoS): наприклад, атака на продуктивність, коли клієнт запитує величезні вкладені дані (або циклічні запити).
- Порушення конфіденційності даних: якщо користувачі можуть отримати доступ до даних, до яких вони не мають прав.
- Несанкціоновані мутації: зловмисник може спробувати змінити дані без авторизації.
- Ін'єкції через 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 захищений від надмірних запитів, несанкціонованого доступу та можливих атак. Але пам'ятайте, що безпека — це не процес "зробив один раз і забув". Регулярно переглядайте свої конфігурації, оновлюйте бібліотеки та тестуйте на вразливості.
Переходимо до наступного кроку!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ