Magento 2 REST API to potężne narzędzie – i potencjalny wektor ataku jeśli jest źle skonfigurowane. Domyślnie wiele endpointów jest dostępnych publicznie lub wymaga jedynie tokenu klienta który można wyprowadzić z frontendowej aplikacji. Pokazuję jak prawidłowo zabezpieczyć API: tokeny OAuth, rate limiting, ACL i monitoring podejrzanych requestów.
Typy tokenów w Magento 2 REST API
| Typ tokenu | Uprawnienia | Jak uzyskać | Ryzyko |
|---|---|---|---|
| Guest (brak tokenu) | Tylko endpointy gość (koszyk, produkty) | Brak autentykacji | Scraping, DDoS |
| Customer token | Dane zalogowanego klienta | POST /V1/integration/customer/token | Eksfiltracja danych klienta |
| Admin token | Pełny dostęp do wszystkiego | POST /V1/integration/admin/token | Krytyczne – pełna kontrola sklepu |
| Integration token (OAuth) | Konfigurowalny przez ACL | Panel admin – Integrations | Zależy od przyznanych uprawnień |
Nigdy nie używaj Admin Token w aplikacjach zewnętrznych
<?php
// ŹLE - Admin Token w zewnętrznej integracji
$response = $client->post('/rest/V1/integration/admin/token', [
'json' => ['username' => 'admin', 'password' => 'admin123']
]);
$adminToken = json_decode($response->getBody(), true);
// Ten token daje pełny dostęp do całego API - ogromne ryzyko!
// DOBRZE - Integration Token z ograniczonymi uprawnieniami przez ACL
// Skonfiguruj w: System -> Integrations -> Add New Integration
// Przyznaj tylko te zasoby których integracja rzeczywiście potrzebuje
Własne reguły ACL dla modułu
<!-- etc/acl.xml - definicja zasobów ACL -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="Vendor_Module::subscriptions" title="Newsletter Subscriptions" sortOrder="100">
<resource id="Vendor_Module::subscriptions_view" title="View Subscriptions" sortOrder="10"/>
<resource id="Vendor_Module::subscriptions_create" title="Create Subscriptions" sortOrder="20"/>
<resource id="Vendor_Module::subscriptions_delete" title="Delete Subscriptions" sortOrder="30"/>
</resource>
</resource>
</resources>
</acl>
</config>
<!-- etc/webapi.xml - rejestracja endpointów z ACL -->
<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
<!-- Endpoint dostępny dla każdego (gość) -->
<route url="/V1/vendor/subscriptions" method="POST">
<service class="Vendor\Module\Api\SubscriptionManagementInterface" method="subscribe"/>
<resources>
<resource ref="anonymous"/>
</resources>
</route>
<!-- Endpoint tylko dla zalogowanego klienta -->
<route url="/V1/vendor/subscriptions/me" method="GET">
<service class="Vendor\Module\Api\SubscriptionManagementInterface" method="getMySubscriptions"/>
<resources>
<resource ref="self"/> <!-- self = zalogowany klient -->
</resources>
</route>
<!-- Endpoint tylko dla admina/integracji z odpowiednim uprawnieniem -->
<route url="/V1/vendor/subscriptions/:id" method="DELETE">
<service class="Vendor\Module\Api\SubscriptionManagementInterface" method="deleteById"/>
<resources>
<resource ref="Vendor_Module::subscriptions_delete"/>
</resources>
</route>
</routes>
Rate Limiting – ochrona przed nadużyciami
Magento 2.4.7+ ma wbudowany rate limiting dla endpointów autentykacji. Dla innych endpointów musisz dodać własny mechanizm lub użyć nginx/Varnish:
<?php
declare(strict_types=1);
namespace Vendor\Module\Plugin;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Cache\FrontendInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Webapi\Controller\Rest;
class RateLimitPlugin
{
// Limity per IP
private const LIMITS = [
'guest' => ['requests' => 60, 'window' => 60], // 60 req/min
'customer' => ['requests' => 300, 'window' => 60], // 300 req/min
'admin' => ['requests' => 600, 'window' => 60], // 600 req/min
];
public function __construct(
private FrontendInterface $cache,
private RequestInterface $request
) {}
public function beforeDispatch(Rest $subject, RequestInterface $request): array
{
$ip = $this->getClientIp();
$token = $request->getHeader('Authorization') ?? '';
$context = $token ? ($this->isAdminToken($token) ? 'admin' : 'customer') : 'guest';
$limit = self::LIMITS[$context];
$key = "rate_limit_{$context}_{$ip}";
$cached = $this->cache->load($key);
$count = $cached ? (int) $cached : 0;
if ($count >= $limit['requests']) {
throw new LocalizedException(
__('Too Many Requests. Please try again later.'),
null,
429
);
}
// Inkrementuj licznik
$this->cache->save((string) ($count + 1), $key, [], $limit['window']);
return [$request];
}
private function getClientIp(): string
{
// Uwzględnij proxy/CDN headers
$headers = ['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($headers as $header) {
$ip = $this->request->getServer($header);
if ($ip) {
return explode(',', $ip)[0]; // X-Forwarded-For może mieć listę
}
}
return '0.0.0.0';
}
private function isAdminToken(string $authHeader): bool
{
// Prosta heurystyka - w praktyce możesz sprawdzić w bazie
return str_starts_with($authHeader, 'Bearer ') && strlen($authHeader) > 40;
}
}
Rate limiting na poziomie nginx – wydajniejsze rozwiązanie
# /etc/nginx/conf.d/magento.conf
# Definicja stref limitowania
limit_req_zone $binary_remote_addr zone=api_guest:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=api_auth:10m rate=300r/m;
limit_req_zone $binary_remote_addr zone=api_login:10m rate=5r/m;
server {
# ...
# Endpoint logowania - bardzo restrykcyjny (ochrona przed brute force)
location ~ ^/rest/V1/integration/(customer|admin)/token {
limit_req zone=api_login burst=3 nodelay;
limit_req_status 429;
# Zwróć JSON zamiast domyślnego HTML Nginx przy 429
error_page 429 /errors/429.json;
fastcgi_pass fastcgi_backend;
include fastcgi_params;
}
# Pozostałe endpointy API
location /rest/ {
# Rozróżnij gości od zalogowanych po nagłówku Authorization
set $rate_zone api_guest;
if ($http_authorization) {
set $rate_zone api_auth;
}
limit_req zone=$rate_zone burst=20 nodelay;
limit_req_status 429;
fastcgi_pass fastcgi_backend;
include fastcgi_params;
}
}
Monitoring i alerty – wykrywanie nadużyć
<?php
declare(strict_types=1);
namespace Vendor\Module\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Psr\Log\LoggerInterface;
class ApiSecurityMonitor implements ObserverInterface
{
// Endpointy które powinny być monitorowane
private const SENSITIVE_ENDPOINTS = [
'/V1/customers',
'/V1/orders',
'/V1/products',
];
public function __construct(
private LoggerInterface $logger,
private \Magento\Framework\App\RequestInterface $request
) {}
public function execute(Observer $observer): void
{
$uri = $this->request->getRequestUri();
$method = $this->request->getMethod();
$ip = $this->request->getServer('REMOTE_ADDR');
// Loguj operacje zapisu na wrażliwych endpointach
foreach (self::SENSITIVE_ENDPOINTS as $endpoint) {
if (str_contains($uri, $endpoint) && in_array($method, ['POST', 'PUT', 'DELETE'], true)) {
$this->logger->warning('Sensitive API operation', [
'method' => $method,
'uri' => $uri,
'ip' => $ip,
'user_agent' => $this->request->getHeader('User-Agent'),
]);
}
}
}
}
Ukrywanie informacji o wersji Magento
<?php
// Plugin usuwający nagłówek X-Magento-Cache-Id i inne wersjonoujące nagłówki
namespace Vendor\Module\Plugin;
class RemoveVersionHeadersPlugin
{
public function afterSendResponse(
\Magento\Framework\App\Response\Http $subject,
\Magento\Framework\App\Response\Http $result
): \Magento\Framework\App\Response\Http {
// Usuń nagłówki które zdradzają wersję/technologię
$result->clearHeader('X-Powered-By');
$result->clearHeader('Server');
// Nie usuwaj X-Magento-Cache-Id jeśli używasz Varnish!
// $result->clearHeader('X-Magento-Cache-Id');
return $result;
}
}
Checklist bezpieczeństwa REST API
- Używaj Integration Tokens z minimalnym zestawem uprawnień ACL zamiast Admin Token
- Włącz rate limiting na poziomie nginx dla endpointu logowania (ochrona przed brute force)
- Ogranicz dostęp do /rest/ po IP dla integracji B2B które komunikują się ze znanych adresów
- Monitoruj logi pod kątem 401/403 – duża liczba może oznaczać skanowanie endpointów
- Wyłącz endpointy których nie używasz przez
etc/webapi.xmloverride w własnym module - Ustaw HTTPS jako wymagane – nigdy nie przekazuj tokenów przez HTTP
- Regularnie rotuj tokeny integracji – szczególnie jeśli są w zewnętrznych systemach
Podsumowanie
Bezpieczeństwo REST API w Magento 2 to kilka warstw: prawidłowy dobór i ograniczenie tokenów przez ACL, rate limiting który chroni przed brute force i nadużyciami, monitoring który daje wczesne ostrzeżenie i odpowiednia konfiguracja nginx jako pierwsza linia obrony. Admin Token w zewnętrznej integracji to niemal gwarancja problemów – Integration Token z minimalnym zestawem uprawnień to jedyna słuszna droga.
