Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [inny] YII - logowanie
Forum PHP.pl > Forum > PHP > Frameworki
skorpionek93
Witam.
Jestem nowy w temacie frameworków, YII jest pierwszym, którego się uczę więc jestem jeszcze lekko zagubiony. Przestudiowałem książke Helionu o YII (nie zachwyciła mnie szczerze mówiąc) ale nadala wielu rzeczy nie rozumiem. Moje pytanie dotyczy bezpieczeństwa takiego skryptu logowania:

Model:

  1. <?php
  2.  
  3. class Uzytkownicy extends CActiveRecord
  4. {
  5.  
  6. public function tableName()
  7. {
  8. return 'spr_uzytkownicy';
  9. }
  10.  
  11.  
  12. public function rules()
  13. {
  14.  
  15. return array(
  16. array('uzytkownik_login, uzytkownik_haslo', 'required'),
  17. array('uzytkownik_login, uzytkownik_haslo', 'length', 'max'=>50),
  18. );
  19. }
  20.  
  21.  
  22. public function relations()
  23. {
  24.  
  25. return array(
  26. );
  27. }
  28.  
  29. public function attributeLabels()
  30. {
  31. return array(
  32. 'uzytkownik_id' => 'ID',
  33. 'uzytkownik_login' => 'Login',
  34. 'uzytkownik_haslo' => 'Hasło',
  35. );
  36. }
  37.  
  38. public static function model($className=__CLASS__)
  39. {
  40. return parent::model($className);
  41. }
  42. }
  43. ?>


Widok

  1. <h1>Zaloguj się</h1>
  2. <div class="form">
  3.  
  4. <?php
  5.  
  6. $form=$this->beginWidget('CActiveForm', array(
  7.  
  8. 'id' => 'uzytkownicy-zaloguj-form',
  9. 'enableAjaxValidation' => FALSE
  10.  
  11. ));
  12.  
  13. ?>
  14.  
  15. <p>Pola oznaczone * są wymagane</p>
  16.  
  17.  
  18. <?php echo $form->errorSummary($Model); ?>
  19.  
  20. <div class="row">
  21. <?php echo $form->labelEx($Model, 'uzytkownik_login'); ?>
  22. <?php echo $form->textField($Model, 'uzytkownik_login'); ?>
  23. <?php echo $form->error($Model, 'uzytkownik_login'); ?>
  24. </div>
  25.  
  26. <div class="row">
  27. <?php echo $form->labelEx($Model, 'uzytkownik_haslo'); ?>
  28. <?php echo $form->passwordField($Model, 'uzytkownik_haslo'); ?>
  29. <?php echo $form->error($Model, 'uzytkownik_haslo'); ?>
  30. </div>
  31.  
  32. <div class="row buttons">
  33. <?php echo CHtml::submitButton('Zaloguj'); ?>
  34. </div>
  35.  
  36. <?php $this->endWidget(); ?>
  37.  
  38. </div>


Kontroler:

  1. <?php
  2.  
  3. class TestController extends Controller
  4. {
  5.  
  6. public function actionIndex()
  7. {
  8.  
  9. $this->pageTitle = 'Zaloguj';
  10.  
  11. $Model = new Uzytkownicy;
  12.  
  13.  
  14. if(isset($_POST['Uzytkownicy']))
  15. {
  16.  
  17. $Model->attributes = $_POST['Uzytkownicy'];
  18.  
  19. if($Model->validate())
  20. {
  21.  
  22. $Ile = $Model->count(
  23.  
  24. 'uzytkownik_login = :UzytkownikLogin AND uzytkownik_haslo = :UzytkownikHaslo',
  25.  
  26. ':UzytkownikLogin' => $Model->attributes['uzytkownik_login'],
  27. ':UzytkownikHaslo' => $Model->attributes['uzytkownik_haslo']
  28.  
  29. ));
  30.  
  31. if($Ile == 1)
  32. {
  33.  
  34. Yii::app()->session['zalogowany'] = 'tak';
  35.  
  36. $Rezultat = $Model->findAll(
  37.  
  38. 'uzytkownik_login = :UzytkownikLogin AND uzytkownik_haslo = :UzytkownikHaslo',
  39.  
  40. ':UzytkownikLogin' => $Model->attributes['uzytkownik_login'],
  41. ':UzytkownikHaslo' => $Model->attributes['uzytkownik_haslo']
  42.  
  43. )
  44.  
  45. );
  46.  
  47. foreach ($Rezultat as $RezultatWiersz)
  48. {
  49.  
  50. Yii::app()->session['root'] = $RezultatWiersz->uzytkownik_id;
  51.  
  52. }
  53. }
  54.  
  55.  
  56. }
  57.  
  58. }
  59.  
  60. $this->render('index', array(
  61.  
  62. 'Model' => $Model,
  63.  
  64. ));
  65.  
  66.  
  67. }
  68.  
  69. public function actionWyloguj()
  70. {
  71.  
  72. Yii::app()->session['zalogowany'] = '';
  73.  
  74. Yii::app()->session['root'] = '';
  75.  
  76. $this->redirect(array('test/'));
  77. }
  78.  
  79. }
  80. ?>


