ویرگول
ورودثبت نام
Sedali
Sedaliیه پسری که کل عمرش دنبال این بود که یه Div رو center کنه:)
Sedali
Sedali
خواندن ۷ دقیقه·۶ ماه پیش

چگونه با PHP یک RESTful API امن بسازیم؟


سلام رفیق! اگه دنبال اینی که یه RESTful API حرفه‌ای و امن با PHP بسازی که هم سریع باشه، هم قابل اعتماد و هم هکرها نتونن بهش نفوذ کنن، این مقاله دقیقاً همون چیزیه که لازم داری! تو این راهنما قراره قدم به قدم با زبون خودمونی و حرفه‌ای یاد بگیری چطور یه API درست و حسابی بسازی. از طراحی ساختار گرفته تا امنیت و تست، همه‌چیز رو پوشش می‌دم. پس بزن بریم که بترکونیم!

RESTful API چیه و چرا باید امن باشه؟

قبل از هر چیزی، یه توضیح سریع بدم که اصلاً RESTful API چیه. به زبان ساده، یه API (رابط برنامه‌نویسی کاربردی) راهیه که سیستم‌های مختلف (مثل اپ موبایل، وب‌سایت یا حتی یه دستگاه IoT) بتونن باهم حرف بزنن. REST (Representational State Transfer) یه سبک طراحی برای APIهاست که از HTTP استفاده می‌کنه و خیلی ساده و مقیاس‌پذیره. یه RESTful API معمولاً از متدهای HTTP مثل GET، POST، PUT و DELETE استفاده می‌کنه و داده‌ها رو با فرمت JSON یا XML رد و بدل می‌کنه.

حالا چرا امنیت مهمه؟ چون APIها دروازه ورود به داده‌های برنامه‌تن. اگه درست ایمنشون نکنی، هکرها می‌تونن اطلاعات کاربرات رو بدزدن، دیتابیس رو خراب کنن یا حتی سرور رو از کار بندازن. پس بیایم یه API بسازیم که هم کار کنه، هم خیالمون از امنیتش راحت باشه!

قدم‌های ساخت یه RESTful API امن با PHP

۱. آماده‌سازی محیط

قبل از شروع کد زدن، باید محیط کار رو آماده کنی:

  • PHP 8.3 یا بالاتر: پیشنهاد می‌کنم از آخرین نسخه PHP استفاده کنی چون هم سریع‌تره، هم امن‌تر.

  • وب‌سرور: Apache یا Nginx، هر کدوم که باهاش راحتی.

  • دیتابیس: MySQL یا PostgreSQL برای ذخیره داده‌ها.

  • Composer: برای مدیریت پکیج‌های PHP.

  • ابزار تست: مثل Postman یا Insomnia برای تست API.

یه پروژه جدید بساز و Composer رو نصب کن:

composer init

۲. طراحی ساختار API

یه API خوب باید ساختار منظم و قابل فهمی داشته باشه. فرض کن قراره یه API برای مدیریت کاربران (Users) بسازی که عملیات CRUD (ایجاد، خواندن، به‌روزرسانی، حذف) رو انجام بده. یه ساختار نمونه برای URLها می‌تونه اینجوری باشه:

متد HTTP مسیر (Endpoint) توضیحات GET /api/users لیست همه کاربران GET /api/users/{id} اطلاعات یه کاربر خاص POST /api/users ایجاد کاربر جدید PUT /api/users/{id} به‌روزرسانی کاربر DELETE /api/users/{id} حذف کاربر

نکته: همیشه یه پیشوند مثل /api برای مسیرها بذار که مشخص بشه این درخواست‌ها برای APIه.

۳. راه‌اندازی پروژه

برای ساده‌تر شدن کار، می‌تونیم از یه فریم‌ورک سبک مثل Slim یا Lumen استفاده کنیم. من اینجا از Slim استفاده می‌کنم چون خیلی سبکه و برای API عالیه. اول Slim رو نصب کن:

composer require slim/slim "^4.0"
composer require slim/psr7

یه فایل index.php بساز و کد زیر رو توش بذار:

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

$app = AppFactory::create();
$app->setBasePath('/api'); // پیشوند برای همه مسیرها

// یه مسیر نمونه
$app->get('/users', function (Request $request, Response $response) {
    $response->getBody()->write(json_encode(['message' => 'خوش اومدی به API کاربران!']));
    return $response->withHeader('Content-Type', 'application/json');
});

$app->run();

این کد یه API ساده با یه مسیر /api/users می‌سازه که یه پیام JSON برمی‌گردونه. حالا وب‌سرور رو اجرا کن (مثلاً با php -S localhost:8000) و تو Postman درخواست GET http://localhost:8000/api/users رو تست کن.

۴. اتصال به دیتابیس

برای کار با دیتابیس، از PDO استفاده می‌کنیم چون امن و قابل اعتماده. یه فایل config.php بساز برای تنظیمات دیتابیس:

