Login-Klasse | Mein erster Versuch mit Klassen

raicoo2k

Grünschnabel
Hallo,

ich habe mich heute das erste mal mit PHP-Klassen beschäftigt und wollte wissen ob das was ich gemacht habe richtig ist. Bisher habe ich nur mein altes Login als "Klasse" geschrieben (nicht fertig).

Für jeden Tip und Verbesserungsvorschlag wäre ich dankbar.

Hier mal mein Versuch:

PHP:
class UserManagement
{
   require_once dirname(__FILE__).'/engine/config.php';
   require_once dirname(__FILE__).'/engine/func.php';

   private $db;
   
   public $Id;
   public $Username;
   private $Password;
   private $Ip;
   public $Email;
   private $Fingerprint;
   private $Salt;
   private $Token;

   public function __construct()
   {
     require_once dirname(__FILE__).'/engine/class.mysqli.php';
     $this->db = new MysqliDb (MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DATABASE);
   }
   
  public function getIP()
   {
     $IpClient = (isset($_SERVER['HTTP_CLIENT_IP']) ? $_SERVER['HTTP_CLIENT_IP'] : NULL);
     $IpForward = (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : NULL);
     $IpRemote = (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL);

     if(filter_var($IpClient, FILTER_VALIDATE_IP))
     {
       $this->Ip = $IpClient;
     } else if(filter_var($IpForward, FILTER_VALIDATE_IP)) {
       $this->Ip = $IpForward;
     } else {
       $this->Ip = $IpRemote;
     }
     return $this->Ip;
   }
   
   public function Login($LoginUsername, $LoginPassword, $LoginToken)
   {
     $this->Username = $LoginUsername;
     $this->Password = $LoginPassword;
     $this->Token = $LoginToken;
     
     session_regenerate_id();
     
     // Leitet vom Login gesperrten User zurück
     $this->db->where('ip', $this->getIP());
     $Attempt = $this->db->getOne('login_attempts', 'count(ip) AS cnt');
     $Attempt_Counter = $this->db->count;
   
     if($Attempt['cnt'] >= MAX_LOGIN_ATTEMPTS)
     {
       $Subject = 'Achtung: Mehrfach fehlerhafter Loginversuch';
       
       $Body = '123 Test';
     
       // Benachrichtigt User per E-Mail bei fehlgeschlagenem Login
       sendmail('testmail@trashmail.com', $Subject, $Body);

       header('Location: enter.php?error=max_attempts');
       exit;
     }

     // Salt wird aus der Datenbank geholt
     $this->db->where('username', $LoginUsername);
     $GetSalt = $this->db->getOne('members', 'salt');

     // Prüft ob das Passwort zum Usernamen stimmt
     $this->db->where('username', $LoginUsername);   
     $this->db->where('password', hash('sha512', $LoginPassword.$GetSalt['salt']));   
     $Check = $this->db->getOne('members', 'id');
     $IsUser = $this->db->count;
     
     if($this->db->count == 1 && $LoginToken == $_SESSION['login_token'])
     {
       // Legt die aktuelle IP und ein Fingerabdruck in die Datenbank ab
       $Data = array(
           'ip'         => $this->getIP(),
           'fingerprint'     => md5(microtime(true).$_SERVER['HTTP_USER_AGENT'].$this->getIP())
           );
       $this->db->where('id', $Check['id']);
       $this->db->update('members', $Data);
     
       // Setzt ein Fingerabdruck
       $_SESSION['fingerprint'] = md5(microtime(true).$_SERVER['HTTP_USER_AGENT'].$this->getIP());
       $_SESSION['valid_login'] = '1';
       
       // Löscht fehlgeschlagene Login-Versuche nach erfolgreichem Login
       $this->db->where('ip', $this->getIP());
       $this->db->delete('login_attempts');
       
       // Weiterleitung wenn der Login erfolgreich war
       header('Location: index.php');
     } else {
       // Löscht alle Sessions bei falschem Login
       $_SESSION = array();
       
       if($LoginUsername == TRUE)
       {
         // Loggt falschen Login, um Angreifer zu sperren
         $Data = array(
             'ip'          => $this->getIP(),
             'time'         => time(),
             'username'       => $LoginUsername
           );
         $this->db->insert('login_attempts', $Data);
         }
     
         // Bei falschem Login wieder auf die Login-Seite weiterleiten
         header('Location: enter.php?action=login_failed');
         exit;
     }
     
     return $IsUser;
   }
}

Und das hier ist bei der Login eingebunden:

PHP:
if(string_get('action') == 'login')
{
   $Login = New UserManagement();
   $Login->Login(string_post('username'), string_post('password'), string_post('login_token'));
}