Ten skrypt jest napisany na podstawie tej właśnie książki (a raczej w większości z niej przepisany z moimi lekkimi zmianami).
Chciałbym się dowiedzieć czy tak przygotowany skrypt logowania jest bezpieczny i może być bez obaw zastosowany na stronach opublikowanych online?
Bo powiem szczerze, że mam lekkie obawy gdyż wiem ile trzeba sobie zadać trudu pisząc coś takiego w czystym PHP (głównie chodzi o walidację).
Przepraszam za tak laickie pytanie ale jestem mocno początkujący a do dyspozycji mam tylko tą książkę i dokumentację, która jest napisana dosyć ciężkim wg mnie językiem. Praktycznie nie ma innych źródeł w języku polskim.
Pozdrawiam i z góry dziękuję za odpowiedzi.
NetBeans
Wybrałeś dobry framework, więc korzystaj z jego dobrodziejstw. Nie pisz tego ręcznie, użyj UserIdentity, tak jak pokazują w manualu. Autoryzacja tam pokazana jest napisana na prawdę dobrze, nie ma co kombinować. Jeżeli wykonasz to tak, jak opisują w linku poniżej, możesz spać spokojnie.

Jeżeli nie jesteś pewien co do walidatorów, zawsze możesz przeczytać ich kod. Ja to zrobiłem, są napisane na prawdę dobrze.

http://www.yiiframework.com/doc/guide/1.1/pl/topics.auth

Btw, nie rób tak dużo "enterów" w kodzie, bo nie da się tego czytać. smile.gif
skorpionek93
Witam ponownie.
Po 2 dniach i przejrzeniu niezliczonej liczby stron i forów wreszcie naskrobałem działający formularz logowania ale, że jego bezpieczeństwo jest bardzo ważne to prosiłbym o sprawdzenie i pokazanie ewentualnych błędów/uchybień. Tam gdzie się da to nie będę wstawiał całego kodu tylko jego istotne fragmenty.

Kontroler:
  1. public function actionLogin()
  2. {
  3. $model=new LoginForm;
  4. if(isset($_POST['LoginForm']))
  5. {
  6. $model->attributes=$_POST['LoginForm'];
  7. if($model->validate() && $model->login())
  8. {
  9. $this->redirect(Yii::app()->user->returnUrl);
  10. }
  11. }
  12. $this->render('login',array('model'=>$model));
  13. }
  14.  
  15. public function actionLogout()
  16. {
  17. Yii::app()->user->logout();
  18. $this->redirect(Yii::app()->homeUrl);
  19. }


LoginForm.php
  1. <?php
  2. class LoginForm extends CFormModel
  3. {
  4. public $username;
  5. public $password;
  6. public $rememberMe;
  7.  
  8. private $_identity;
  9.  
  10. public function rules()
  11. {
  12. return array(
  13. array('username, password', 'required'),
  14. array('rememberMe', 'boolean'),
  15. array('password', 'authenticate'),
  16. );
  17. }
  18.  
  19. public function attributeLabels()
  20. {
  21. return array(
  22. 'rememberMe' => 'Remember me next time',
  23. 'username' => 'Użytkownik',
  24. 'password' => 'Hasło',
  25. );
  26. }
  27.  
  28. public function authenticate($attribute,$params)
  29. {
  30. $this->_identity=new UserIdentity($this->username,$this->password);
  31. if(!$this->_identity->authenticate())
  32. {
  33. $this->addError('password','Incorrect username or password.');
  34. }
  35.  
  36. }
  37.  
  38. public function login()
  39. {
  40. if($this->_identity===null)
  41. {
  42. $this->_identity=new UserIdentity($this->username,$this->password);
  43. $this->_identity->authenticate();
  44. }
  45. if($this->_identity->errorCode===UserIdentity::ERROR_NONE)
  46. {
  47. $duration=$this->rememberMe ? 3600*24*30 : 0;
  48. Yii::app()->user->login($this->_identity,$duration);
  49. return true;
  50. }
  51. else
  52. {
  53. return false;
  54.  
  55. }
  56. }
  57. }
  58. ?>