<?php
return [
    'db' => [
        'host' => 'localhost',
        'dbname' => 'api_db',
        'user' => 'root',
        'pass' => '',
    ],
];

حالا یه کلاس ساده برای مدیریت دیتابیس بساز تو فایل Database.php:

<?php
class Database {
    private $pdo;

    public function __construct($config) {
        $dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset=utf8mb4"
        try {
            $this->pdo = new PDO($dsn, $config['user'], $config['pass'], [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);
        } catch (PDOException $e) {
            throw new Exception("اتصال به دیتابیس خراب شد: " . $e->getMessage());
        }
    }

    public function getPdo() {
        return $this->pdo;
    }
}

تو index.php از این کلاس استفاده کن:

$config = require 'config.php';
$db = new Database($config['db']);

۵. پیاده‌سازی عملیات CRUD

حالا بیایم یه نمونه کامل برای مدیریت کاربران پیاده کنیم. فرض کن جدول users تو دیتابیس اینجوریه:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

تو index.php مسیرهای CRUD رو اضافه کن:

// لیست کاربران
$app->get('/users', function (Request $request, Response $response) use ($db) {
    $stmt = $db->getPdo()->query('SELECT * FROM users');
    $users = $stmt->fetchAll();
    $response->getBody()->write(json_encode($users));
    return $response->withHeader('Content-Type', 'application/json');
});

// گرفتن یه کاربر
$app->get('/users/{id}', function (Request $request, Response $response, $args) use ($db) {
    $id = $args['id'];
    $stmt = $db->getPdo()->prepare('SELECT * FROM users WHERE id = ?');
    $stmt->execute([$id]);
    $user = $stmt->fetch();
    
    if (!$user) {
        $response->getBody()->write(json_encode(['error' => 'کاربر پیدا نشد']));
        return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
    }

    $response->getBody()->write(json_encode($user));
    return $response->withHeader('Content-Type', 'application/json');
});

// ایجاد کاربر
$app->post('/users', function (Request $request, Response $response) use ($db) {
    $data = $request->getParsedBody();
    if (empty($data['name']) || empty($data['email'])) {
        $response->getBody()->write(json_encode(['error' => 'نام و ایمیل الزامی است']));
        return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
    }

    $stmt = $db->getPdo()->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
    $stmt->execute([$data['name'], $data['email']]);

    $response->getBody()->write(json_encode(['message' => 'کاربر با موفقیت ساخته شد']));
    return $response->withStatus(201)->withHeader('Content-Type', 'application/json');
});

// به‌روزرسانی کاربر
$app->put('/users/{id}', function (Request $request, Response $response, $args) use ($db) {
    $id = $args['id'];
    $data = $request->getParsedBody();

    $stmt = $db->getPdo()->prepare('UPDATE users SET name = ?, email = ? WHERE id = ?');
    $stmt->execute([$data['name'] ?? null, $data['email'] ?? null, $id]);

    if ($stmt->rowCount() === 0) {
        $response->getBody()->write(json_encode(['error' => 'کاربر پیدا نشد']));
        return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
    }

    $response->getBody()->write(json_encode(['message' => 'کاربر به‌روزرسانی شد']));
    return $response->withHeader('Content-Type', 'application/json');
});

// حذف کاربر
$app->delete('/users/{id}', function (Request $request, Response $response, $args) use ($db) {
    $id = $args['id'];
    $stmt = $db->getPdo()->prepare('DELETE FROM users WHERE id = ?');
    $stmt->execute([$id]);

    if ($stmt->rowCount() === 0) {
        $response->getBody()->write(json_encode(['error' => 'کاربر پیدا نشد']));
        return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
    }

    $response->getBody()->write(json_encode(['message' => 'کاربر حذف شد']));
    return $response->withHeader('Content-Type', 'application/json');
});

۶. اضافه کردن امنیت

حالا که API کار می‌کنه، وقتشه حسابی ایمنش کنیم. چندتا روش مهم برای امنیت API:

الف) احراز هویت (Authentication)

برای اینکه فقط کاربران مجاز به API دسترسی داشته باشن، از JWT (JSON Web Token) استفاده می‌کنیم. پکیج firebase/php-jwt رو نصب کن:

composer require firebase/php-jwt

یه مسیر برای لاگین بساز که توکن JWT تولید کنه:

use Firebase\JWT\JWT;

$app->post('/login', function (Request $request, Response $response) use ($db) {
    $data = $request->getParsedBody();
    $email = $data['email'] ?? '';
    $password = $data['password'] ?? '';

    // فرض کن رمز تو دیتابیس هَش شده
    $stmt = $db->getPdo()->prepare('SELECT * FROM users WHERE email = ?');
    $stmt->execute([$email]);
    $user = $stmt->fetch();

    if ($user && password_verify($password, $user['password'])) {
        $secretKey = 'your_secret_key'; // اینو تو محیط امن نگه دار
        $payload = [
            'iat' => time(),
            'exp' => time() + 3600, // توکن ۱ ساعت معتبره
            'sub' => $user['id'],
        ];
        $jwt = JWT::encode($payload, $secretKey, 'HS256');

        $response->getBody()->write(json_encode(['token' => $jwt]));
        return $response->withHeader('Content-Type', 'application/json');
    }

    $response->getBody()->write(json_encode(['error' => 'ایمیل یا رمز اشتباهه']));
    return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
});

