Proste logowanie

Chciałbym zaprezentować jak w Kohanie serii 3.1 zaimplementować proste logowanie.

Nasze logowanie będzie sprawdzało czy w sesji jest zmienna przechowująca id użytkownika (wykorzystamy zrobiony wcześniej kontroler default), jeżeli będzie to po autentykacji (sprawdzeniu tożsamości użytkownika) przypisze (przy wykorzystaniu ORM) do obiektu user dane użytkownika z bazy.

Zakładamy, że w tabeli users w bazie będzie również pole type określające prawa użytkowników (user/admin). Tabela w bazie users powinna wyglądać tak:

+----+-------+----------------------------------+-------+-------+
| id | nick  | pass                             | name  | type  |
+----+-------+----------------------------------+-------+-------+
|  1 | admin | 21232f297a57a5a743894a0e4a801fc3 | Admin | admin |
|  2 | user  | ee11cbb19052e40b07aac0ca060c23ee | Name  | user  |
+----+-------+----------------------------------+-------+-------+

Do wyciągania obiektu user użyjemy ORM, dlatego należy stworzyć model user
/application/classes/model/user.php

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Model_User extends ORM {

    public function  __construct($id = NULL) {
        parent::__construct($id);
    }
}
?>

Obiekt user (po sprawdzeniu czy w sesji jest id użytkownika) będzie generowany przy wywołaniu konstruktora kontrolera default (za każdym wywołaniem strony).

Można by przechowywać cały obiekt w sesji z prawami, aczkolwiek w sytuacji gdy użytkownik zalogowałby się, miałby do momentu wylogowania niezmienione prawa (jeśli zostałby zbanowany lub zmodyfikowany w tym czasie to dopiero po przelogowaniu jego prawa by się zaktualizowały).

Można by prawa użytkowników przechowywać w innej tabeli, tak aby podczas wykonywania pewnych czynności sprawdzane było prawo dla grupy użytkowników, jednakże i w tym przypadku aktualizacja praw byłaby tylko obrębie danej grupy (jeśli zmienilibyśmy grupę użytkownika z admin na user, to dopiero po przelogowaniu otrzymałby odpowiednie prawa).
/application/classes/controller/default.php

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

class Controller_Default extends Controller_Template {

    public $template = 'default';
    public $redirect = NULL;
    protected $SES;
    public $user;
    
    public function  __construct(Request $request, Response $response) {
        parent::__construct($request, $response);

        //Create sesion
        if(!isset ($_SESSION)){
            $this->SES = Session::instance();
        }

        //Save user id to session
        if(!$this->SES->get('user')){
            $usr = ORM::factory('user');
            $this->SES->set('user', $usr->id);
        }

        //Create user
        $this->user = ORM::factory('user', $_SESSION['user']);
    }

    public function before() {
      parent::before();

        if ($this->auto_render)
        {
            $this->template->title = '';
            $this->template->description = '';
            $this->template->content = '';

            $this->template->styles = array();
            $this->template->scripts = array();
            $this->template->top_tab = '';

        if($_SESSION['user']){
            $this->template->user = $this->user;
        }
        
        if($this->request->action() !== 'login'){
            $this->SES->delete('prev_url');
        }
        }
    }

    public function after() {
        if($this->redirect !== NULL)
        {
            Request::initial()->redirect($this->redirect);
        }
        if ($this->auto_render)
        {
                $styles = array(
                        'media/css/templatemo_style.css' => 'screen',
                );

                $scripts = array(
                        'media/js/jquery-1.5.1.min.js',
                );

                $this->template->styles = array_merge( $this->template->styles, $styles );
                $this->template->scripts = array_merge( $this->template->scripts, $scripts );
        }
        parent::after();
    }

    public function action_index() {
        $this->template->title = __('Home');
        $this->template->content='home';
        $this->template->top_tab='home';
    }
}
?>

Przeanalizujmy zatem kod kontrolera default (/application/classes/controller/default.php):
Rozszerza on kontroler template, zatem aby wczytać nowy widok do zmiennej template przypisujemy widok default (/application/views/default.php)

