GraphQL نحوه دریافت داده در برنامههای مدرن را متحول کرده است و به مشتریان کنترل دقیقی بر درخواستهای خود میدهد. با این حال، این انعطافپذیری با یک هزینه امنیتی قابل توجه همراه است: امکان وقوع حملات رد سرویس (DoS) از طریق پرسوجوهای بیش از حد پیچیده. برخلاف APIهای REST که نقاط پایانی از پیش تعریفشده دارند، GraphQL اجازه میدهد یک نقطه پایانی تقریباً هر ساختار پرسوجویی را اجرا کند که آن را به هدفی اصلی برای بازیگران مخرب تبدیل میکند که به دنبال تخلیه منابع سرور هستند.
در این پست، ما دو استراتژی قوی برای کاهش این خطرات را بررسی خواهیم کرد: اعمال محدودیتهای عمق پرسوجو و پیادهسازی تحلیل هزینه متغیر. این تکنیکها تضمین میکنند که API شما تحت بار کاری، پاسخگو و امن باقی بماند.
خطر پرسوجوهای بدون محدودیت
یک پرسوجوی ساده را در نظر بگیرید که دادههای کاربر را دریافت میکند. اگر طرحواره (Schema) اجازه روابط بازگشتی را بدهد (برای مثال، یک کاربر دوستان زیادی دارد و هر دوست نیز یک کاربر است)، مهاجم میتواند یک پرسوجوی تو در تو و عمیق طراحی کند. به جای دریافت ده کاربر، آنها ممکن است ده دوست را درخواست کنند، سپس ده دوست برای هر یک از آنها، که منجر به رشد نمایی در تعداد ضربات پایگاه داده و مصرف حافظه میشود. این مورد اغلب به عنوان حمله «پرسوجوی عمیق» شناخته میشود.
علاوه بر این، دریافت مقادیر زیادی داده در یک درخواست میتواند پهنای باند شبکه و مخازن اتصال پایگاه داده را اشباع کند. برای مقابله با این موضوع، نیاز داریم که ترمزهای ایمنی را پیادهسازی کنیم که ساختار پرسوجوی ورودی را قبل از تماس با منطق کسبوکار شما تحلیل کنند.
استراتژی ۱: اعمال محدودیت عمق پرسوجو
محدودیت عمق پرسوجو، میزان تو در تو بودن یک عملیات را محدود میکند. برای مثال، ممکن است تصمیم بگیرید که حداکثر عمق ۵ برای برنامه شما قابل قبول است. هر پرسوجویی که از این عمق فراتر رود، با یک پیام خطای واضح رد خواهد شد.
بسیاری از سرورهای GraphQL میانافزارها یا پلاگینهایی برای مدیریت این موضوع ارائه میدهند. در زیر نمونهای با استفاده از بسته محبوب graphql-depth-limit همراه با یک سرور Express آورده شده است.
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const depthLimit = require('graphql-depth-limit');
const schema = require('./schema');
const app = express();
app.use('/graphql', graphqlHTTP((req) => ({
schema: schema,
graphiql: true,
validationRules: [
depthLimit(5, 3, ['*'], (depth, paths) => {
// اختیاری: ثبت عمیقترین مسیرها برای نظارت
console.log('Query depth exceeded:', depth);
}),
],
})));
app.listen(4000, () => console.log('Server running on http://localhost:4000'));
در این پیکربندی، آرایه validationRules تضمین میکند که هر پرسوجو قبل از اجرا با محدودیت عمق بررسی شود. آرگومان سوم ['*'] این قانون را به تمام انواع پرسوجو اعمال میکند، در حالی که فراخوانی (callback) به شما امکان میدهد ناهنجاریها را برای نظارت امنیتی ثبت کنید.
استراتژی ۲: تحلیل هزینه متغیر
در حالی که محدودیت عمق از تو در تو شدن عمیق جلوگیری میکند، اما هزینه حل تکتک فیلدها را در نظر نمیگیرد. یک فیلد واحد ممکن است یک پیوند پایگاه داده سنگین یا یک تماس با API خارجی را فعال کند. تحلیل هزینه به هر فیلد بر اساس پیچیدگی محاسباتی آن «هزینه» اختصاص میدهد. وقتی یک پرسوجو دریافت میشود، سرور هزینه کل را محاسبه کرده و در صورتی که از بودجه تعریفشده فراتر رود، آن را رد میکند.
این رویکرد ظریفتر از محدودیت عمق است. برای مثال، یک پرسوجوی تخت که ۱۰,۰۰۰ شناسه کاربر را دریافت میکند ممکن است عمقی برابر با ۱ داشته باشد اما هزینهای عظیم، در حالی که یک پرسوجوی تو در تو با عمق ۳ و فیلدهای سبک ممکن است ناچیز باشد.
در اینجا نحوه پیادهسازی یک میانافزار ساده تحلیل هزینه در Node.js آورده شده است:
const { convertToCost } = require('graphql-query-complexity');
// تعریف هزینهها برای رزولورهای شما
const fieldConfig = {
User: {
friends: { cost: 10 },
posts: { cost: 5 },
},
Post: {
comments: { cost: 15 },
},
};
app.use('/graphql', graphqlHTTP((req) => ({
schema: schema,
validationRules: [
convertToCost({
schema,
maximumComplexity: 1000,
variables: {},
onComplete: (complexity) => {
console.log('Query complexity:', complexity);
},
createError(max, actual) {
return new Error(`Query is too complex: ${actual}. Maximum allowed complexity: ${max}`);
},
}),
],
})));
نتیجهگیری
پیادهسازی محدودیت نرخ (Rate Limiting) برای GraphQL یک راهحل یکاندازه برای همه نیست. در حالی که محدودیت عمق یک دفاع سریع در برابر حملات بازگشتی فراهم میکند، تحلیل هزینه رویکردی دقیقتر برای مدیریت مصرف منابع ارائه میدهد. با ترکیب هر دو استراتژی، شما یک لایه دفاعی مقاوم ایجاد میکنید که از زیرساخت شما محافظت میکند در حالی که انعطافپذیری قدرتمند GraphQL را حفظ میکند.
به یاد داشته باشید که به طور منظم لاگهای API خود را نظارت کنید. همانطور که طرحواره شما تکامل مییابد، محدودیتهای شما نیز باید تغییر کنند. حسابرسیهای منظم از تخصیصهای هزینه و آستانههای عمق تضمین میکند که برنامه شما امن، کارآمد و آماده برای مقیاسپذیری باقی بماند.