
سلام رفیق! اگه دنبال اینی که یه 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 برای مدیریت کاربران (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']);حالا بیایم یه نمونه کامل برای مدیریت کاربران پیاده کنیم. فرض کن جدول 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:
برای اینکه فقط کاربران مجاز به 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');
}برای جلوگیری از حملات 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);
});همیشه 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 رو 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بعد از تست، API رو روی سرور Deploy کن (مثلاً با Docker یا Heroku). برای مانیتورینگ از ابزارهایی مثل New Relic یا Prometheus استفاده کن تا عملکرد و خطاها رو رصد کنی.