Einleitung
Einige von euch kennen sicherlich das PHP-Framework CodeIgniter.
Heute zeige ich euch wie man schnell einen einfachen Login für CodeIgniter
schreiben kann.
Für die jenigen von euch die CodeIgniter nicht kennen:
CodeIgniter ist ein PHP-Framework wie PHPCake oder andere Frameworks.
Es bietet euch die Möglichkeit nach MVC-Pattern (Model View Controller)
ein eigenes Projekt zu entwickeln.
Models
In die Models kommen die Methoden die die Funktionalität bereitstellen,
bzw. die Methoden die auch auf die Datenbank zugreifen müssen. Größtenteils
spielt sich die volle Funktionalität eures Projektes hier ab.
Views
Die Views sind die Seiten die eure Besucher zu gesicht bekommen. Hier spielt
sich das vollständige Templating/Anzeige ab (Keine Programmlogik).
Controller
Und zu guter letzt die Controller-Klassen.
Die Controller dienen dazu um die Ausgabe der Models an die View-Elemente
zu übertragen, bzw. um Eingaben von Besuchern an die Models zu übergeben.
Die Controller fungieren hier als reine „Übergabe-Klassen“, sie transportieren
Ausgaben und Eingaben.
Ein Controller kann mehrere Models sowie mehrere Views laden und verwenden.
Allgemein
Der Vorteil von MVC ist der, dass man sich nicht selbst wiederholen muss,
die einzelnen Bestandteile des Projekts sauber getrennt sind und mal viel
Spielraum besitzt um neue Module zu schreiben, oder Elemente des Projekts
anzupassen.
Auf den nachfolgenden Zeilen werde ich euch demonstrieren, wie einfach es ist
einen simplen Login für das eigene Projekt zu schreiben.
Hier bei lagern wir Sessions in die Datenbank aus und werden für Logindaten
auf die Datenbank zugreifen.
Teile meiner Konfiguration von CodeIgniter:
<?php $config['log_threshold'] = 1; $config['sess_cookie_name'] = 'xconf_session'; $config['sess_expiration'] = 7200; $config['sess_expire_on_close'] = TRUE; $config['sess_encrypt_cookie'] = TRUE; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = TRUE; $config['sess_time_to_update'] = 300; $config['global_xss_filtering'] = TRUE; $config['csrf_protection'] = TRUE; $config['csrf_token_name'] = 'token'; $config['csrf_cookie_name'] = 'cookie'; $config['csrf_expire'] = 7200; $config['compress_output'] = TRUE; $config['rewrite_short_tags'] = TRUE; $config['url_suffix'] = '.html'; ?>
Definierte Route meines CodeIgniter:
<?php $route['default_controller'] = "main"; ?>
.htaccess
ErrorDocument 404 /index.php # Set the default handler. DirectoryIndex index.php # Various rewrite rules. <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?/$1 [L,QSA] </IfModule>
Fangen wir zu aller erst beim Controller an.
Der Grundaufbau vom Controller sieht wie folgt aus:
/application/controller/login.php
<?php class login extends CI_Controller{ public function __construct(){ parent::__construct(); } public function index(){ } } ?>
Wie wir sehen ist diese Controller-Klasse eine Kindklasse von CI_Controller.
Alle Controller in CodeIgniter erben von dieser Klasse.
Die Methode index() kann nicht frei verwendet werden. Diese wird geladen sobald
zu diesem Controller innerhalb des Frameworks gerouted wird.
Um CodeIgniter besser verstehen zu können möchte ich euch hier erst einmal
die Funktionsweise von Controllern im Bezug auf URLs erst einmal erklären.
Nehmen wir an es wird zum Controller login gerouted, so sieht dies nach meiner
Konfiguration wie folgt aus:
http://localhost/login.html
Wieso?
Als url_suffix habe ich eingetragen „.html“. So ergibt sich aus dem „login“ ein „login.html“.
In meiner htaccess File habe ich gesagt soll alles was hinter http://localhost/
aufgerufen wird rewrited werden zu http://localhost/index.php/
Notiz:
Standardgemäß sieht das Konzept wie folgt aus:
http://localhost/index.php///<Übergabeparameter>
Wenn nun http://localhost/login.html aufgerufen wird ergibt sich daraus:
http://localhost/index.php/login.html
das „.html“ wird wegen des Suffix angehangen spielt aber hier keine wesentliche Rolle nur schönheits-
halber.
Ohne Suffix würde es wie folgt aussehen:
http://localhost/index.php/login
Dennoch wird uns der Inhalt der Methode index() ausgegeben, sofern etwas dort zusehen ist.
Die Methode index() wird auferufen wenn keine Methode in der URL angegeben wurde.
Nehmen wir an wir hätten in unserem login-controller die Methode login, so würde
diese Methode auferufen werden können durch die Navigation zu:
http://localhost/login/login.html
Eine andere Schreibweise für http://localhost/login.html wäre hier auch:
http://localhost/login/index.html
Soviel erst einmal zum Konzept von Controllern in CodeIgniter.
Models dagegen sind wie folgt aufgebaut in CodeIgniter:
<?php class user extends CI_Model{ public function __construct(){ } } ?>
Unser Beispiel-Model „user“ erbt hier auch wieder von einer Klasse und zwar der Klasse „CI_Model“.
Hier kommen alle unsere Methoden rein die wir brauchen um bei unserem Loginbeispiel auf die Datenbank
zuzugreifen.
Ergänzen wir die Methode __construct z.B. um eine Datenbankverbindung sehe dies wie folgt aus:
<?php class user extends CI_Model{ public function __construct(){ $this->load->database(); } } ?>
Hier wird beim Laden des Models die Datenbankverbindung hergestellt und kann verwendet werden über:
<?php $this->db->[..]; ?>
Kommen wir nun zu aller letzt zum View.
Den View könnt ihr relativ viel selbst gestalten. Dieser muss nicht zwingend OOP sein.
Dort kommt euer HTML-Code hinein, mit einigen PHP-Ergänzungen wie z.B. Variablen die an den View
übergeben werden.
So nun wo ihr ungefähr wisst wie CodeIgniter funktioniert mit MVC-Pattern können wir beginnen.
Datenbankzugriffe werden bei unserem simplen Login auf Active-Record Pattern basieren.
Fangen wir doch zu aller erst mit unserem Model an.
Wir erstellen zunächst einmal eine neue PHP-Klasse im Ordner /application/models/
diese nennen wir user.php und fügen folgenden Inhalt in diese hinein:
<?php /** * Description of user * * @author Ilya Beliaev */ class User extends CI_Model { public function __construct(){ parent::__construct(); //Datenbankverbindung herstellen $this->load->database(); } /** * Überprüft die Benutzerdaten eines Benutzers der sich einloggen möchte * @param String $username Benutzername des Kunden * @param String $password Passwort des Kunden * @return false/String Fail/Username */ public function checkUserCredentials($username, $password){ //Salt für den Benutzer raussuchen aus der Datenbank $salt = $this->getSalt($username); if(!empty($salt)){ //Anhand des herausgesuchten Salts und des Plaintext Passworts einen Hash bilden $hash = $this->gethash($password, $salt); //Datenbankabfrage mittels Active Record Pattern $this->db->select("username"); $this->db->from("user"); $this->db->where("username", $username); $this->db->where("password", $hash); $query = $this->db->get(); //Prüfen ob MySQL-Ausgabe mehr als 0 Zeilen ausliefert. if($query->num_rows() > 0){ $row = $query->row(0); $return = $row->username; }else{ $return = false; } }else{ $return = false; } // Username oder False zurück geben, an die Aufrufer-Klasse in dem Falle den Controller return $return; } /** * Holt den Salt eines Benutzers * @param String $username Benutzername * @return String Salt */ private function getSalt($username){ //Salt mittels einer SQL-Abfrage aus der Datenbank holen $this->db->select('salt'); $this->db->from("user"); $this->db->where("username", $username); $query = $this->db->get(); $row = $query->row(0); if($query->num_rows() > 0){ $salt = $row->salt; }else{ $salt = ""; } return $salt; } /** * Erzeugt aus Password und Salt einen Sha1-Hash * @param String $password Plaintext Passwort * @param String $salt 40stelliger Salt * @return String SHA1-Hash */ private function gethash($password, $salt){ //Erzeugen eines Hashes anhand des Passworts und des Salts return sha1($password.md5($salt)); } } ?>
Hier ist ein simples Beispiel eines User-Models.
Die entscheidende Methode für unseren Login ist hier die Methode checkUserCredentials().
Diese überprüft die Benutzerdaten mit den Daten in der Datenbank.
Wenn Username und Passwort korrekt sind wird der Aufruferklasse der Benutzername zurück gegeben,
wenn nicht wird eine False Meldung ausgegeben.
Den Nicknamen geben wir aus dem Grunde zurück, weil wir diesen als Session setzen werden um diesen
evtl. später auf der gesicherten Seite auszugeben. Wir verwenden hier nicht den Namen den der User eingegeben
hat weil dieser ebenso evtl. klein geschrieben sein könnte, aber wir möchten ja letztendlich den Nicknamen
in der korrekten Schreibweise, daher holen wir uns diesen aus der Datenbank, sofern der Login erfolgreich
war.
Nun kommen wir zu unserem View.
Views werden hinterlegt in CodeIgniter in /application/views/.
Dort erstellen wir ein neuen view unter dem Namen „login.php“:
<?php /** Hier erzeugen wir unseren Doctype des Dokumentes in dem Falle ist sofern nichts anderes angegeben wurde als Übergabeparameter wird der Doctype xhtml verwendet. Ändert sich der Doctype so ändern sich ebenso die entsprechenden HTML tags die sich dynamisch erzeugen lassen zu dem entsprechenden Doctype. */ echo doctype()."\n"; ?> <html> <head> <?php //Hier wird als <link></link> die CSS-File eingebunden. //base_url() ist die Basis-Adresse des Dokumentes //http://localhost/<pfad>/ echo link_tag(base_url().'themes/css/style.css')."\n"; ?> <title>Login</title> </head> <body> <div id="logo"> </div> <div class="login-box"> <?php //Hier wird ein H3 Tag geworfen mit dem Text "Beispiel-Login": //<h3>Beispiel-Login</h3> echo heading('Beispiel-Login', 3); //Ausgabe des Formulars für den Login //Attribute für das Formular <form></form> //Die URL wird sofern das Attribut "action" nicht angegeben wird als aktuelle Adressleiste geworfen. //http://localhost/<pfad>/<controllername>.html $attributes = array('class' => 'login', 'id' => 'login'); //form_open erzeugt sofern die csrf protection aktiviert ist einen token als input type hidden //mit einem zufälligen md5 hash dieser wird autom. vom Controller beim auswerten des Formulares überprüft echo form_open('login', $attributes)."\n\t\t"; //Erzeugen eines labels <label for="username">Domain:</label> echo form_label('Domain:', 'username')."\n\t\t"; //<br> ausgeben oder eben das was der Doctype verlangt. echo br()."\n\t\t"; //Attribute für den input //Set_value setzt den Wert auf den POST $username = array( 'name' => 'username', 'id' => 'username', 'placeholder' => 'Domain...', 'value' => set_value('username') ); echo form_input($username)."\n\t\t"; //br(2) heißt nichts anderes als gebe 2 x br aus. sprich <br><br> echo br(2)."\n\t\t"; echo form_label('Passwort:', 'password')."\n\t\t"; echo br()."\n\t\t"; $password = array( 'name' => 'password', 'id' => 'password', 'placeholder' => 'Passwort...', 'type' => 'password', 'value' => set_value('password') ); echo form_input($password)."\n\t\t"; echo br(2)."\n\t\t"; //Erstellen des Submit buttons mit dem namen "submit" und dem Text "Login" echo form_submit('submit', 'Login')."\n\t\t"; //schließen der form </form> echo form_close()."\n\t\t"; //Formular Ende echo br()."\n"; //Fehlermeldungen ausgeben //validation_errors() gibt true zurück sofern ein Fehler aufgetreten ist beim auswerten des formulares //$error ist hier unsere fehlermeldung die z.B. zurückgeworfen wird sofern der login nicht erfolgreich gewesen ist. if(validation_errors() || isset($error)){ ?> <div class="msg error"> <?php //Gibt hier aus welche Felder nicht geserzt wurden als menschenlesbarer Text echo validation_errors(); if(isset($error)){ echo "<p>".$error."</p>"; } ?> </div> <?php } ?> </div> </body> </html>
und zu letzt kommt unser Controller, in dem wir die sessions setzen werden wenn der login erfolgreich gewesen ist und anderes.
Der controller wird erstellt im Ordner /application/controllers/.
Dort legen wir einen neuen Controller unter dem Namen „login.php“ an.
<?php /** * Description of login * * @author Ilya Beliaev */ class login extends CI_Controller { public function __construct(){ parent::__construct(); //Laden der form helper $this->load->helper(array('form', 'html', 'url')); //Laden der form_validation library sowie der session library //Zur verwendung von sessions und form_validations $this->load->library(array('form_validation', 'session')); //Laden unseres models (/application/models/user.php) //Methoden des models können dann verwendet werden mit $this->user->[..]; $this->load->model("user"); } public function index(){ //Prüfen der session login_state auf ihren Wert if($this->session->userdata('login_state') == FALSE){ //Vorraussetzen welche input felder ausgefüllt sein müssen. wenn diese nicht ausgefüllt sind wird //validation_errors() im view die nicht ausgefüllten felder zurückliefern beim absenden des formulars //sobald die methode $this->form_validation->run() ausgeführt wird. $this->form_validation->set_rules('username', 'Domain', 'required'); $this->form_validation->set_rules('password', 'Passwort', 'required'); $data = array(); //Prüfen ob alle Felder ausgefüllt wurden, welche als required definiert wurden. if ($this->form_validation->run() == TRUE){ //Abfangen der Post-Parameter und zeitgleiches filtern auf xss und etc sofern die entsprechenden //sachen im der config auf true gestellt worden sind. $username = $this->input->post('username'); $password = $this->input->post('password'); //Logindaten überprüfen $user = $this->user->checkUserCredentials($username, $password); //Wenn der username zurück geliefert wurde die session login_state auf true setzen, //sowie den benutzernamen zur session "username" setzen. if($user != FALSE){ $newdata = array( 'username' => $user, 'login_state' => TRUE ); $this->session->set_userdata($newdata); //Weiterleiten zum controller main //http://localhost/<pfad>/main.html redirect("main"); }else{ //Setzen der variable $error im view $data["error"] = "Login fehlgeschlagen."; } } //Übergeben des data arrays an den view, welcher die keynamen in entsprechende Variablen umwandelt. //$data["error] zu $error und etc. $this->load->view('login', $data); }else{ //Falls der user bereits eingeloggt ist, dann direkt zum gesicherten Bereich weiterleiten. redirect("main"); } } } ?>
und nun das ganze spielchen mit dem main dem gesicherten Teil der Webseite.
/application/controllers/main.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); /* @var $this CI_Controller */ class Main extends CI_Controller { public function __construct(){ parent::__Construct(); $this->load->helper(array('form', 'html', 'url')); $this->load->library(array('session')); $this->load->model("news"); } public function index(){ //Check if user is logged in if($this->session->userdata('login_state') === TRUE){ //hier könnte man nun das entsprechende view laden. }else{ //Redirect to http://xyz.de/login.html redirect("login"); } } } ?>