17 اسفند 1403

آموزش محدود کردن دسترسی به API جنگو
حتما شما هم بعد از یادگیری API نویسی متوجه شدهاید که تمامی افراد میتوانند API های شما را مشاهده کنند. در طراحی سایت، نباید امنیت تا حدی پایین باشد که افراد دیگر به محتواهای شما دسترسی داشته باشند یا درخواستهای HTTP به آن ارسال کنند. در ادامه با ما همراه باشید تا به شما نشان دهیم چگونه میتوانید در جنگو دسترسی به API های خود را برای عموم قطع کرده و امنیت خود را افزایش دهید.
پیش نیاز
این یک آموزش سطح بالا است.
اگر توسعهدهندهی بکاند هستید، باید با مفاهیم مربوط به ایجاد و کار با API در Django آشنا باشید.
اگر توسعهدهندهی فرانتاند هستید، باید حداقل با یک فریمورک فرانتاند آشنایی داشته باشید.
راه حل اول : محدود کردن منشع درخواست
در این روش، فقط از طریق دامنههای تعیین شده توسط شما میتوان به سرور درخواست ارسال کرد و مشاهده API ها از طریق مرورگر به هیچ وجه امکانپذیر نیست.
*مهم : راه اندازی این روش ساده است اما میتوان این محدودیت را دور زد. این روش برای درک بهتر موضوع آورده شده است راه حل دوم کاملا غیرقابل نفوذ است. *
تابع محدود کننده
ابتدا یک فایل پایتون به نام middleware.py در فولدر اپلیکیشنی که ایجاد کردید بسازید و کد های زیر رو در آن کپی میکنیم.
# --> yourapp/middleware.pyclass StrictAccessByOrigin:def __init__(self, get_response):self.get_response = get_responsedef __call__(self, request):allowed_origins = ['https://royalsite.org',"https://www.royalsite.org"]origin = request.META.get('HTTP_ORIGIN')if request.method in ['POST', 'GET'] and origin not in allowed_origins:root = ET.Element("response")ET.SubElement(root, "status").text = "Access Denied"ET.SubElement(root, "message").text = "You are not allowed to access this resource."xml_response = ET.tostring(root, encoding="utf-8", method="xml")return HttpResponse(xml_response, content_type='application/xml', status=403)response = self.get_response(request)return response
در متغیر allowed_origins، دامنهی مورد نظر که میخواهید اجازهی دسترسی داشته باشد را جایگزین کنید.
اضافه کردن middleware به تنظیمات
middleware ها به کد هایی گفته میشود که بین درخواست کلاینت و پاسخ سرور اجرا میشود.
# --> settings.pyMIDDLEWARE = [...'corsheaders.middleware.CorsMiddleware','yourapp.middleware.StrictAccessByOrigin']
نکته : در قسمت yourapp نام اپلیکیشنی که ایجاد کردید رو قرار بدهید.
راه حل دوم : استفاده از کلید API
در این روش، هم فرانتاند (Frontend) و هم بکاند (Backend) باید اقدامات لازم برای برقراری امنیت را انجام دهند که در ادامه به جزئیات آن خواهیم پرداخت. اگر وظیفه اجرای فرانتاند پروژه با شخص دیگری است، لینک این آموزش را برای آنها ارسال کنید تا با مفاهیم و اقدامات لازم آشنا شوند.
توضیحات تئوری
در این روش ابتدا باید یک مدل برای پایگاه داده تعریف کنیم که یک API Key در پایگاه داده ذخیره کند و همچنین همان کلید را باید در فرانتاند نگهداری کنیم.
API Key یا کلید API باید در هر درخواست HTTP که از سمت کلاینت به سرور ارسال میشود، قرار داده شود تا اعتبارسنجی انجام پذیرد.
*ذخیره و ارسال API Key در فرانتاند باید به صورت ServerSide باشد که در ادامه مفصل توضیح دادهایم.*
پیاده سازی بکاند
ساخت مدل برای پایگاه داده
# --> models.pyfrom django.db import modelsclass APIKey(models.Model):key = models.CharField(max_length=128, unique=True)created_at = models.DateTimeField(auto_now_add=True)def __str__(self):return self.key
در این middleware، درخواست ارسال شده بررسی میشود و در صورت عدم موفقیت در اعتبارسنجی، به کاربر اجازه دسترسی به دادهها داده نمیشود.
# --> middleware.pyimport xml.etree.ElementTree as ETfrom django.http import HttpResponsefrom .models import APIKeyclass APIKeyMiddleware:def __init__(self, get_response):self.get_response = get_responsedef __call__(self, request):api_key = request.headers.get('X-API-KEY')if not api_key or not APIKey.objects.filter(key=api_key).exists():root = ET.Element("response")ET.SubElement(root, "status").text = "403"ET.SubElement(root, "message").text = "Access Denied"xml_response = ET.tostring(root, encoding="utf-8", method="xml")return HttpResponse(xml_response, content_type='application/xml', status=403)return self.get_response(request)
اضافه کردن middleware به تنظیمات
# --> settings.pyMIDDLEWARE = [...'corsheaders.middleware.CorsMiddleware','yourapp.middleware.APIKeyMiddleware']
پیاده سازی فرانتاند
ما به هیچ وجه نباید API Key دریافتی را در کلاینت ذخیره کنیم یا از طریق درخواست های HTTP ارسال کنیم و گرنه افراد میتوانند به آن دسترسی پیدا کنند. خب چاره چیست ؟
کتابخانههای فرانتاند (کلاینتساید) مانند React و Vue به خودی خود میتوانند به صورت استاتیک دپلوی شوند. اما اگر نیاز به قابلیتهایی مانند Server-Side Rendering (SSR) باشد، باید از یک سرور مانند Express برای دپلوی استفاده کرد. همچنین، میتوان از فریمورکهایی مانند Next.js (برای React) یا Nuxt.js (برای Vue) استفاده کرد که قابلیت SSR را به صورت built-in ارائه میدهند.
*ما در این آموزش از Next.js استفاده میکنیم. اگر از فناوریهای دیگری استفاده میکنید، میتوانید نحوه ارسال درخواستهای HTTP از سمت سرور (ServerSide Requests) را جستوجو کنید*
ابتدا یک فایل env.local. در روت پروژه ایجاد میکنیم و API Key را در اون ذخیره میکنیم
// --> .env.localAPI_KEY=Your_API_KEY
فراخوانی API در سرور کامپوننت (GET)
درخواستهایی که با متد GET ارسال میشوند، تا حد امکان باید به این صورت انجام شوند.
// --> app/page.jsconst handler = async ()=>{const response = await fetch("https://yourbackenddomain.com/api/endpoint",{method:"GET",headers:{"X-API-KEY":process.env.API_KEY||""}})const result = await response.json();return result}const Home = async ()=>{const result = await handler();// مقادیر دریافت شده از سرور در result}export default Home;
فراخوانی API در کلاینت کامپوننت (POST)
کدهای داخل پوشهی api هرگز در مرورگر کاربر اجرا نمیشوند، بنابراین میتوانید عملیاتهای حساس مانند استفاده از کلیدهای API را به صورت امن انجام دهید.
// --> pages/api/fetchData.jsexport default async function handler(req, res) {if (req.method === "POST"){const apiKey = process.env.API_KEY;const response = await fetch('https://yourbackenddomain.com/api/endpoint', {method: 'POST',headers: {'X-API-KEY': apiKey,},body:req.body});const data = await response.json();res.status(200).json(data);}else{res.status(405).json({ message: 'Method Not Allowed' });}}
در قطعه کد زیر، نحوهی فراخوانی API Routes سرور-ساید (Server-Side) از سمت کلاینت-ساید (Client-Side) را مشاهده میکنید.
// --> app/index.js'use client'import { useEffect, useState } from 'react';export default function Home() {const [data, setData] = useState(null);useEffect(() => {async function fetchData() {const response = await fetch('/api/fetchData',{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({"data":"some data"})});const result = await response.json();setData(result);}fetchData();}, []);return (<div><h1>Data from Backend</h1>{data && <div>{JSON.stringify(data, null, 2)}</div>}</div>);}
نتیجه گیری
شما در این آموزش با مفاهیم محدود سازی API آشنا شدید. اینک میتوانید با بهرهگیری از دانش برنامهنویسی خود، این مفاهیم را بسط داده و به طور عملی پیادهسازی کنید. با استفاده از این مهارتها، قادر خواهید بود پروژههای پیچیدهتر و کاربردیتری را توسعه داده و به دنیای برنامهنویسی قدمی محکمتر بردارید.
اگر در هر بخشی از این آموزش به نکتهای برخورد کردید که برایتان نامفهوم یا نیازمند توضیح بیشتر است، خواهشمندیم که با ارسال تیکت به ما، پیشنهاد یا سوالتان را مطرح کنید.