Uzytkownicy.php
  1. <?php
  2.  
  3. class Uzytkownicy extends CActiveRecord
  4. {
  5.  
  6. public function tableName()
  7. {
  8. return 'spr_uzytkownicy';
  9. }
  10.  
  11.  
  12. public function rules()
  13. {
  14. return array(
  15. array('uzytkownik_login, uzytkownik_haslo', 'required'),
  16. array('uzytkownik_login, uzytkownik_haslo', 'length', 'max'=>50),
  17. );
  18. }
  19.  
  20. public function relations()
  21. {
  22. return array(
  23. );
  24. }
  25.  
  26. public function attributeLabels()
  27. {
  28. return array(
  29. );
  30. }
  31.  
  32.  
  33. public static function model($className=__CLASS__)
  34. {
  35. return parent::model($className);
  36. }
  37. }


UserIdentity.php
  1. <?php
  2.  
  3. class UserIdentity extends CUserIdentity
  4. {
  5. private $_id;
  6. public function authenticate()
  7. {
  8. $user=Uzytkownicy::model()->findByAttributes(array('uzytkownik_login'=>$this->username));
  9. if($user===null)
  10. {
  11. $this->errorCode=self::ERROR_USERNAME_INVALID;
  12. }
  13. else if($user->uzytkownik_haslo != $this->password)
  14. {
  15. $this->errorCode=self::ERROR_PASSWORD_INVALID;
  16. }
  17. else
  18. {
  19. $this->_id=$user->uzytkownik_id;
  20. $this->username=$user->uzytkownik_login;
  21. $this->errorCode=self::ERROR_NONE;
  22. }
  23. return $this->errorCode==self::ERROR_NONE;
  24. }
  25.  
  26. public function getId()
  27. {
  28. return $this->_id;
  29. }
  30. }


Widok:
  1. <?php
  2. $form=$this->beginWidget('CActiveForm', array( //1
  3. 'id'=>'login-form', //2
  4. 'enableAjaxValidation'=>FALSE,
  5. )); ?>
  6. <div class="row">
  7. <?php echo $form->labelEx($model,'username'); ?>
  8. <?php echo $form->textField($model,'username'); ?>
  9. <?php echo $form->error($model,'username'); ?>
  10. </div>
  11.  
  12. <div class="row">
  13. <?php echo $form->labelEx($model,'password'); ?>
  14. <?php echo $form->passwordField($model,'password'); ?>
  15. <?php echo $form->error($model,'password'); ?>
  16. </div>
  17.  
  18. <div class="row rememberMe">
  19. <?php echo $form->checkBox($model,'rememberMe'); ?>
  20. <?php echo $form->label($model,'rememberMe'); ?>
  21. <?php echo $form->error($model,'rememberMe'); ?>
  22. </div>
  23.  
  24. <div class="row submit">
  25. <?php echo CHtml::submitButton('Login'); ?>
  26. </div>
  27.  
  28. <?php $this->endWidget(); ?>


Co o tym sądzicie? Wydaje mi się, że jest to strasznie chaotycznie napisane bo to jest zlepek kilku formularzy przerobionych przeze mnie. Szczególnie mnie zastanawia czy nie dałoby się połączyć LoginForm.php i Użytkownicy.php w jeden model? Tylko wtedy musiałby dziedziczyć jednocześnie po CActiveRecord i CFormModel co jest chyba niemożliwe? Byłbym bardzo wdzięczny za wskazówki co można poprawić aby ten interfejs logowania był prostszy i bezpieczny.

Mam jeszcze jedno pytanie.
W książce z Helionu autor zalecał dodanie w config/main.php czegoś takiego:
  1. 'session' => array(
  2. 'class' => 'CDbHttpSession',
  3. 'connectionID' => 'db',
  4. 'sessionTableName' => 'spr_session'
  5. ),


a w bazie danych tabelę sesji, w moim przypadku spr_session(id, expire, data).
Może mi ktoś wytłumaczyć do czego to słuzy i co to ma dać?
Bo testowałem zarówno z tym fragmentem jak i bez niego i firebug pokazuje dokładnie tak samo cookies sesyjne, a jedyna róznica jest w tym, że w tej tabeli zapisują się identyfikatory sesjii. Byłbym wdzięczny jakby mi to ktoś przy okazji wytłumaczył.