حالا یه میدل‌ور (Middleware) برای چک کردن توکن بساز:

function authMiddleware($request, $response, $next) {
    $authHeader = $request->getHeaderLine('Authorization');
    if (!$authHeader) {
        $response->getBody()->write(json_encode(['error' => 'توکن لازم است']));
        return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
    }

    $token = str_replace('Bearer ', '', $authHeader);
    try {
        $secretKey = 'your_secret_key';
        $decoded = JWT::decode($token, $secretKey, ['HS256']);
        $request = $request->withAttribute('user_id', $decoded->sub);
        return $next($request, $response);
    } catch (Exception $e) {
        $response->getBody()->write(json_encode(['error' => 'توکن نامعتبر']));
        return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
    }
}

// اضافه کردن میدل‌ور به مسیرها
$app->add(function ($request, $response, $next) {
    return authMiddleware($request, $response, $next);
});

ب) اعتبارسنجی ورودی‌ها

برای جلوگیری از تزریق SQL و حملات دیگه، همیشه ورودی‌ها رو اعتبارسنجی کن. مثلاً برای مسیر POST /users:

if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
    $response->getBody()->write(json_encode(['error' => 'ایمیل معتبر نیست']));
    return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
}

ج) محدود کردن نرخ درخواست (Rate Limiting)

برای جلوگیری از حملات DDoS، می‌تونی نرخ درخواست‌ها رو محدود کنی. پکیج bandwidth-throttle/token-bucket رو نصب کن:

composer require bandwidth-throttle/token-bucket

و یه میدل‌ور برای Rate Limiting بساز:

use BandwidthThrottle\TokenBucket\Rate;
use BandwidthThrottle\TokenBucket\TokenBucket;

function rateLimitMiddleware($request, $response, $next) {
    $bucket = new TokenBucket(10, new Rate(1, Rate::SECOND), new \BandwidthThrottle\TokenBucket\Storage\SessionStorage());
    if (!$bucket->consume(1)) {
        $response->getBody()->write(json_encode(['error' => 'بیش از حد درخواست زدی']));
        return $response->withStatus(429)->withHeader('Content-Type', 'application/json');
    }
    return $next($request, $response);
}

$app->add(function ($request, $response, $next) {
    return rateLimitMiddleware($request, $response, $next);
});

د) استفاده از HTTPS

همیشه API رو روی HTTPS اجرا کن تا داده‌ها رمزنگاری بشن. تو سرور (مثل Nginx یا Apache) یه گواهی SSL نصب کن.

ه) هدرهای امنیتی

هدرهای زیر رو به پاسخ‌ها اضافه کن:

$response = $response
    ->withHeader('X-Content-Type-Options', 'nosniff')
    ->withHeader('X-Frame-Options', 'DENY')
    ->withHeader('Content-Security-Policy', "default-src 'self'");

۷. تست API

قبل از اینکه API رو Deploy کنی، حسابی تستش کن:

  • تست دستی: از Postman استفاده کن و همه مسیرها رو با داده‌های مختلف تست کن.

  • تست خودکار: از PHPUnit برای نوشتن تست‌های Unit و Integration استفاده کن.

  • تست امنیتی: ابزارهایی مثل OWASP ZAP یا Burp Suite رو برای پیدا کردن حفره‌های امنیتی امتحان کن.

یه نمونه تست با PHPUnit:

use PHPUnit\Framework\TestCase;

class ApiTest extends TestCase {
    public function testGetUsers() {
        $client = new \GuzzleHttp\Client();
        $response = $client->get('http://localhost:8000/api/users');
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertJson($response->getBody());
    }
}

۸. مستندسازی

یه API خوب باید مستندات درست و حسابی داشته باشه. از ابزارهایی مثل Swagger یا OpenAPI استفاده کن. یه فایل openapi.yaml نمونه:

openapi: 3.0.0
info:
  title: Users API
  version: 1.0.0
paths:
  /users:
    get:
      summary: Get all users
      responses: 
        '200':
          description: List of users

۹. Deploy و مانیتورینگ

بعد از تست، API رو روی سرور Deploy کن (مثلاً با Docker یا Heroku). برای مانیتورینگ از ابزارهایی مثل New Relic یا Prometheus استفاده کن تا عملکرد و خطاها رو رصد کنی.

apirestful apiphp
۰
۰
Sedali
Sedali
یه پسری که کل عمرش دنبال این بود که یه Div رو center کنه:)
شاید از این پست‌ها خوشتان بیاید