فصل ۷: امنیت و بهترین شیوه‌ها

در این فصل، به بررسی جنبه‌های امنیتی و بهترین شیوه‌ها برای استفاده از Laravel Reverb در نسخه ۱۲ لاراول می‌پردازیم. موضوعات کلیدی شامل حفاظت از کانال‌ها در برابر حملات با استفاده از authorization callbacks، مدیریت rate limiting برای اتصالات WebSocket، رمزنگاری پیام‌ها و جلوگیری از حملات CSRF در ارتباطات real-time، و دیباگ اتصالات با استفاده از flag --debug و لاگ‌گیری است. این فصل بر اساس مستندات رسمی لاراول ۱۲.x تنظیم شده و شامل مثال‌های عملی و نکات کاربردی برای محیط‌های تولید است.

حفاظت از کانال‌ها در برابر حملات (Authorization Callbacks)

کانال‌های خصوصی و حضور در Laravel Reverb نیازمند احراز هویت هستند تا اطمینان حاصل شود که فقط کاربران مجاز به آن‌ها دسترسی دارند. این کار از طریق تعریف authorization callbacks در فایل routes/channels.php انجام می‌شود. این callback‌ها بررسی می‌کنند که آیا کاربر اجازه دسترسی به یک کانال خاص را دارد یا خیر، و از حملاتی مانند دسترسی غیرمجاز (unauthorized access) جلوگیری می‌کنند.

تنظیم Authorization Callbacks

برای مثال، فرض کنید می‌خواهید یک کانال خصوصی برای نوتیفیکیشن‌های کاربر و یک کانال حضور برای یک اتاق چت تنظیم کنید. فایل routes/channels.php را به این صورت ویرایش کنید:

<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('user.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Broadcast::channel('chat-room', function ($user) {
    // بررسی نقش کاربر (مثلاً فقط کاربران با نقش 'member' یا بالاتر)
    return $user->hasRole('member') ? ['id' => $user->id, 'name' => $user->name] : false;
});
  • user.{id}: فقط کاربری که id او با شناسه کانال مطابقت دارد، می‌تواند subscribe کند.
  • chat-room: فقط کاربران با نقش member می‌توانند به کانال حضور subscribe کرده و اطلاعات خود (مانند id و name) را به اشتراک بگذارند.

نکات امنیتی

  • بررسی دقیق مجوزها: از منطق پیچیده‌تر (مانند بررسی نقش‌ها یا سیاست‌ها) در callback‌ها استفاده کنید. مثلاً:
  Broadcast::channel('project.{projectId}', function ($user, $projectId) {
      return $user->hasAccessToProject($projectId);
  });
  • استفاده از Policy‌ها: برای پروژه‌های بزرگ، از Laravel Authorization Policies استفاده کنید:
  Broadcast::channel('project.{projectId}', function ($user, $projectId) {
      return $user->can('view', \App\Models\Project::findOrFail($projectId));
  });
  • محدود کردن داده‌ها: در کانال‌های حضور، فقط اطلاعات ضروری (مانند id و name) را برگردانید و از ارسال داده‌های حساس (مانند ایمیل یا رمز عبور) خودداری کنید.

مدیریت Rate Limiting برای اتصالات WebSocket

Rate limiting برای جلوگیری از سوءاستفاده از سرور Reverb (مانند حملات DoS یا اسپم کردن اتصالات) ضروری است. لاراول از middleware داخلی برای rate limiting پشتیبانی می‌کند، و Reverb امکان تنظیم محدودیت‌های اتصال را فراهم می‌کند.

تنظیم Rate Limiting

  1. محدودیت در سطح اپلیکیشن: در فایل config/reverb.php، می‌توانید حداکثر تعداد اتصالات برای هر اپلیکیشن را با استفاده از پارامتر capacity محدود کنید:
<?php

return [
    'apps' => [
        [
            'app_id' => env('REVERB_APP_ID', 'my-reverb-app'),
            'key' => env('REVERB_APP_KEY', 'your-app-key'),
            'secret' => env('REVERB_APP_SECRET', 'your-app-secret'),
            'capacity' => 100, // حداکثر 100 اتصال همزمان
            'allowed_origins' => ['example.com'],
        ],
    ],
    'host' => env('REVERB_HOST', 'localhost'),
    'port' => env('REVERB_PORT', 8080),
    'scheme' => env('REVERB_SCHEME', 'http'),
];
  1. Rate Limiting در سطح HTTP: برای endpointهای احراز هویت (مانند /broadcasting/auth)، از middleware throttle لاراول استفاده کنید. فایل app/Http/Kernel.php را ویرایش کنید:
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middlewareGroups = [
        'web' => [
            // ...
            \Illuminate\Routing\Middleware\ThrottleRequests::class . ':60,1', // 60 درخواست در دقیقه
        ],
    ];
}
  • این تنظیم، درخواست‌های ارسالی به endpointهای وب را به 60 درخواست در دقیقه محدود می‌کند.

  • Rate Limiting در سطح WebSocket: برای محدود کردن تعداد پیام‌های WebSocket، می‌توانید از پکیج laravel-rate-limiter یا یک middleware سفارشی استفاده کنید. مثال middleware سفارشی:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

