Auth w Kohana Framework [KO3]

Auth w Kohana Framework [KO3]

Written by sbl

Topics: Framework, Kohana 3, Programowanie

Auth module w Kohana 3 to nic innego jak zbiór bibliotek wymaganych do obsługi autoryzacji użytkowników, lecz to zapewne już wiesz. W tym wpisie przedstawię sposób zastosowania modułu do rejestracji i logowania użytkowników.

Zaczynamy! Pierwszym krokiem jest oczywiście włączenie modułów auth i orm w pliku bootstrap.php (application/bootstrap.php)
Odnajdujemy tablicę z modułami i usuwamy komentarz z początku lini.

'auth'    =>    MODPATH.'auth',
'orm'    =>    MODPATH.'orm',

Tworzenie struktury bazy danych

Zakładam, że skonfigurowałeś już plik konfiguracyjny database.php do obsługi połączeń z MySQLem. Jeśli nie, zrób to czym prędzej.
Ok, teraz musimy poniższy kod struktury tabel użytkowników i ról dodać do naszej bazy (np, z użyciem phpMyAdmin’a).

CREATE TABLE IF NOT EXISTS `roles` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `description` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `uniq_name` (`name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

INSERT INTO `roles` (`id`, `name`, `description`) VALUES(1, 'login', 'Login privileges, granted after account confirmation');
INSERT INTO `roles` (`id`, `name`, `description`) VALUES(2, 'admin', 'Administrative user, has access to everything.');

CREATE TABLE IF NOT EXISTS `roles_users` (
  `user_id` int(10) UNSIGNED NOT NULL,
  `role_id` int(10) UNSIGNED NOT NULL,
  PRIMARY KEY  (`user_id`,`role_id`),
  KEY `fk_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `email` varchar(127) NOT NULL,
  `username` varchar(32) NOT NULL DEFAULT '',
  `password` char(50) NOT NULL,
  `logins` int(10) UNSIGNED NOT NULL DEFAULT '0',
  `last_login` int(10) UNSIGNED,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `uniq_username` (`username`),
  UNIQUE KEY `uniq_email` (`email`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `user_tokens` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` int(11) UNSIGNED NOT NULL,
  `user_agent` varchar(40) NOT NULL,
  `token` varchar(32) NOT NULL,
  `created` int(10) UNSIGNED NOT NULL,
  `expires` int(10) UNSIGNED NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `uniq_token` (`token`),
  KEY `fk_user_id` (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

ALTER TABLE `roles_users`
  ADD CONSTRAINT `roles_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  ADD CONSTRAINT `roles_users_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE;

ALTER TABLE `user_tokens`
  ADD CONSTRAINT `user_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE;

Czym są role użytkowników?

Role to nic innego jak grupy, do których przypisywany jest użytkownik z tabeli users. Za pomocą ról możemy później decydować do jakich zasobów może mieć dostęp dana grupa użytkowników.
Standardową rolą jest rola „login„, która pozwala nam na logowanie. Jeśli chcemy stworzyć administratora musimy dodać go do roli login oraz admin tak by miał dostęp do panelu administracyjnego, ale o tym w przyszłych wpisach.

Kontroler obsługi użytkowników

Przedstawię najprostszy kontroler obsługi użytkowników (register|login|logout).
W tym celu tworzymy plik user.php w folderze application/classes/controller/
Oto jego zawartość:

<?php defined('SYSPATH') or die('No direct script access.');

class Controller_User extends Controller_Template { //dziedziczenie z Controller_Template
	var $template; //definiowanie zmiennej do obsługi widoków

	public function action_index(){
		$this->template = View::factory('user/main'); //załadowanie widoku
		$auth = Auth::instance(); //utworzenie instancji obiektu Auth
		$whoami $auth->get_user(); //pobieranie danych o zalogowanym użytkowniku

		if ($auth->logged_in('login')){ //sprawdzanie czy użytkownik jest zalogowany
		//jeśli jest to dopuszczamy (to sekcja dla zalogowanych)

			$this->template->wiadomosc = "Witaj $whoami->username!<br />Jesteś zalogowany.";
			//i cała reszta kodu dla autoryzowanego użytkownika

		}else{ //jeśli nie jest zalogowany, przekierowujemy do logowania
			$this->request->redirect('user/login'); //jeśli nie, przekierowujemy do logowania
		}
	}

	public function action_register(){
		$this->template = View::factory('user/register'); //załadowanie widoku
		$auth = Auth::instance(); //utworzenie instancji obiektu Auth

		if ($auth->logged_in('login')){ //sprawdzanie czy użytkownik jest zalogowany
			$this->request->redirect('user/login'); //jeśli nie, przekierowujemy do logowania
		}else{

			if($_POST){ //sprawdzanie czy dane są przesyłane POSTem
				$walidacja = new Validate($_POST); //tworzenie obiektu walidacji
				$walidacja->rule('login', 'not_empty')
						->rule('login', 'alpha_dash')
						->rule('email', 'not_empty')
						->rule('haslo', 'not_empty')
						->rule('email', 'email');

				if($walidacja->check()){
					$user = ORM::factory('user'); //tworzenie obiektu ORM z użyciem tabeli users
					$user->username = $_POST['login']; //przypisanie pola z formularza do nazwy kolumny w tabeli
					$user->email = $_POST['email'];
					$user->password = $_POST['haslo'];

				//instrukcja warunkowa/zapis danych użytkownika/przypisanie roli "login"
                if($user->save() && $user->add('roles', ORM::factory('role', array('name' => 'login'))) ){
					$this->template->sukces = 'Dziękujemy za rejestrację!'; //przekazanie zmiennej $sukces do widoku
				}else{
					$this->template->fail = 'Nie udało się dodać użytkownika!'; //przekazanie zmiennej $fail do widoku
				}

				}else{
				$this->template->fail = 'Uzupełnij poprawnie formularz rejestracyjny!';
				}
			}

		}
	}

	public function action_login(){
		$this->template = View::factory('user/login');
		$auth = Auth::instance(); //utworzenie instancji obiektu Auth

		if ($auth->logged_in('login')){ //sprawdzanie czy użytkownik jest zalogowany
			$this->request->redirect('user'); //jeśli jest, przekierowujemy do user
		}else{

			if($_POST){
				$walidacja = new Validate($_POST); //tworzenie obiektu walidacji
				$walidacja->rule('login', 'not_empty')
						->rule('login', 'alpha_dash')
						->rule('haslo', 'not_empty');

				if($walidacja->check()){ //jeśli walidacja OK to zaloguj
					$auth->login($_POST['login'], $_POST['haslo'], FALSE); //logowanie użytkownika
					$this->request->redirect('user'); //przekierowanie po zalogowaniu do kontrolera user
				}else{
					$this->template->fail = 'Uzupełnij poprawnie formularz!';
				}

		}

		}
	}

	public function action_logout(){
		$this->template = View::factory('user/login');
		$auth = Auth::instance(); //utworzenie instancji obiektu Auth
        if ($auth->logged_in('login')){ //sprawdzanie czy użytkownik jest zalogowany
            if($auth->logout(TRUE)){ //jeśli jest, to go wylogowujemy
                $this->template->sukces = 'Pomyślnie wylogowano!';
            }else{
                $this->request->redirect('user/login'); //przekierowujemy do logowania
            }
        }else{ //jeśli nie jest zalogowany przekierowujemy do logowania
            $this->request->redirect('user/login');
        }
	}

Uff… piszę to z głowy więc miejmy nadzieję, że wszystko zadziała. Przetestuję to innym razem (jest 03:04). W razie czego czekam na komentarze ;)

Ok kontroler z akcjami już mamy. Jak zauważyliście na początku każdej akcji ładowałem inny widok. Należy teraz stworzyć widoki, które opisałem w komentarzach.

Widoki i Krajobrazy

Widok to niestety nie krajobraz ;) To ogólnie pojęte pliki z kodem (wyglądem) naszej aplikacji. Zgodnie z architekturą danych MVC (Model-View-Controller) należy oddzielać mechanizmy aplikacji od widoku.

Krótka rozpiska, w powyższym kontrolerze użyliśmy następujących widoków, które musimy za chwilę stworzyć.

  • application/views/ user/main.php
  • application/views/ user/register.php
  • application/views/ user/login.php

Przedstawię tylko minimalistyczny obraz jak to wszystko działa, nie będę tworzył nagłówków HTML ani używał, żadnego szablonu CSS.
Oto nasze widoki:

main.php

<?php
	if(isset($wiadomosc)){
		echo '<h2>'.$wiadomosc.'</h2>';
	}
?>

register.php

<?php //wyświetlanie komunikatów, które przekazaliśmy w kontrolerze
	if(isset($sukces)){
		echo '<h2 style="color:green;">'.$sukces.'</h2>'."\n";
	}elseif(isset($fail)){
		echo '<h2 style="color:red;">'.$fail.'</h2>'."\n";
	}
?>
<?php
	echo Form::open()."\n";
?>
	<table width="600" border="0" cellpadding="3">
<?php
	echo '<tr><td>Nazwa uzytkownika:</td><td>'.Form::input('login').'</td></tr>'."\n";
	echo '<tr><td>Hasło:</td><td>'.Form::input('haslo').'</td></tr>'."\n";
	echo '<tr><td>Email:</td><td>'.Form::input('email').'</td></tr>'."\n";
        echo '<tr><td></td><td>'.Form::submit('rejestruj').'</td></tr>'."\n";
?>
	</table>
<?php
	echo Form::close()."\n";
?>

login.php

<?php //wyświetlanie komunikatów, które przekazaliśmy w kontrolerze
	if(isset($sukces)){
		echo '<h2 style="color:green;">'.$sukces.'</h2>'."\n";
	}elseif(isset($fail)){
		echo '<h2 style="color:red;">'.$fail.'</h2>'."\n";
	}
?>
<?php
	echo Form::open()."\n";
?>
	<table width="600" border="0" cellpadding="3">
<?php
	echo '<tr><td>Nazwa uzytkownika:</td><td>'.Form::input('login').'</td></tr>'."\n";
	echo '<tr><td>Hasło:</td><td>'.Form::input('haslo').'</td></tr>'."\n";
	echo '<tr><td></td><td>'.Form::submit('zaloguj').'</td></tr>'."\n";
?>
	</table>
<?php
	echo Form::close()."\n";
?>

Jak widać, nie wysiliłem się zbytnio nad widokami ale nie one są tutaj najważniejsze.

Jeśli wszystko zrobiliśmy poprawnie, nasz kontroler powinien działać bez zarzutów. Mile widziane opinie i uwagi do wpisu.
Do rejestracji możemy dopisać potwierdzenie konta przez email, ale o tym w przyszłych wpisach :)

