zamezení vícenásobného přihlášení jednoho uživatele

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
bagr_001
Člen | 3
+
0
-

Ahoj, v aplikaci řeším unikátní přihlašování uživatelů. Resp. aby se na jeden login mohl současně uživatel přihlásit jen jednou. Pokud je již přihlášen a přihlásí se podruhé, na prvním místě se odhlásí. vymyslel jsem následující řešení: Po přihlášení vygeneruji unikátní hash, který se uloží do DB a do session. Při každém refreshi znovu načtu identitu uživatele (vč. hashe) a zkontroluji s tou v session. Pokud se neshodují, uživatele to odhlásí. Myslíte že je mé řešení dobré, nebo by se to dalo udělat „elegantněji“?

Díky.

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
    protected $section;

    protected $uzivatele;

    protected function startup() {
		parent::startup();
		$this->section = $this->session->getSection('MySection');
		$this->uzivatele = $this->context->uzivatele;
		if($this->user->isLoggedIn()){
			$this->reloadUserData();
			if(!$this->isHashValid()){
				$this->user->logout(true);
				unset($this->section->loginHash);
				$this->flashMessage('Byl jste odhlášen. Tento účet byl použit z jiného počítače!');
				$this->redirect('Homepage:');
			}
		}
    }

    public function reloadUserData(){
		$user_data = $this->uzivatele->findById($this->user->id);
		unset($user_data->heslo);
		$this->user->storage->setIdentity(new NS\Identity($user_data->id_uzivatele, NULL, $user_data->toArray()));
    }

    public function isHashValid(){
		return $this->section->loginHash == $this->user->identity->data['loginhash'];
    }
}
class SignPresenter extends BasePresenter
{
	..

    public function signInFormSubmitted(Form $form)
    {
		try {
			$user = $this->getUser();
			$values = $form->getValues();
			if ($values->persistent) {
				$user->setExpiration('+30 days', FALSE);
			}
			$user->login($values->email, $values->heslo);
			$hash = Uzivatele::generateLoginHash();
			$this->uzivatele->saveHash($hash, $this->user->id);
			$this->section->loginHash = $hash;
			$this->reloadUserData();
			$this->flashMessage('Přihlášení bylo úspěšné.', 'success');
			$this->redirect('Homepage:');
		} catch (Nette\Security\AuthenticationException $e) {
			$form->addError($e->getMessage());
		}
    }

    public function actionOut()
    {
	    $this->uzivatele->unsetHash($this->user->id);
	    unset($this->section->loginHash);
	    $this->getUser()->logout(true);
	    $this->flashMessage('Byl jste odhlášen.');
	    $this->redirect('in');
    }

}
class Uzivatele extends \Nette\Object {

    private $db;

    public function __construct(\DibiConnection $connection)
    {
        $this->db = $connection;
    }

    public function findByEmail($email) {
		return $this->db->query("SELECT * FROM `uzivatele` WHERE `email` = %s", $email)->fetch();
    }

    public function findById($id) {
		return $this->db->query("SELECT * FROM `uzivatele` WHERE `id` = %i", $id)->fetch();
    }

    public function saveHash($hash, $uid){
		$this->db->query("UPDATE `uzivatele` SET `loginhash` = %s WHERE `id` = %i", $hash, $uid);
    }

    public function unsetHash($uid){
		$this->db->query("UPDATE `uzivatele` SET `loginhash` = NULL WHERE `id` = %i", $uid);
    }

    public static function generateLoginHash(){
		return md5(uniqid() . microtime() . rand());
    }

}
David Matějka
Moderator | 6445
+
0
-

princip s tim hashem je ok, provest by to slo lepe, par poznamek:

  1. udelej si vlastni identitu implementujuci Nette\Security\IIdentity, ktera bude do session ukladat id a ten hash (to urcis v metode __sleep()
  2. tu logiku presun z presenteru jinam – treba to UserStorage – inspirovat se muzes zde

v tom UserStorage by slo vyresit vsechno – znovu nacitani identity, overeni hashe a vraceni logout reasonu v pripade neplatneho hashe

enumag
Člen | 2118
+
0
-

Používám tohle UserStorage na ověření že se od přihlášení nezměnilo heslo nebo oprávnění uživatele, okud jo tak ho odhlásím. Tvůj problém tím půjde vyřešit taky, jen budeš měnit identityHash při každém přihlášení (já to měním při změně hesla či rolí).

bagr_001
Člen | 3
+
0
-

Moc děkuju za rady. Implementoval jsem upravené třídy User a UserStorage a funguje to skvěle. Jen mám problém s tím, že při každé žádosti o data uživatele se aktualizuje UserStorage. Takže pokaždé když použiju $this->user->data… tak si to sahá do DB. Což mi ve výsledku přijde až dost paranoidní, ověřovat platnost účtu 10× během vykreslení stránky. Nebylo by lepší to přesunout do třídy User a kontrolovat validitu jen při vytvoření a prvním načtení identity?

enumag
Člen | 2118
+
0
-

Moje implementace sahá na databázi 1× za request, což je ok. Omezovat to víc už znamená bezpečnostní díru.