واجهات GraphQL

أمان GraphQL

18 دقيقة الدرس 27 من 35

أمان GraphQL

تعلم الممارسات الأمنية الأساسية لحماية واجهة برمجة تطبيقات GraphQL الخاصة بك من الثغرات والهجمات الشائعة.

هجمات تعقيد الاستعلام

يمكن استغلال مرونة GraphQL باستعلامات متداخلة بعمق أو مكلفة:

# استعلام ضار - يجلب كميات هائلة من البيانات query MaliciousQuery { users { posts { comments { author { posts { comments { author { posts { comments { id } } } } } } } } } }
تحذير: بدون حماية، يمكن أن يعيد هذا الاستعلام ملايين السجلات ويتسبب في تعطل الخادم. قم دائماً بتطبيق تحليل تعقيد الاستعلام.

الحد من العمق

حدد العمق الأقصى للاستعلامات لمنع الهجمات المتداخلة بعمق:

const depthLimit = require('graphql-depth-limit'); const { ApolloServer } = require('apollo-server'); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [depthLimit(5)], // عمق أقصى 5 مستويات }); // حد عمق مخصص برسالة خطأ const customDepthLimit = (maxDepth) => { return (context) => { return { Field(node) { if (node.selectionSet) { const depth = getDepth(node); if (depth > maxDepth) { context.reportError( new GraphQLError( `عمق الاستعلام ${depth} يتجاوز العمق الأقصى المسموح ${maxDepth}` ) ); } } }, }; }; };

تحديد المعدل

نفذ تحديد المعدل لمنع إساءة الاستخدام وهجمات DDoS:

const { ApolloServer } = require('apollo-server-express'); const rateLimit = require('express-rate-limit'); const express = require('express'); const app = express(); // محدد معدل عام const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 دقيقة max: 100, // حدد كل IP بـ 100 طلب لكل نافذة زمنية message: 'طلبات كثيرة جداً من هذا IP، يرجى المحاولة لاحقاً.', }); app.use('/graphql', limiter); // تحديد معدل لكل محلل const rateLimitDirective = { RateLimit: class extends SchemaDirectiveVisitor { visitFieldDefinition(field) { const { resolve = defaultFieldResolver } = field; const { max, window } = this.args; field.resolve = async function (...args) { const [, , context] = args; const key = `${context.user?.id || context.ip}:${field.name}`; const count = await redisClient.incr(key); if (count === 1) { await redisClient.expire(key, window); } if (count > max) { throw new Error('تم تجاوز حد المعدل'); } return resolve.apply(this, args); }; }, }, }; // الاستخدام في المخطط const typeDefs = gql` directive @rateLimit(max: Int!, window: Int!) on FIELD_DEFINITION type Query { expensiveQuery: Result @rateLimit(max: 10, window: 60) } `;

الفحص الذاتي في الإنتاج

عطّل استعلامات الفحص الذاتي في الإنتاج لإخفاء بنية المخطط الخاص بك:

const { ApolloServer } = require('apollo-server'); const server = new ApolloServer({ typeDefs, resolvers, introspection: process.env.NODE_ENV !== 'production', playground: process.env.NODE_ENV !== 'production', }); // تعطيل الفحص الذاتي المخصص const { NoIntrospection } = require('graphql-disable-introspection'); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [ process.env.NODE_ENV === 'production' ? NoIntrospection : null, ].filter(Boolean), });
ملاحظة: تعطيل الفحص الذاتي لا يوفر أماناً كاملاً من خلال الغموض، لكنه يرفع العارضة أمام المهاجمين ويمنع اكتشاف المخطط التلقائي.

تعقيم المدخلات

تحقق دائماً من صحة مدخلات المستخدم وعقّمها لمنع هجمات الحقن:

