x

Einloggen

Hast du noch keinen Account? Jetzt registrieren!

Immer rein mit euch! Zugang ohne Login und Passwort

Wer ist schuld?
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.
Für jeden dieser Fälle müssen wir für den Benutzer eine Art Kennung schaffen und diese in die URL einfügen, die mit der Mail an ihn gesendet wird.

Normalerweise sollte diese Kennung folgende Eigenschaften besitzen:
  • einmalig
  • sicher
  • schwer (unmöglich) zu fälschen
  • nicht allzu lang
Ich habe eine Reihe von Möglichkeiten gesehen (oder bin auch manchmal selbst dazu übergegangen) die darauf aufbauen in die Datenbank ein zusätzliches Datenfeld, oder eine ganze Tabelle einzufügen, die diese generierten Kennungen zusammen mit zusätzlichen Informationen speichert. Das erlaubt es den Benutzer einzuloggen, wenn er mit dieser Kennung die Seite besuchen will. Diese Kennung bekommt man meist mithilfe einer Hashfunktion. Zum Beispiel:
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.
Diese Nachteile sind erheblich, von daher ist so eine Möglichkeit oftmals ungeeignet. Jedoch ist es kein Grund die Kennungen in der Datenbank zu speichern. Man kann die Daten in der Kennung speichern, und zwar verschlüsselt und deshalb sicher.

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)
Ich ziehe mal vor was für eine Kennung herauskommt: 67147328f43d69f7784770a2d9c84b181a8c. Was ihr in dieser Kennung speichert hängt von der jeweiligen Aufgabe ab. Hiermit wollte ich nur zeigen, dass eine Kennung mit annehmbarer Länge 18 Byte an Daten speichern kann.

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 FALSE

Wenn 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.
372 Mal gelesen
+5
19. Feb 2011, 17:55

Kommentare

(2)
RSS
avatar
0
v x
Klener 19. Feb 2011, 19:01

Endlich wieder ein Beitrag bei dem man etwas lernen kann!
Vielen Dank, wird mir sicher noch helfen!

avatar
0
v x
OnlineMeister 30. Jun 2011, 16:58

Besucht online-meister.de.tl


Kommentieren

Fett Kursiv Unterstrichen Durchgestrichen   Link Zitieren Code
Ich bin mit den Nutzungsbedingungen einverstanden.
Zukünftige Kommentare zu diesem Beitrag abonnieren (abbestellbar).
 
Bitte klicke jetzt auf den Bestätigungslink in deiner E-Mail.