Eins der am häufigsten auftretenden Probleme für Web-Designer ist es dem Benutzer den Zutritt zur Seite zu gewähren ohne Eingabe von Passwort und Login, diesen jedoch trotzdem zu autorisieren.
Einige Beispiele dazu:
- Ein Aktivierungslink für ein Account für einen Benutzer, der sich gerade erst registriert hat.
- Link um das Passwort wiederherzustellen
- Die Rückkehr, bzw. Einladung für einen Benutzer, der lange nicht mehr auf der Seite war.
Normalerweise sollte diese Kennung folgende Eigenschaften besitzen:
- einmalig
- sicher
- schwer (unmöglich) zu fälschen
- nicht allzu lang
sha1($userId. «secret_key». time());
Meistens bekommt man das als erstes vorgeschlagen, wenn man vor so einem Problem steht, jedoch sollte es nur der letzte Ausweg sein.Ich will euer Augenmerk auf eine andere Möglichkeit richten die weniger aufwendig ist, die ohne eine Datenbank auskommt.
Was sollte man also tun?
Zusätzlich zu den oben genannten Eigenschaften einer Kennung werden meist noch weitere Einschränkungen getroffen, von diesen sollte man sich verabschieden.
In den einfachsten Fällen kommt man mit einer der Funktionen aus:
sha1($userId. «secret_key»);
oder
sha1($userId. «secret_key». «confirm_code»);
Wenn der Benutzer mit einer solchen Kennung ankommt mit einer URL die zum Beispiel wie folgt aussieht example.com/users/%user_id%?t=%key%, können wir ihn überprüfen und autorisieren.Die Nachteile dieser Methode:
- Für jeden Benutzer wird die Kennung gleich sein
- So eine Kennung wird stets gültig sein, man kann das zeitlich nicht begrenzen
- Man muss die Benutzer-ID offen in der URL schreiben.
In php wird mittels Mcrypt verschlüsselt, dieses Programm kann man einfach installieren, mittels pecl, oder man greift auf die Daten des eigenen Betriebssystems zu.Man kann auch seinen eigenen Algorithmus verwenden. Es ist auch unerheblich – Hauptsache man hat eine Funktion, die einen Text verschlüsselt und wieder entschlüsselt.
Die Idee ist simpel, wir wählen Daten, die wir brauchen und verschlüsseln diese mit unserem geheimen Schlüssel. Jetzt wandeln wir das in eine URL um und schicken das als eine Kennung an unseren Benutzer, der damit unsere Seite besuchen wird.
Man sollte sich bewusst, sein, dass je mehr Daten wir verschlüsseln wollen, desto größer die URL sein wird. Da es nicht mehr eine Hashfunktion mit einer vorgegebenen Länge ist.
Ich habe mich zum Beispiel für folgende Daten entschieden:
- Benutzer-ID (4 Byte)
- Zeit, wann die Kennung geschaffen wurde (4 Byte)
- Wann soll die Kennung gültig sein (4 Byte)
- Variable (1 Byte) – diese soll die Kennungen nach ihrer Bestimmung unterteilen (Passwort wiederherzustellen/ den Account Aktivieren/ eine einfache Einladung)
- Zufallszahl (1 Byte) – Damit bekommen wir Einzigartigkeit
- Kontrollsumme (4 Byte)
Kommen wir zur Verwirklichung. Ich habe das mithilfe zweier statischer Methoden realisiert.
<?php
class AuthToken {
private static $key = "Geheimer Schlüssel";
private static $iv = "sollte jedes Mal zufällig sein, aber hier reicht einfach ein geheimer Schlüssel";
private static function int2char($int) {
$char = "";
$hex = sprintf("%08x", $int);
for ($i = 0; $i < 4; $i++) {
$char .= chr(hexdec(substr($hex, $i * 2, 2)));
}
return $char;
}
private static function char2int($char) {
$int = 0;
$hex = "";
for ($i = 0; $i < 4; $i++) {
$hex .= sprintf("%02x", ord($char{$i}));
}
$int = hexdec($hex);
return $int;
}
public static function create($id, $expire = 0, $mode = 0) {
$id = intval($id);
$expire = intval($expire);
$mode = intval($mode);
if ($id < 0 || $expire < 0 || $mode < 0) {
return null;
}
$info = array();
$info["id"] = $id;
$info["time"] = time();
$info["expire"] = $expire;
$info["mode"] = $mode;
$info["rnd"] = ceil(mt_rand( 0, 255));
$info["sum"] = $info["time"] - $info["expire"] - $info["mode"] - $info["rnd"] - $info["id"];
$info = self::int2char($info["id"]) . self::int2char($info["time"]) .
self::int2char($info["expire"]) . chr($info["mode"]) . chr($info["rnd"]) .
self::int2char($info["sum"]);
$token = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, sha1(self::$key), $info,
MCRYPT_MODE_OFB, md5(self::$iv));
$tokenHex = "";
$tokenLength = strlen($token);
for ($i = 0; $i < $tokenLength; $i++) {
$tokenHex .= sprintf("%02x", ord($token{$i}));
}
return $tokenHex;
}
public static function check($tokenHex, $mode = null) {
$token = "";
$tokenHexLength = strlen($tokenHex) / 2;
for ($i = 0; $i < $tokenHexLength; $i++) {
$token .= chr(hexdec(substr($tokenHex, $i * 2, 2)));
}
$info = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, sha1(self::$key), $token, MCRYPT_MODE_OFB, md5(self::$iv));
if (strlen($info) == 18) {
$info = array("id" => self::char2int(substr($info, 0, 4)), "time" => self::char2int(substr($info, 4, 4)), "expire" => self::char2int(substr($info, 8, 4)), "mode" => ord($info{12}), "rnd" => ord($info{13}), "sum" => self::char2int(substr($info, 14, 4)));
if ($info["sum"] == $info["time"] - $info["expire"] - $info["mode"] - $info["rnd"] - $info["id"]) { if ($info["expire"] > 0) {
if ($info["expire"] + $info["time"] < time()) {
return false;
}
}
if ($info["mode"] > 0) {
if ($mode !== null) {
if ($info["mode"] != $mode) {
return false;
}
}
}
return $info["id"];
} else {
return false;
}
} else {
return false;
}
}
}
?>
Die Schlüssel self::$key und self::$iv sollten sicherheitshalber getrennt vom Algorithmus gespeichert werden. (string) AuthToken::create($id, $expire = 0, $mode = 0)
Erzeugt eine Kennung(mixed) AuthToken::check($tokenHex, $mode = null)
Überprüft die Gültigkeit der Kennung. Falls die Überprüfung erfolgreich war, dann wird die ID des Benutzers wieder hergestellt, im entgegengesetzten Fall kommt es zu einem FALSEWenn man für $expire und/oder $mode nichts angibt, dann wird es nicht bei der Überprüfung der Kennung berücksichtigt. Wenn man $mode bei der Überprüfung nicht angibt, wird es ebenfalls nicht berücksichtigt.
Was tun, wenn es einen nicht weiterbringt?
Diese Methode ist ungeeignet, wenn die Kennung einmalig sein soll. Jedoch kann man dieses Problem notdürftig beheben. Man könnte zum Beispiel einen memcache-flag einbauen, der diese Kennung bei der Überprüfung nur eine begrenzte Zeit annimmt. Das wird in den meisten Fällen zu dem gewünschten Ergebnis führen.
Und falls das nicht hilft, dann müsst ihr euch selbst was einfallen lassen, oder auf die Datenbank zurückgreifen.


Kommentare
Endlich wieder ein Beitrag bei dem man etwas lernen kann!
Vielen Dank, wird mir sicher noch helfen!
Besucht online-meister.de.tl
Kommentieren