const validator = require('validator'); const resolvers = { Mutation: { createUser: async (_, { input }, { db }) => { // التحقق من البريد الإلكتروني if (!validator.isEmail(input.email)) { throw new Error('تنسيق بريد إلكتروني غير صالح'); } // تعقيم السلاسل النصية const sanitizedName = validator.escape(input.name); // التحقق من طول السلسلة if (sanitizedName.length < 2 || sanitizedName.length > 50) { throw new Error('يجب أن يكون الاسم بين 2 و 50 حرفاً'); } // التحقق من أنماط حقن SQL (إذا كنت تستخدم SQL الخام) const sqlInjectionPattern = /(\bOR\b|\bAND\b|;|--|\/\*|\*\/)/i; if (sqlInjectionPattern.test(input.name)) { throw new Error('تم اكتشاف مدخلات غير صالحة'); } // استخدم استعلامات مُعَلَّمَة return db.user.create({ data: { name: sanitizedName, email: input.email.toLowerCase(), }, }); }, }, };

منع CSRF

احمِ من هجمات تزوير الطلبات عبر المواقع:

const { ApolloServer } = require('apollo-server-express'); const csrf = require('csurf'); const express = require('express'); const app = express(); // تفعيل حماية CSRF const csrfProtection = csrf({ cookie: true }); // طلب رمز CSRF للطفرات const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => { // التحقق من رمز CSRF للطفرات if (req.body.query?.includes('mutation')) { csrfProtection(req, {}, (err) => { if (err) throw new Error('رمز CSRF غير صالح'); }); } return { req }; }, }); // استخدم CORS بشكل صحيح const corsOptions = { origin: ['https://yourdomain.com', 'https://app.yourdomain.com'], credentials: true, }; server.applyMiddleware({ app, cors: corsOptions });

أفضل ممارسات المصادقة

نفذ مصادقة وترخيص آمنين:

const jwt = require('jsonwebtoken'); const { AuthenticationError } = require('apollo-server'); // التحقق من رمز JWT const getUser = (token) => { try { if (token) { return jwt.verify(token, process.env.JWT_SECRET); } return null; } catch (error) { return null; } }; const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => { const token = req.headers.authorization?.replace('Bearer ', ''); const user = getUser(token); return { user }; }, }); // محلل محمي const resolvers = { Query: { me: (_, __, { user }) => { if (!user) { throw new AuthenticationError('يجب تسجيل الدخول'); } return user; }, }, Mutation: { deleteUser: async (_, { id }, { user, db }) => { if (!user) { throw new AuthenticationError('يجب تسجيل الدخول'); } if (user.role !== 'ADMIN' && user.id !== id) { throw new ForbiddenError('ليس لديك إذن'); } return db.user.delete({ where: { id } }); }, }, };
نصيحة: استخدم رموز وصول قصيرة الأجل (15 دقيقة) ورموز تحديث (7 أيام) مخزنة في ملفات تعريف ارتباط httpOnly للحصول على أمان أفضل.

تحليل تكلفة الاستعلام

احسب وحدد تكلفة الاستعلامات بناءً على التعقيد:

const { createComplexityLimitRule } = require('graphql-validation-complexity'); const complexityLimit = createComplexityLimitRule(1000, { // حدد تكاليف الحقول scalarCost: 1, objectCost: 10, listFactor: 10, // تكاليف حقول مخصصة onCost: (cost, { type, field }) => { if (field.name === 'expensiveField') { return 100; } return cost; }, }); const server = new ApolloServer({ typeDefs, resolvers, validationRules: [complexityLimit], });
تمرين:
  1. نفذ تحديد العمق بعمق أقصى 4 مستويات
  2. أضف تحديد المعدل إلى نقطة نهاية GraphQL (100 طلب لكل 15 دقيقة)
  3. عطّل الفحص الذاتي والملعب في وضع الإنتاج
  4. أنشئ التحقق من صحة المدخلات لطفرة تسجيل المستخدم
  5. نفذ مصادقة JWT مع التحقق من المستخدم القائم على السياق