BMC IO DocsBMC IO Docs
IO v6
IO v7
Notes
Docs
IO v6
IO v7
Notes
Docs
  • IO Framework v7

    • О фреймворке
    • Что изменилось в 7 версии
    • Структура
    • Доступ к участкам проекта
    • Разворачивание нового проекта
  • Backend

    • Настройки проекта Conf.php
    • Настройки модуля Module.php
    • Настройки composer
    • Функции фреймворка
    • Глобальные переменные
    • Константы фреймворка
    • Event класс
    • IOCore класс
    • Route и работа с ними
    • Request и работа с ними
    • Middleware и работа с ними
    • Class и работа с ними
    • Api model и работа с ними
    • Template и работа с ними
    • Мультиязычность и словарь фраз
    • Bin скрипты и работа с ними
    • Cron скрипты и работа с ними
    • $ioSession и работа с сессией
    • $ioHash и работа с кешем
    • $iodb и работа с базой данных
  • Frontend

    • Twig шаблонизатор и работа с ним
    • Twig функции
    • Twig фильтры
    • Twig контекст
    • Настройка NPM
    • Сборка Frontend составляющей
    • Обращение к Api через JS

IO v7. Middleware и работа с ними

  • Описание
  • Интерфейс Middleware
  • Методы Route для работы с Middleware
    • addMiddleware()
  • Встроенные middleware
    • IO\Middleware\AuthMiddleware
  • Поддержка CORS
    • Конфигурация CORS в роуте
    • Параметры CORS
    • Автоматическая обработка OPTIONS
    • Утилита CorsTrait
  • Создание собственного middleware
    • Пример: Middleware для логирования
    • Пример: Middleware для проверки IP
    • Использование кастомных middleware
  • Порядок выполнения
    • Примеры использования
  • Рекомендации по безопасности

Описание

Middleware (промежуточное ПО) предоставляет удобный механизм для фильтрации HTTP-запросов, поступающих в приложение. Middleware выполняются до и после вызова экшена роута, что позволяет:

  • Проверять авторизацию пользователя
  • Добавлять CORS заголовки
  • Логировать запросы
  • Модифицировать входящие запросы или исходящие ответы
  • Ограничивать доступ по IP
  • И многое другое

Фреймворк также предоставляет встроенную поддержку CORS (Cross-Origin Resource Sharing) с автоматической обработкой preflight (OPTIONS) запросов.

Middleware используются в Route.

Интерфейс Middleware

Все middleware должны реализовывать интерфейс IO\Middleware:

<?php

namespace IO;

interface Middleware
{
    /**
     * Обработка запроса до выполнения роута
     * 
     * @param App $app Экземпляр приложения
     * @param array $params Параметры middleware
     * @return mixed|null Если возвращает не null, выполнение прерывается
     */
    public function handle($app, $params = []);

    /**
     * Обработка после выполнения роута (опционально)
     * 
     * @param App $app Экземпляр приложения
     * @param mixed $response Ответ роута
     * @param array $params Параметры middleware
     * @return void
     */
    public function terminate($app, $response, $params = []);
}

Методы Route для работы с Middleware

addMiddleware()

Добавляет middleware в цепочку выполнения для текущего роута программно.

/**
 * Добавить middleware в цепочку
 * 
 * @param string|Middleware $middleware Класс middleware (например, \IO\Middleware\Auth::class) или объект
 * @param array $params Параметры для middleware
 * @return self Возвращает текущий объект роута для цепочечного вызова
 */
protected function addMiddleware($middleware, $params = [])

Параметры:

  • $middleware - полное имя класса middleware или объект, реализующий интерфейс Middleware
  • $params - ассоциативный массив параметров, которые будут переданы в методы handle() и terminate()

Примеры:

<?php
namespace App\Routes;

use IO\Route;

class DashboardRoute extends Route
{
    public function init()
    {
        parent::init();
        
        // Простая авторизация
        $this->addMiddleware(\IO\Middleware\Auth::class);
        
        // Авторизация с параметрами
        $this->addMiddleware(\IO\Middleware\Auth::class, [
            'redirect' => '/login',
            'json' => false
        ]);
        
        // Несколько middleware
        $this->addMiddleware(\IO\Middleware\Cors::class, [
            'allow_origin' => ['https://example.com']
        ]);
        
        $this->addMiddleware(\App\Middleware\CheckPermission::class, [
            'permission' => 'dashboard.view',
            'log' => true
        ]);
    }
    
    public function actionIndex()
    {
        return $this->render('@app/dashboard.twig');
    }
}

Встроенные middleware

IO\Middleware\AuthMiddleware

Проверяет авторизацию пользователя.

Пространство имен: IO\Middleware\AuthMiddleware

Параметры:

  • redirect - string - URL для перенаправления неавторизованных пользователей (по умолчанию '/login')
  • json - bool - если true, возвращает JSON ответ вместо редиректа (по умолчанию false)

Примеры:

// Редирект на страницу логина
$app->add_route([
    "url" => "/cabinet",
    "name" => "app:cabinet",
    "method" => "actionIndex",
    "middleware" => [
        \IO\Middleware\AuthMiddleware::class => [
            'redirect' => '/login'
        ]
    ]
]);