class Controller_Default extends Controller_Template {
    public $template = 'default';

Deklarujemy zmienną do przekierowań (redirect), do sesji (SES) i dla przechowywania obiektu użytkownika (user)

public $redirect = NULL;
protected $SES;
public $user;

W konstruktorze sprawdzamy czy nie ma utworzonej sesji, jeśli nie ma to tworzymy.

if(!isset ($_SESSION)){
    $this->SES = Session::instance();
}

Następnie sprawdzamy czy w sesji nie ma użytkownika, jeśli nie ma to wyciągamy tabelę user z bazy i zapisujemy do sesji user id

if(!$this->SES->get('user')){
    $usr = ORM::factory('user');
    $this->SES->set('user', $usr->id);
}

Na końcu konstruktora przypisujemy do obiektu user użytkownika o id które znajduje się w sesji.

$this->user = ORM::factory('user', $_SESSION['user']);

W funkcji before() deklarujemy i przesyłamy do szablonu puste zmienne odpowiedzialne za tagi meta, style, skrypty itp. Jeśli w sesji znajduje się id użytkownika, przesyłamy również obiekt użytkownika do widoku.

if($_SESSION['user']){
    $this->template->user = $this->user;
}

W sesji zapisywany jest także adres poprzedniej strony. Wykorzystywane jest to do tego, aby po zalogowaniu przejść do wcześniejszej strony.

if($this->request->action() !== 'login'){
    $this->SES->delete('prev_url');
}

W funkcji after() sprawdzamy czy przekierowanie jest puste, jeśli jest, przesyłamy style i skrypty do widoku, jeśli ustawiliśmy przekierowanie, zostajemy przeniesieni na daną stronę

if($this->redirect !== NULL){
    Request::initial()->redirect($this->redirect);
}

Akcji index chyba nie trzeba przedstawiać (wykonuje się ona standardowo po wywołaniu kontrolera z linku)

Czas na kontroler do logowania i wylogowywania.

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

class Controller_Auth extends Controller_Default {

	public function action_login()
	{
            $post = $_POST;
            $error = NULL;
            
            if(isset($post['submit'])){

                $user = ORM::factory('user')
                    ->where('nick', '=', $post['nick'])
                    ->and_where('pass', '=', md5($post['pass']))
                    ->find_all();

                if(count($user)){
                    $this->user = $user->current();
                    $_SESSION['user']=$this->user->id;
                    $this->redirect = $this->SES->get('prev_url');
                }else{
                    $error = '<span style="color: red">'.__('Incorrect login or password').'</span><br />';
                }
            }else{
                if(!isset($_SESSION['prev_url'])){
                    if(strpos(Request::initial()->referrer(), Url::base(FALSE,TRUE))===FALSE){
                        $this->SES->set('prev_url', '');
                    }else{
                        $this->SES->set('prev_url', Request::initial()->referrer());
                    }
                }
            }

            
            $this->template->title   = __('Login');
            $this->template->content = 'login';
            $this->template->data=NULL;
            if(isset ($_POST['submit']))
            $this->template->data=$_POST;
            $this->template->error = $error;
	}
        