class LimitWebSocketMessages
{
    public function handle($request, Closure $next)
    {
        $key = 'websocket:' . $request->user()->id;
        $maxAttempts = 100; // حداکثر 100 پیام در ساعت
        $decayMinutes = 60;

        if (RateLimiter::tooManyAttempts($key, $maxAttempts)) {
            return response()->json(['error' => 'Too many messages'], Response::HTTP_TOO_MANY_REQUESTS);
        }

        RateLimiter::hit($key, $decayMinutes * 60);

        return $next($request);
    }
}

این middleware را در فایل app/Http/Kernel.php ثبت کنید:

protected $routeMiddleware = [
    // ...
    'limit.websocket' => \App\Http\Middleware\LimitWebSocketMessages::class,
];

سپس آن را به route‌های ارسال پیام اعمال کنید:

Route::middleware(['auth', 'limit.websocket'])->post('/send-message', [MessageController::class, 'send']);

رمزنگاری پیام‌ها و جلوگیری از CSRF در Real-Time

رمزنگاری پیام‌ها

برای اطمینان از امنیت داده‌ها در ارتباطات real-time: - استفاده از WSS (WebSocket Secure): همان‌طور که در فصل سوم توضیح داده شد، همیشه از پروتکل wss:// (SSL/TLS) در محیط تولید استفاده کنید. این کار تمام داده‌های ارسالی بین سرور و کلاینت را رمزنگاری می‌کند. - رمزنگاری داده‌های حساس: اگر داده‌های حساس (مانند اطلاعات شخصی) ارسال می‌کنید، آن‌ها را قبل از broadcasting رمزنگاری کنید. مثال:

<?php

namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Crypt;

class EncryptedMessage implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $encryptedMessage;
    public $userId;

    public function __construct($message, $userId)
    {
        $this->encryptedMessage = Crypt::encryptString($message);
        $this->userId = $userId;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('user.' . $this->userId);
    }

    public function broadcastAs()
    {
        return 'encrypted.message';
    }
}

در سمت کلاینت، پیام را رمزگشایی کنید (نیاز به کلید رمزنگاری مشترک):

window.Echo.private('user.1')
    .listen('.encrypted.message', (e) => {
        // فرض کنید کلید رمزنگاری در کلاینت موجود است
        const decrypted = decrypt(e.encryptedMessage); // پیاده‌سازی رمزگشایی
        console.log('Decrypted message:', decrypted);
    });

جلوگیری از CSRF

برای جلوگیری از حملات CSRF در درخواست‌های HTTP که پیام‌ها را به سرور ارسال می‌کنند:

  1. استفاده از CSRF Token: لاراول به‌صورت خودکار CSRF token را برای فرم‌های وب فراهم می‌کند. در فرم‌های خود، از دایرکتیو @csrf استفاده کنید:
   <form method="POST" action="/send-message">
       @csrf
       <input type="text" name="message">
       <button type="submit">Send</button>
   </form>
  1. برای درخواست‌های AJAX: توکن CSRF را در هدرهای درخواست اضافه کنید:
   $.ajaxSetup({
       headers: {
           'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
       }
   });

و در فایل blade، متا تگ CSRF را اضافه کنید:

   <meta name="csrf-token" content="{{ csrf_token() }}">

دیباگ اتصالات با --debug Flag و لاگ‌گیری

برای عیب‌یابی مشکلات مربوط به اتصالات WebSocket، Reverb ابزارهای مفیدی مانند flag --debug و لاگ‌گیری ارائه می‌دهد.

استفاده از --debug Flag

برای مشاهده لاگ‌های دقیق‌تر هنگام اجرای سرور Reverb:

php artisan reverb:start --debug

این دستور اطلاعاتی مانند اتصال/قطع اتصال کلاینت‌ها، پیام‌های ارسالی، و خطاها را در ترمینال نمایش می‌دهد. مثال خروجی:

[2025-09-30 08:00:00] Connection established: client_id=12345
[2025-09-30 08:00:01] Subscribed to channel: chat-room
[2025-09-30 08:00:02] Message sent: {"event":"chat.message","data":"Hello!"}

تنظیم لاگ‌گیری

برای ذخیره لاگ‌ها در فایل، از سیستم لاگ‌گیری لاراول استفاده کنید. فایل app/Providers/EventServiceProvider.php را ویرایش کنید تا لاگ‌های مربوط به broadcasting را ثبت کنید:

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Log;

class EventServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Event::listen('Illuminate\Broadcasting\Events\BroadcastEvent', function ($event) {
            Log::info('Broadcast event dispatched', [
                'event' => get_class($event->event),
                'data' => $event->event->broadcastWith(),
                'channel' => $event->event->broadcastOn(),
            ]);
        });
    }
}

لاگ‌ها در فایل storage/logs/laravel.log ذخیره می‌شوند. مثال خروجی:

[2025-09-30 08:00:02] local.INFO: Broadcast event dispatched {"event":"App\\Events\\ChatMessage","data":{"message":"Hello!"},"channel":"chat-room"}

ابزارهای دیباگ اضافی

  • wscat: برای تست اتصال WebSocket:
  wscat -c ws://localhost:8080/app/your-app-key?protocol=7&client=js
  • Laravel Telescope: برای نظارت بر رویدادهای broadcast و درخواست‌ها:
  composer require laravel/telescope
  php artisan telescope:install
  php artisan migrate
  • Browser DevTools: تب Network در مرورگر (فیلتر WebSocket) برای مشاهده پیام‌ها و خطاها.

مثال عملی: سیستم چت امن با Rate Limiting و لاگ‌گیری

در این مثال، سیستم چت فصل ششم را ارتقا می‌دهیم تا شامل احراز هویت، rate limiting، و لاگ‌گیری باشد.

مرحله ۱: تنظیم Rate Limiting

middleware LimitWebSocketMessages را (مانند بالا) به route ارسال پیام اضافه کنید:

<?php

use App\Events\ChatMessage;
use App\Models\Message;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('chat');
});

Route::middleware(['auth', 'limit.websocket'])->post('/send-message', function () {
    $message = request('message');
    Message::create([
        'user_id' => auth()->id(),
        'content' => $message
    ]);
    event(new ChatMessage($message, auth()->user()));
    return response()->json(['status' => 'Message sent']);
});

مرحله ۲: رمزنگاری پیام‌ها

فایل app/Events/ChatMessage.php را برای رمزنگاری پیام‌ها به‌روزرسانی کنید:

<?php

namespace App\Events;

use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Crypt;

class ChatMessage implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $encryptedMessage;
    public $user;

    public function __construct($message, $user)
    {
        $this->encryptedMessage = Crypt::encryptString($message);
        $this->user = $user;
    }

    public function broadcastOn()
    {
        return new PresenceChannel('chat-room');
    }

    public function broadcastAs()
    {
        return 'chat.message';
    }

    public function broadcastWith()
    {
        return [
            'encryptedMessage' => $this->encryptedMessage,
            'user' => ['id' => $this->user->id, 'name' => $this->user->name],
            'timestamp' => now()->toDateTimeString()
        ];
    }
}

مرحله ۳: تنظیم Frontend با رمزگشایی

فایل resources/js/components/Chat.vue را برای رمزگشایی پیام‌ها به‌روزرسانی کنید (برای سادگی، فرض می‌کنیم کلاینت به کلید رمزنگاری دسترسی دارد):

<template>
    <div>
        <h1>Secure Real-Time Chat</h1>
        <div v-if="!isAuthenticated">
            <p>Please log in to join the chat.</p>
        </div>
        <div v-else>
            <form @submit.prevent="sendMessage">
                <input v-model="newMessage" placeholder="Type a message..." required>
                <button type="submit">Send</button>
            </form>
            <h2>Messages</h2>
            <ul>
                <li v-for="msg in messages" :key="msg.timestamp">
                    {{ msg.user.name }}: {{ msg.message }} ({{ msg.timestamp }})
                </li>
            </ul>
            <h2>Online Users</h2>
            <ul>
                <li v-for="user in onlineUsers" :key="user.id">
                    {{ user.name }} (ID: {{ user.id }})
                </li>
            </ul>
        </div>
    </div>
</template>

<script>
import { decrypt } from 'crypto-js/aes'; // فرضی: کتابخانه‌ای برای رمزگشایی
import utf8 from 'crypto-js/enc-utf8';