// API с JSON ответом при ошибке
$app->add_route([
    "url" => "/api/user/data",
    "name" => "app:api:userdata",
    "method" => "actionUserData",
    "middleware" => [
        \IO\Middleware\AuthMiddleware::class => [
            'json' => true
        ]
    ]
]);

Поддержка CORS

Фреймворк предоставляет встроенную поддержку CORS (Cross-Origin Resource Sharing) без необходимости использования middleware.

Конфигурация CORS в роуте

Для включения CORS добавьте ключ cors в конфигурацию роута:

$app->add_route([
    "url" => "/api/data",
    "name" => "app:api:data",
    "method" => "actionData",
    "cors" => [
        'allow_origin' => ['https://client.com', 'https://app.com'],
        'allow_credentials' => true,
        'allow_methods' => ['GET', 'POST'],
        'allow_headers' => ['Content-Type', 'Authorization'],
        'max_age' => 3600,
    ]
]);

Параметры CORS

ПараметрТипПо умолчаниюОписание
allow_originarray[]Список разрешенных источников (доменов)
allow_methodsarray['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']Разрешенные HTTP методы
allow_headersarray['Content-Type', 'Authorization', 'X-Requested-With', 'Accept']Разрешенные заголовки
allow_credentialsboolfalseРазрешить отправку кук и HTTP-авторизацию
max_ageint86400Время кеширования preflight запроса (в секундах)
expose_headersarray[]Заголовки, доступные для чтения в браузере

Автоматическая обработка OPTIONS

Фреймворк автоматически обрабатывает OPTIONS (preflight) запросы для роутов с конфигурацией CORS:

  • При получении OPTIONS запроса, фреймворк находит соответствующий роут по URL
  • Устанавливает CORS заголовки из конфигурации
  • Возвращает ответ 200 без вызова экшена роута
  • Для не-OPTIONS запросов CORS заголовки устанавливаются перед выполнением экшена

Утилита CorsTrait

Для удобной генерации конфигурации CORS на основе глобальной переменной $ioProjects предоставляется трейт IO\CorsTrait:

<?php

namespace App\Routes;

use IO\Route;
use IO\CorsTrait;

class ApiRoute extends Route
{
    use CorsTrait;
    
    public static function routes($app)
    {
        $instance = new self();
        
        // Автоматическая генерация origins из $ioProjects
        $corsConfig = $instance->getCorsConfig('mobi');
        
        $app->add_route([
            "url" => "/api/user/data",
            "name" => "app:api:userdata",
            "method" => "actionUserData",
            "cors" => $corsConfig,
            "middleware" => [
                \IO\Middleware\AuthMiddleware::class => ['json' => true]
            ]
        ]);
    }
}

Метод getCorsConfig()

/**
 * Получить конфигурацию CORS для проекта
 * 
 * @param string|null $project Имя проекта (например, 'mobi', 'admin')
 * @param array $extraOrigins Дополнительные источники для добавления
 * @return array Конфигурация CORS
 */
protected function getCorsConfig($project = null, $extraOrigins = [])

Автоматически включает:

  • Все проекты из $ioProjects (с https и http для dev-среды)
  • Специфичный домен для указанного проекта
  • Дополнительные источники из параметра $extraOrigins

Создание собственного middleware

Пример: Middleware для логирования

app/Middlewares/LoggerMiddleware.php
<?php

namespace App\Middleware;

use IO\Middleware;

class LoggerMiddleware implements Middleware
{
    protected $logFile = '/var/log/requests.log';
    
    public function handle($app, $params = [])
    {
        if (isset($params['log_file']))
        {
            $this->logFile = $params['log_file'];
        }
        
        $this->log('START ' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI']);
        return null;
    }
    
    public function terminate($app, $response, $params = [])
    {
        $this->log('END ' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] . 
                   ' - ' . http_response_code());
    }
    
    protected function log($message)
    {
        $date = date('Y-m-d H:i:s');
        $ip = getRealIP();
        file_put_contents($this->logFile, "[$date] [$ip] $message\n", FILE_APPEND);
    }
}

Пример: Middleware для проверки IP

app/Middlewares/IpFilterMiddleware.php
<?php

namespace App\Middleware;

use IO\Middleware;

class IpFilterMiddleware implements Middleware
{
    public function handle($app, $params = [])
    {
        $ip = getRealIP();
        $whitelist = $params['whitelist'] ?? [];
        $blacklist = $params['blacklist'] ?? [];
        $redirect = $params['redirect'] ?? '/403';
        $json = $params['json'] ?? false;
        
        // Проверка черного списка
        if (in_array($ip, $blacklist))
        {
            return $this->deny('IP is blacklisted', $redirect, $json);
        }
        
        // Если есть белый список, проверяем вхождение
        if (!empty($whitelist) && !in_array($ip, $whitelist))
        {
            return $this->deny('IP not in whitelist', $redirect, $json);
        }
        
        return null;
    }
    
