Täällä Ohjelmointiputkassa on useampiakin kertoja keskusteltu IP-estojen toteuttamisesti yms... Nyt päätin julkaista täällä koodivinkeissä oman toteutukseni IP-blokkaajasta, jota olen käyttänyt myös tämän hetkisessä projektissani.
Tämä versio on tehty luokkatoteutuksena ja se käyttää tietokantaa hyväkseen.
Koodi on myös aika väljää, ja selkeää (miestäni :D), joten en nähnyt syytä kommentoida sitä...
HUOM. Jos IP:tä estettäessä ei anneta estoaikaa (lifetime), niin tulkitaan, että IP on estetty määräämättömän pitkäksi ajaksi.
HUOM2. Estoaika tallennetaan tietokantaan UNIX-aikaleimana (eli INT-tyyppisenä), koska inhoan MySQL:n päivämäärätyyppejä.
ip_blocker.php
<?php /* Luokka: IPBlocker Tehnyt: Triton Kuvaus: Hoitaa IP-estot yms... */ class IPBlocker { private $database; public function __construct() { $this->database = new PDO(); } public function block($ipAddress, $lifetime=null) { $lifetime = (is_null($lifetime) ? null : $lifetime + time()); if (!$this->getBlocking($ipAddress)) { $query = $this->database->prepare("INSERT INTO ip_block_list (ipAddress, lifetime) VALUES (?, ?)"); $query->execute(array($ipAddress, $lifetime)); } else { $query = $this->database->prepare("UPDATE ip_block_list SET lifetime = ? WHERE ipAddress = ?"); $query->execute(array($lifetime, $ipAddress)); } } public function free($ipAddress) { if ($this->getBlocking($ipAddress)) { $query = $this->database->prepare("DELETE FROM ip_block_list WHERE ipAddress = ?"); $query->execute(array($ipAddress)); } } public function isBlocked($ipAddress) { if ($blocking = $this->getBlocking($ipAddress)) { if (is_null($blocking->lifetime) || $blocking->lifetime >= time()) return true; $this->free($ipAddress); return false; } return false; } private function getBlocking($ipAddress) { $query = $this->database->prepare("SELECT * FROM ip_block_list WHERE ipAddress = ?"); $query->execute(array($ipAddress)); return $query->fetchObject(); } }
ip_block_list-taulu
CREATE TABLE ip_block_list ( id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY, ipAddress CHAR(15) NOT NULL, lifetime INT NULL );
Ihan ensiksi täytyy ihmetellä, miksi tässä on kaksi ylimääräistä tasoa sisennystä. Se ei todellakaan tee koodista selkeää ja ilmavaa vaan aiheuttaa jatkuvaa hämmennystä.
Koodissasi on suunnitteluvirheitä, joiden takia se voi toimia väärin, jos kaksi pyyntöä tapahtuu samaan aikaan. Lisäyksen ja muokkauksen yhteydessä ei tarvitse hakea vanhaa tietoa. Poistokoodi pitäisi ajaa ilman ehtoa, ja lisäyksessä INSERT pitäisi ajaa ilman ehtoa ja sen epäonnistuessa UPDATE (ellei sitten suoraan MySQL-spesifisesti REPLACE tai jopa INSERT ... ON DUPLICATE KEY UPDATE). IP-osoitteen pitäisi olla kannassa PRIMARY KEY tai ainakin UNIQUE KEY.
Hakuvaiheessa taas olisi tyylikästä tarkistaa voimassaolo jo kyselyssä eikä enää PHP:n puolella. Lisäominaisuutena voisi harkita vanhentuneiden blokkausten automaattista poistoa.
Tietokannassa on hämäävästi lifetime, vaikka loogisempi nimi olisi esimerkiksi endTime. Ei myöskään tarvitse noita MySQL:n aikaleimoja pelätä, oikein toteutettunahan et joutuisi niihin koskemaan tässä ollenkaan, kun lisäyskyselyynkin voisi kirjoittaa NOW() + INTERVAL ? SECONDS
ja hakuun sitten vain WHERE endTime > NOW()
.
Osoitteen tallennusta tekstimuodossa pidetään yleisesti aika huonona tapana, IPv4-osoitteen voi käytännöllisesti tallentaa 32-bittisenä lukuna (INET_ATON(teksti)
) tai vähintäänkin BINARY-muodossa.
Aihe on jo aika vanha, joten et voi enää vastata siihen.