        public function action_logout()
	{
            $this->template->title   = __('Logout');
            $this->SES->destroy();
            $this->redirect = '';
	}

}
?>

W akcji login() przypisujemy zmienne z posta do zmiennej $post i ustawiamy pusty komunikat błędu. Sprawdzamy czy nacisnęliśmy przycisk submit.

public function action_login()
{
    $post = $_POST;
    $error = NULL;
 
    if(isset($post['submit'])){

Jeśli wysłaliśmy formularz, do zmiennej user przypisujemy użytkownika którego nick i skrót md5 hasła, podany w formularzu logowania, zgadza się z tym z bazy.

$user = ORM::factory('user')
    ->where('nick', '=', $post['nick'])
    ->and_where('pass', '=', md5($post['pass']))
    ->find_all();

Sprawdzamy czy w bazie znajduje się wpis o takich wartościach, jeśli tak, do zmiennej user w kontrolerze default przesyłamy obiekt użytkownika ($this->user = $user->current();), zapisujemy do sesji id i przekierowujemy na poprzedni url. Jeśli nie znalazło użytkownika wyświetlamy info o złym loginie lub haśle

if(count($user)){
    $this->user = $user->current();
    $_SESSION['user']=$this->user->id;
    $this->redirect = $this->SES->get('prev_url');
}else{
    $error = '<span style="color: red">'.__('Incorrect login or password').'</span><br />';
}

W przypadku gdy nie ma zmiennej submit (nie został wysłany formularz), sprawdzane jest czy w sesji jest poprzedni adres, jeśli nie ma zapisujemy z jakiej strony przybyliśmy na formularz z logowaniem.

}else{
    if(!isset($_SESSION['prev_url'])){
        if(strpos(Request::initial()->referrer(), Url::base(FALSE,TRUE))===FALSE){
            $this->SES->set('prev_url', '');
        }else{
            $this->SES->set('prev_url', Request::initial()->referrer());
        }
    }
}

Ustawiamy także tytuł, wczytujemy do zmiennej content widok login, przesyłamy puste, lub wysłane formularzem dane, a także komunikat błędu.

$this->template->title   = __('Login');
$this->template->content = 'login';
$this->template->data=NULL;
if(isset ($_POST['submit']))
$this->template->data=$_POST;
$this->template->error = $error;

W akcji logout() niszczymy sesje i przekierowujemy na stronę główną

public function action_logout()
	{
            $this->template->title   = __('Logout');
            $this->SES->destroy();
            $this->redirect = '';
	}

Widok z formularzem do logowania może wyglądać tak
/application/views/login.php

<h1><?php echo $title?></h1>
<?php
echo  Form::open('/auth/login').
    '<fieldset>'.
        '<legend>'.__('Fill the fields').'</legend>'.
        $error.
        Form::label('nick', __('Nick').':').
        Form::input('nick', $data['nick']).'<br />'.

        Form::label('pass', __('Password').':').
        Form::password('pass', $data['pass']).'<br />'.
    '</fieldset>'.
    Form::submit('submit', __('Login')).
Form::close();?>

O czym należy pamiętać?
W kontrolerach to czy użytkownik jest zalogowany należy sprawdzać na przykład tak:

if($this->user->type==='user')
{//zalogowany}

lub

if($this->SES->user)
{//zalogowany}

To czy jest zalogowany i jest adminem (ma prawo wykonywać daną czynność)

if($this->user->type==='admin')
{//prawa administratora}

W widokach aby wyświetlić dane zalogowanego użytkownika należy odwołać się do zmiennej user

echo __('Name').': '.$user->name;

Tak oto stworzyliśmy logowanie do systemu. Trzeba jednak pamiętać o tym, żeby sprawdzać w każdej akcji, która tego wymaga, to czy użytkownik jest zalogowany i jakie ma prawa przed wykonaniem odpowiednich operacji.
Logowanie może odbywać się na różne sposoby, przedstawione rozwiązanie to sposób na proste logowanie. Do logowania można wykorzystać również moduł Auth ale to temat na osobny wpis.

15 Odpowiedzi :“Proste logowanie”

  1. O kuźwa doczekałem się nowego wpisu :) Czekam na więcej bo dokumentacja 3.1 jest żałosna już się zastanawiałem, czy przypadkiem nie zacząć od 2.5

  2. Chciałbym zobaczyć wykorzystanie modułu Auth.

  3. Mariusz napisał:

    Dla nowych projektów powinno się wykorzystywać najaktualniejszą wersję: KO3.1, nie zalecam używania starej, gdyż nie jest już rozwijana. Wpis o Auth postaram się dzisiaj zamieścić.

  4. Perry napisał:

    Dzięki za ciekawe wpisy.
    Mam pytanie odnośnie tego przykładu: po wylogowaniu i kliknięciu w przycisk wstecz przeglądarki ponownie wyświetla się z cache’a przeglądarki strona która powinna być dostępny tylko dla zalogowanych użytkowników. W którym miejscu należałoby dodać te headery:
    header(„Cache-Control: no-cache, must-revalidate”); // HTTP/1.1
    header(„Expires: Sat, 26 Jul 1997 05:00:00 GMT”); // Date in the past
    aby zadziałały?

    • Może to arecheologia, ale że problem dość istotny podziele się swoim rozwiązaniem:
      bootstrap.php
      //set the headers here
      header(‚Cache-Control: no-cache, no-store, must-revalidate’);
      // HTTP 1.1.
      header(‚Pragma: no-cache’);
      // HTTP 1.0.
      header(‚Expires: 0′);
      // Proxies.

  5. Mariusz napisał:

    Ta sytuacja występuje nie tylko w tym przykładzie. Jak znajdę (lub ktoś) rozwiązanie to napisze.

  6. Witam,

    mam parę pytań do tej metody logowania,
    Jak ograniczyć np. edytowanie newsow tylko dla type=admin , oraz
    jak wywołać wszystkie dane z bazy zalogowanego użytkownika, czy to po prostu wyciągać ORM’em wszystko podająć jego Id ktory jest w sesji, czy jest jakas inna metoda ?

    Dzięki,
    Pozdrawiam

  7. Mariusz napisał:

    Odpowiem od końca. Nie trzeba w kontrolerach tworzyć nowego obiektu ORM na podstawie sesji, taki obiekt jest tworzony w konstruktorze i przypisywany do atrybutu user klasy Default. Odwołujemy się do niego przez wskaźnik $this->

    $this->user
    

    Aby wyciągnąć np. nick zalogowanego użytkownika dajemy w konstruktorze

    $this->user->nick
    

    Aby ograniczyć dodawanie tylko administratorowi dajemy

    if($this->user->type==='admin')
    {//prawa administratora}
    

    Lub możemy zadeklarować dodatkowy atrybut klasy Default ($admin)

    public $user;
    public $admin;
    

    A następnie w konstruktorze przypisać wartość (przechowującą info czy jesteśmy adminiem):

    $this->admin=$usr->type=='admin' ? TRUE : FALSE;
    

    W kontrolerze sprawdzamy

    if($this->admin)
    {//prawa administratora}
    
  8. A kiedy lepiej stosować moduł auth, kiedy on będzie przydatny, uzywać go zawsze gdy mamy styczność z rejestracją użytkowników, czy tylko gdy mamy dość szeroki podział ról ?

  9. Mariusz napisał:

    Moduł Auth ma kilka funkcji jak sprawdzanie zalogowania, pobieranie użytkownika, sprawdzanie ról, czy inne mechanizmy. Jeśli nie korzystamy z tego to wystarczy takie proste logowanie.

  10. Kuba napisał:

    nie działa mi ten przykład(mam wersję 3.2).
    po wpisaniu mojastrona.pl/ wyrzuca mi jedynie Homehomehome
    Nie rozumiem jak Controller_Default współpracuje z Controller_Auth

    • Mariusz napisał:

      W kontrolerze Default zmień w akcji index

      $this->template->content='home';
      

      Na

      $this->template->content = View::factory('home');
      

      Analogicznie w Auth.

      Można też skorzystać z już gotowego, również prostego modułu do logowania, to takiej samej nazwie: Auth, Logowanie z Auth

      Tam też polecam wczytywać widoki tak, jak teraz wspomniałem (w kontrolerze ładujemy widok do zmiennej, a w widoku tylko echo), a nie przez szukanie pliku w widoku, tak jak to pisałem w pierwszych wpisach:

      <?php include Kohana::find_file('views', $content);?>
      

      Człowiek uczy się na błędach :)

  11.     public function  __construct($id = NULL) {
            parent::__construct($id);
        }
    

    mam pytanie. czemu sluzy ten fragment?

    • Mariusz napisał:

      To jest konstruktor modelu, możemy w nim coś robić podczas tworzenia obiektu. W takiej postaci jednak nic nie robi i można go całkiem usunąć.

Dodaj komentarz

Dodając kod PHP używaj tagów: [php][/php]

*