    protected function deny($message, $redirect, $json)
    {
        if ($json)
        {
            return [
                'error_code' => -12,
                'error_str' => $message
            ];
        }
        
        header("Location: $redirect");
        exit;
    }
    
    public function terminate($app, $response, $params = []) {}
}

Использование кастомных middleware

<?php

namespace App\Routes;

use IO\Route;

class AdminRoute extends Route
{
    public static function routes($app)
    {
        $app->add_route([
            "url" => "/admin",
            "name" => "app:admin",
            "method" => "actionIndex",
            "middleware" => [
                // Проверка IP
                \App\Middleware\IpFilter::class => [
                    'whitelist' => ['192.168.1.0/24', '10.0.0.1'],
                    'redirect' => '/403'
                ],
                // Авторизация
                \IO\Middleware\AuthMiddleware::class => [
                    'redirect' => '/login'
                ],
                // Логирование
                \App\Middleware\Logger::class => [
                    'log_file' => '/var/log/admin.log'
                ]
            ]
        ]);
    }
}

Порядок выполнения

Middleware выполняются в следующем порядке:

  1. Middleware из конфигурации роута (в порядке объявления)
  2. Middleware, добавленные через addMiddleware() (в порядке добавления)

Для каждого middleware:

  1. Сначала вызывается метод handle() (этап before)
  2. Выполняется экшен роута
  3. Затем для каждого middleware (в обратном порядке) вызывается метод terminate() (этап after)
$this->addMiddleware(MiddlewareA::class);
$this->addMiddleware(MiddlewareB::class);
$this->addMiddleware(MiddlewareC::class);

// Порядок выполнения:
// 1. MiddlewareA::handle()
// 2. MiddlewareB::handle() 
// 3. MiddlewareC::handle()
// 4. Выполнение экшена роута
// 5. MiddlewareC::terminate()
// 6. MiddlewareB::terminate()
// 7. MiddlewareA::terminate()

Если какой-либо middleware возвращает не-null значение в handle(), цепочка прерывается и ответ возвращается немедленно.

Примеры использования

Пример 1: Защищенное API с CORS

<?php

namespace App\Routes;

use IO\Route;
use IO\CorsTrait;

class ApiRoute extends Route
{
    use CorsTrait;
    
    public static function routes($app)
    {
        $instance = new self();
        
        // Публичное API (без авторизации)
        $app->add_route([
            "url" => "/api/version",
            "name" => "app:api:version",
            "method" => "actionVersion",
            "cors" => [
                'allow_origin' => ['*'],
                'allow_methods' => ['GET'],
            ],
        ]);
        
        // Защищенное API
        $app->add_route([
            "url" => "/api/user/data",
            "name" => "app:api:userdata",
            "method" => "actionUserData",
            "cors" => $instance->getCorsConfig('mobi'),
            "middleware" => [
                \IO\Middleware\AuthMiddleware::class => ['json' => true],
            ],
        ]);
    }
    
    public function actionVersion()
    {
        return $this->json(['version' => '1.0.0']);
    }
    
    public function actionUserData()
    {
        global $ioSession;
        
        return $this->json([
            'user' => $ioSession->getUserInfo()
        ]);
    }
}

Пример 2: Административная панель с несколькими middleware

<?php

namespace App\Routes;

use IO\Route;

class AdminRoute extends Route
{
    public function init()
    {
        parent::init();
        
        // Добавляем проверку авторизации для всех экшенов
        $this->addMiddleware(\IO\Middleware\AuthMiddleware::class, [
            'redirect' => '/admin/login',
        ]);
        
        // Добавляем проверку IP
        $this->addMiddleware(\App\Middleware\IpFilterMiddleware::class, [
            'whitelist' => ['10.0.0.0/8', '192.168.1.0/24'],
            'redirect' => '/403',
        ]);
    }
    
    public function actionIndex()
    {
        return $this->render('@app/admin/index.twig');
    }
    
    public function actionUsers()
    {
        return $this->render('@app/admin/users.twig');
    }
}

Пример 3: Внутренний сервис (без CORS)

// Внутренний сервис, вызывается только через /gate/
$app->add_route([
    "url" => "/internal/process",
    "name" => "app:internal:process",
    "method" => "actionProcess",
    "type" => ["post"],
    "nosession" => true,
    // cors отсутствует - CORS заголовки не добавляются
]);

Пример 4: Динамическое добавление middleware

<?php

namespace App\Routes;

use IO\Route;

class DynamicRoute extends Route
{
    public function actionConditional()
    {
        // Добавляем middleware только при определенном условии
        if ($this->input('debug') === 'true')
        {
            $this->addMiddleware(\App\Middleware\DebugLoggerMiddleware::class);
        }
        
        // ... остальной код
    }
}

Рекомендации по безопасности

  1. CORS только там, где нужно - не добавляйте CORS к внутренним сервисам
  2. Явно указывайте origins - избегайте * если используются credentials
  3. Ограничивайте методы - разрешайте только необходимые HTTP методы
  4. Кешируйте preflight - используйте max_age для уменьшения нагрузки
  5. Проверяйте middleware - всегда проверяйте входные данные
Prev
Request и работа с ними
Next
Class и работа с ними