LG
Daniel
 
Hi

schaut nicht schlecht aus (bis auf "md5")
..wobei ein "richtig" beim Klassendesign sowieso subjektiv ist; es gibt nicht nur eine richtige Lösung.

edit: OK, hätte wohl genauer auf den Code schauen sollen...
 
Zuletzt bearbeitet:
Deine Klasse hat zu viele Aufgaben (und Abhängigkeiten). Versuch das besser zu trennen und mach lieber mehr Klassen als zu wenig. Mach dir klar, was alles eine Klasse sein könnte. Du hast Im Prinzip nur einige Funktionen in eine Klasse gepackt.

Außerdem: Die require/includes gehören normal nicht in die Klassendefinition.

Kapsel die Zugriffe auf diverse "Umgebungsparameter" auch in Klassen. Du könntest etwa Klassen für SessionManagement (Mit Methoden wie get, set, isset, regenerate, .. schreiben), andere für Request (Zugriff auf POST und GET, evt auch die IP) und Response (dadurch kannst du die header-Murkserei in der Klasse sparen.)
Mach die außerdem klar, dass der User selber ebefalls am besten eine Klasse ist. Wenn auch im einfachsten Fall nur ein einfacher DataContainer.

Dein UserManagemet sollte nur methoden wie register, login/authenticate und getUser beinhalten.

Den Zugriff auf die Datenbank selber hast du ja schon gut ausgelagert, du solltest die Abhängigkeit allerdings bessr injizieren (Dependency Injection) als sie "aus dem nichts" zu generieren. Optimaler weise ist der Zugriff auf den PersistenceLayer (=die DB) auch abstrahiert, sodass sie theoretisch austauschbar wäre.

Google am besten die Dinge, die du nicht kennst ;) Richtiges OO-Design ist aj wirklich geschmachssache
 
Herzlich Willkommen im Forum raicoo2k,

- Die Probleme, die alxy ansprach, sind wirklich sehr wichtig! Siehe http://en.wikipedia.org/wiki/Single_responsibility_principle und http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
- Variablennamen fangen in vielen PHP Coding Standards klein an. camelCase!
- SHA512 sollte nicht für das Hashen von PWs benutzt werden. Nutze bcrypt oder scrypt! SHA512 wurde nicht für solche Anwendungsszenarieren erstellt und ist (vor allem bei nur einer Iteration) viel zu schnell! Hash-Funktionen für PWs sollen langsam sein!
- Die Kommentare sind auf Deutsch.

Am besten erledigst du die in den vorherigen Beiträgen genannten Probleme und meine und postest den Code erneut.


PS: @sheel ist anscheinend schon nur noch auf md5 fixiert, geradezu obsessiv auf der Suche danach :p
 
Danke ComFreek,

habe es auf bCrypt 12 Runden umgestellt , hoffe das reicht, sonst kann ich es noch ändern.
Ich habe auch die Vars am anfang klein geschrieben.
Kommentare ebenfalls auf englisch :)

Noch eine Frage, da mir jemand sagte, dass OOP auch auf die Performance geht.
Wo ist es denn sinnvoll es zu nutzen? Macht es denn Sinn aus jeder Funktion eine Klasse zu machen?

Und wie "Joined" man andere klassen? Ist es richtig so, dass ich Informationen aus einer anderen Klasse so setze: getAddress::getISP(); ?

Werde an der Klasse noch etwas arbeiten und werde es dann hier nochmal posten! :)

LG
 
Natürlich wirkt sich jede Abstraktionsschicht auf die Performance aus.
Doch wir haben doch mittlerweile so schnelle PCs (vgl. Anfang 90er Jahre), dass das in fast allen Fällen keine Rolle mehr spielt und das Hauptanliegen das Design (= Lesbarkeit, Wartbarkeit, Erweiterbarkeit, ...) an sich spielt!

Nein, nicht jede Funktion soll in eine Klasse umgewandelt werden. Bei Mathe-Funktionen würde ich zum Beispiel eher zu einem Namespace als zu einer Klasse (mit nur statischen Funktionen) raten, da Namespaces für das Gruppieren gedacht sind.

Was meinst du mit "joinen" zweier Klassen? Wenn du von einem Objekt auf ein anderes Objekt zugreifen willst, brauchst du logischerweise eine Referenz zum zweiten Objekt. Woher du diese bekommst, ist nun die Frage. Dependency Injection wäre ein Stichwort dazu. "getAddress::getISP()" schaut ganz nach einem Singleton aus, was ich eher vermeiden würde, denn diese bringen einige Nachteile mit sich (u. a. beim Unit Testing).
 

Neue Beiträge

Zurück