Polecam spis przydatnych metod w KO3

  • http://villentre.net/blog Villentre Vearee

    Witam.
    Mam parę uwag co do kodu, jednakże nie wiem jak jest z formatowaniem u Ciebie w komentarzach – użyję zatem MD – powinno być zrozumiale…

    * `var $template;` – Trochę to brzydko wygląda… I jest całkowicie nie potrzebne, gdy dziedziczysz po Controller_Template…
    * `if ($auth->logged_in(‘login’)){ //sprawdzanie czy użytkownik jest zalogowany` – Nie wiem jak jest w K3, jednakże w K2 jeśli user nie miał roli `login`, to nie mógł się wcale zalogować (poza tym, takie sprawdzenie generuje dodatkowe 1 zapytanie więc ja bym się go wystrzegał). Tu w zupełności wystarcza `if($auth->logged_in())` – działanie takie samo, a jednak o to jedno zapytanie mniej.
    * `if ($auth->logged_in(‘login’)){ //sprawdzanie czy użytkownik jest zalogowany
    $this->request->redirect(‘user/login’); //jeśli nie, przekierowujemy do logowania
    }`
    To nie ma sensu – jeśli user jest zalogowany, to przekierujmy go na stronę logowania, gdzie znów sprawdzimy czy jest zalogowany, a ponieważ jest zalogowany, to przekierujemy go jeszcze gdzie indziej (gdzie znów sprawdzimy czy ma rolę `user`…
    Poza tym, w rejestracji user raczej nie powinien być zalogowany, więc komentarz *jeśli nie* jest nie poprawny.
    * Użycie walidacji w rejestracji jest nie potrzebne.
    `$user = ORM::factory(‘user’);
    $user->values($_POST);
    if($user->check())
    $user->save();
    //dodanie roli
    )`
    prosto, a skutecznie. Lepiej od Twoich własnych, bo sprawdzają np. długość wprowadzanych ciągów, etc.
    * Po użyciu walidacji nie odwołuj się do danych po przez `$_POST`, tylko `$array = $vaalidate->as_array();` – to uwzględnia takie rzeczy jak np. filtry (który, tak btw. by Ci się przydał, np. trim).
    * Nigdzie nie wspomniałeś o możliwości zapamiętania logowania.
    * Nie wspomniałeś także co robi `logout(true)`, a co `logout(false)` – co w Twoim przypadku, dodatkowo mija się z celem – `logout(true)` usuwa tokeny powstałe przy zapamiętywaniu logowania.

    Trochę długo wyszło, ale wybacz, ja już taki jestem – *upierdliwy*… (:
    Mam nadzieję, że niczego istotnego nie pominąłem oraz, że w swoich wypocinach, czegoś głupiego nie napisałem.

    Pozdrawiam,
    V.V.

  • sbl

    Pisałem to o 3:00 :D z głowy, nie czytając tego ponownie. Nawet nie wiem czy działa (powinno). Nie mniej jednak dzięki za uwagi.
    Co do var $template; Zdefiniowałem bym mógł dla każdej akcji ładować inny widok.

    Starałem się przedstawić to jak najbardziej czytelnie dla początkującego użytkownika. Bo jak ja zaczynałem, przykłady z nieoficjalnej dokumnetacji KO3 nie były do końca zrozumiałe.

    Co do logout(true)/(false) to faktycznie zapomniałem opisać z czym to się je, ale to obecnie nie jest najbardziej istotne.

    Dzięki za komentarz ;)

  • http://www.sibul.linux.pl/programowanie-2/phpmailer-w-kohana-framework-ko3-169.html Linux Blog » phpmailer w Kohana Framework [KO3]

    [...]   ←Auth w Kohana Framework [KO3] [...]

  • wojtek

    Witam

    swietny artykul. Bardzo mi pomogl bo wlasnie zaczynam a kohana;)

    Pozdrawiam
    w.