export default {
    data() {
        return {
            newMessage: '',
            messages: [],
            onlineUsers: [],
            isAuthenticated: !!localStorage.getItem('token')
        };
    },
    mounted() {
        if (this.isAuthenticated) {
            this.joinChat();
        }
    },
    methods: {
        joinChat() {
            window.Echo.join('chat-room')
                .here((users) => {
                    this.onlineUsers = users;
                })
                .joining((user) => {
                    this.onlineUsers.push(user);
                    this.messages.push({
                        message: `${user.name} joined the chat`,
                        user: { name: 'System' },
                        timestamp: new Date().toLocaleString()
                    });
                })
                .leaving((user) => {
                    this.onlineUsers = this.onlineUsers.filter(u => u.id !== user.id);
                    this.messages.push({
                        message: `${user.name} left the chat`,
                        user: { name: 'System' },
                        timestamp: new Date().toLocaleString()
                    });
                })
                .listen('.chat.message', (e) => {
                    // فرض می‌کنیم کلید رمزنگاری در کلاینت موجود است
                    const decrypted = decrypt(e.encryptedMessage, 'secret-key').toString(utf8);
                    this.messages.push({
                        message: decrypted,
                        user: e.user,
                        timestamp: e.timestamp
                    });
                });
        },
        sendMessage() {
            fetch('/send-message', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
                    'Authorization': `Bearer ${localStorage.getItem('token')}`
                },
                body: JSON.stringify({ message: this.newMessage })
            });
            this.newMessage = '';
        }
    }
};
</script>

توجه: در این مثال، از کتابخانه crypto-js برای رمزگشایی استفاده شده است. در عمل، باید کلید رمزنگاری را به‌صورت امن بین سرور و کلاینت مدیریت کنید (مثلاً از طریق یک API جداگانه).

مرحله ۴: تنظیم CSRF و لاگ‌گیری

  1. متا تگ CSRF را به فایل blade اضافه کنید:
<!DOCTYPE html>
<html>
<head>
    <title>Secure Real-Time Chat</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    @vite(['resources/js/app.js'])
</head>
<body>
    <div id="app"></div>
</body>
</html>
  1. لاگ‌گیری را همان‌طور که در بخش قبل توضیح داده شد، تنظیم کنید.

مرحله ۵: اجرای پروژه

  1. سرور Reverb را با حالت دیباگ اجرا کنید:
   php artisan reverb:start --debug
  1. سرور لاراول را اجرا کنید:
   php artisan serve
  1. فایل‌های جاوااسکریپت را کامپایل کنید:
   npm run dev
  1. به http://localhost:8000 بروید، وارد سیستم شوید، و چت را تست کنید. لاگ‌ها را در storage/logs/laravel.log بررسی کنید.

خروجی مورد انتظار

  • پیام‌های ارسالی رمزنگاری شده و در سمت کلاینت رمزگشایی می‌شوند.
  • کاربران غیرمجاز نمی‌توانند به کانال chat-room دسترسی پیدا کنند.
  • اگر کاربر بیش از 100 پیام در ساعت ارسال کند، خطای 429 Too Many Requests دریافت می‌کند.

نکات و بهترین شیوه‌ها

  • امنیت کلیدها: کلیدهای رمزنگاری را در محیط‌های امن (مانند .env یا Vault) ذخیره کنید.
  • مانیتورینگ: از ابزارهایی مانند Laravel Telescope یا Pulse برای نظارت بر اتصالات و پیام‌ها استفاده کنید.
  • تست امنیت: از ابزارهایی مانند OWASP ZAP برای تست حملات احتمالی روی WebSocket استفاده کنید.

تمرین پیشنهادی

  • یک سیستم اطلاع‌رسانی برای خطاهای rate limiting به کاربران اضافه کنید.
  • قابلیت رمزنگاری end-to-end برای پیام‌های چت را پیاده‌سازی کنید.
  • لاگ‌های دیباگ را به یک فایل جداگانه هدایت کنید و آن‌ها را با یک ابزار مانیتورینگ (مانند Grafana) تجزیه‌وتحلیل کنید.

منابع

  • مستندات رسمی لاراول: laravel.com/docs/12.x/broadcasting
  • مستندات Reverb: laravel.com/docs/12.x/reverb
  • مستندات Sanctum: laravel.com/docs/12.x/sanctum

در فصل بعدی، به نظارت و بهینه‌سازی عملکرد سرور Reverb خواهیم پرداخت.