API Sicherheit
Ein großes Thema bei einer API ist die Zugangskontrolle. Wir wollen natürlich nicht, dass jeder, der die richtige URL entdeckt, unsere Daten abgreifen kann.
Wie oben schon erwähnt ist der erste Schritt HTTPS zu verwenden, dann kann schon mal niemand mehr so leicht die Abfragen mitlesen.
Darüber hinaus müssen wir unbedingt den Client authentifizieren zum Beispiel mit einem Token, dass wir individuell für jeden Partner vergeben. Zusätzlich können wir durch eine Begrenzung auf die IPs des Partners Missbrauch eingrenzen.
Authorization Token
Um die einzelnen Nutzer der API zu legitimieren bietet sich ein Authorization Token im Kopfteil jeder Anfrage an.
Die Anfrage würde entsprechend erweitert.
1 |
curl -X GET -H 'Authorization: Token token="super_geheimes_token"' "https://api.example.com/v1/user?id=5" |
Das müssen wir dann an einer zentralen Stelle prüfen, damit es für alle Endpunkte gilt. Hier kommt die bereits zuvor erwähnte Basisklasse ApiController ins Spiel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
namespace Api\Controller; abstract class ApiController { public function initialize() { $this->checkToken(); } private function checkToken() { // Token prüfen $split = []; // Wenn der Server das für uns auftrennt. if (isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) { $split = [$_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']]; } // Falls wir mal selbst was testen wollen - PHPUnit! if (isset($_SERVER['HTTP_AUTHORIZATION'])) { $split = explode('=', $_SERVER['HTTP_AUTHORIZATION']); } // Das hier wird wohl normalerweise richtig sein if (function_exists('getallheaders')) { $headers = getallheaders(); if (isset($headers['Authorization'])) { $split = explode('=', $headers['Authorization']); } } if (isset($split[1])) { if (trim($split[1], '"') == 'super_geheimes_token') { return; } } throw new ApiException('access denied', ApiException::AUTHENTICATION_FAILED); } } |
Etwas aufwendiger (aber auch sicherer) wäre die Umsetzung von OAuth. Hier fordert der Client zunächst ein Access-Token über einen separaten Login-Endpunkt an und sendet dieses bei den nachfolgenden Anfragen mit. Die API wäre dann zwar nicht mehr 100 % RESTful (der Server speichert ja das Access-Token), aber der Zugewinn an Sicherheit rechtfertigt dies in einigen Situationen.
Zugriff begrenzen durch IP-Whitelist
Sehr empfehlenswert ist eine Zugriffsbeschränkung anhand einer IP-Whitelist. Wir wollen hier konkrete IPs wie 192.168.10.15 aber auch Netzmasken wie 192.168.10.0/27 erlauben.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public function initialize() { $this->checkToken(); $this->checkIpAddress(); } private function checkIpAddress() { $whiteList = ['127.0.0.1', '192.168.10.15/27']; $ip = $_SERVER['REMOTE_ADDR']; foreach ($whiteList as $allowed) { if ($ip == $allowed) { // Exakte Übereinstimmung, direkt fertig. return true; } elseif (strpos($allowed, '/') !== false) { // Netzmaske prüfen list($allowed, $netmask) = explode('/', $allowed, 2); $x = explode('.', $allowed); while (count($x) < 4) { $x[] = '0'; } $range = sprintf("%u.%u.%u.%u", (int)$x[0], (int)$x[1], (int)$x[2], (int)$x[3]); $rangeDecimal = ip2long($range); $ipDecimal = ip2long($ip); $wildcardDecimal = pow(2, (32 - $netmask)) - 1; $netmaskDecimal = ~$wildcardDecimal; if (($ipDecimal & $netmaskDecimal) == ($rangeDecimal & $netmaskDecimal)) { // Netzmaske enhält die IP return true; } } } throw new ApiException('access denied', ApiException::AUTHENTICATION_FAILED); } |