Pozdrawiam i z góry dziękuję za odpowiedzi.
Turson
Kiedyś napisałem tak
  1. <?php
  2. class LoginController extends Controller
  3. {
  4. public function actionIndex()
  5. {
  6. $model = new Login;
  7. if(isset($_POST['Login'])){
  8. // logowanie użytkownika za pomocą nazwy użytkownika oraz hasła
  9. $identity=new UserIdentity($_POST['Login']['login'],$_POST['Login']['password']);
  10. if($identity->authenticate()){
  11. $logged=true;
  12. Yii::app()->user->login($identity);
  13. }
  14. else{
  15. $logged="error";
  16. echo $identity->errorMessage;
  17. }
  18. }
  19. else $logged=false;
  20. $this->render('index', array(
  21. 'model'=>$model,
  22. 'logged'=>$logged,
  23. ));
  24. }
  25.  
  26. public function actionLogout(){
  27. Yii::app()->user->logout();
  28. $this->redirect('index.php');
  29. }
  30. }
  31.  
  32.  
  33. class UserIdentity extends CUserIdentity
  34. {
  35. private $_id;
  36. public function authenticate()
  37. {
  38. $record=Login::model()->findByAttributes(array('login'=>$this->username));
  39. if($record===null)
  40. $this->errorCode=self::ERROR_USERNAME_INVALID;
  41. else if($record->password!==sha1($this->password))
  42. $this->errorCode=self::ERROR_PASSWORD_INVALID;
  43. else
  44. {
  45. $this->_id=$record->id;
  46. $this->setState('login', $record->login);
  47. $this->errorCode=self::ERROR_NONE;
  48. }
  49. return !$this->errorCode;
  50. }
  51.  
  52. public function getId()
  53. {
  54. return $this->_id;
  55. }
  56. }

NetBeans
Wszystko fajnie, ale nie hashujesz hasła, albo ja nie widzę. Hasło trzymane w plain tekście to zło. Yii dostarcza fajny helper, który pomaga prawidłowo hashować hasła. Jego użycie jest banalnie proste. W zasadzie sprowadza się do wywołania dwóch metod, na pewno dasz sobie radę. Hasła hashowane metodami tej klasy są hashowane z użyciem mcryptu bodajże, a więc aktualnego standardu polecanego przez samo php.net.

http://www.yiiframework.com/doc/api/1.1/CPasswordHelper

Jeżeli chodzi o połączenie modelu Użytkownicy i LoginForm, to jest to niemożliwe. Dla początkujących w tym FW może wydać się to pogmatwane, ale ogólna zasada brzmi, że kiedy zapisujesz dane w bazie używaj ActiveRecord, a kiedy tylko pobierasz a potem porzucasz (np. formularz kontaktu, gdzie pobierasz dane, wysylasz maila i je porzucasz) to FormModel.

Sesje na bazie to fajna sprawa, szczególnie na współdzielonych hostingach. Nie będę tu teraz o tym mówił, bo to dość rozległy temat, ale: jeżeli chcesz większe bezpieczeństwo, użyj bazy danych.
Ciastko zawsze będzie, bo zawsze jest porównanie id z ciastka == id z bazy || id z ciastka == id na serwerze (gdzie normalnie trzymane są dane sesji).

Jeżeli masz jeszcze jakieś pytania, to pisz.

PS. Pod żadnym pozorem nie używaj SHA1 jak kolega wyżej oraz nie realizuj hashowania z UserIdentity, rób to w modelu.
skorpionek93
Jeśli chodzi o hashowanie to w kilku przykładach było realizowane za pomocą md5 ale je wyciąłem chwilowo żeby kod był czytelniejszy (podczas kombinowania z niedziałającym kodem im mniej znaków tym lepiej) a później zapomniałem dokleić ale oczywiście.
Dziękuję za pomoc smile.gif
To moje początki z Yii więc pewnie jeszcze niejedno pytanie zadam w tym dziale tongue.gif
Pozdrawiam

Edit://
Czyli rozumiem, że hashowanie najlepiej wykonać tutaj:

  1. // LoginForm.php
  2.  
  3. public function authenticate($attribute,$params)
  4. {
  5. $this->_identity=new UserIdentity($this->username,$this->password);
  6. if(!$this->_identity->authenticate())
  7. {
  8. $this->addError('password','Incorrect username or password.');
  9. }


czyli przed przekazaniem hasła do UserIdentity? Dobrze myślę?
NetBeans
W zasadzie pasowałoby w modelu użytkownika, ale żeby zbytnio nie kombinować, to proponuję tak:

UserIdentity
  1. // ...
  2. public function authenticate() {
  3. $user = User::model()->findByAttributes(array('username' => $this->username));
  4. if ($user === null) {
  5. return false;
  6. } else if (!CPasswordHelper::verifyPassword($this->password, $user->initialPassword)) { // tutaj weryfikujemy zahashowane już hasło
  7. return false;
  8. } else {
  9. $this->_id = $user->id_user;
  10. $this->username = $user->username;
  11. $this->errorCode = self::ERROR_NONE;
  12. return true;
  13. }
  14. }
  15. // ...
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.