Kullanıcı Girişi (Kimlik Doğrulama)

Neredeyse hiçbir web uygulaması, kullanıcı girişi ve kullanıcı izinlerini doğrulama mekanizması olmadan yapamaz. Bu bölümde şunları ele alacağız:

  • kullanıcıların giriş ve çıkış yapması
  • özel kimlik doğrulayıcılar

Kurulum ve Gereksinimler

Örneklerde, mevcut kullanıcıyı temsil eden Nette\Security\User sınıfının nesnesini kullanacağız ve buna dependency injection kullanarak erişebilirsiniz. Presenter'larda sadece $user = $this->getUser() çağırmanız yeterlidir.

Kimlik Doğrulama

Kimlik doğrulama, kullanıcı girişi anlamına gelir, yani kullanıcının gerçekten iddia ettiği kişi olup olmadığının doğrulandığı süreçtir. Genellikle kullanıcı adı ve şifre ile kanıtlanır. Doğrulama, sözde autentikátor tarafından gerçekleştirilir. Giriş başarısız olursa, Nette\Security\AuthenticationException atılır.

try {
	$user->login($username, $password);
} catch (Nette\Security\AuthenticationException $e) {
	$this->flashMessage('Kullanıcı adı veya şifre yanlış');
}

Bu şekilde kullanıcıyı oturumdan çıkarırsınız:

$user->logout();

Ve giriş yapıp yapmadığını kontrol etme:

echo $user->isLoggedIn() ? 'evet' : 'hayır';

Çok basit, değil mi? Ve tüm güvenlik yönlerini Nette sizin için halleder.

Presenter'larda, startup() metodunda girişi doğrulayabilir ve giriş yapmamış kullanıcıyı giriş sayfasına yönlendirebilirsiniz.

protected function startup()
{
	parent::startup();
	if (!$this->getUser()->isLoggedIn()) {
		$this->redirect('Sign:in');
	}
}

Süre Sonu

Kullanıcı girişi, genellikle session olan depolama alanının sona ermesiyle birlikte sona erer (bkz. session sona erme ayarı). Ancak, kullanıcının oturumdan çıkarılacağı daha kısa bir zaman aralığı da ayarlanabilir. Bunun için, login() öncesinde çağrılan setExpiration() metodu kullanılır. Parametre olarak göreli bir zaman dizesi belirtin:

// giriş 30 dakikalık hareketsizlikten sonra sona erecek
$user->setExpiration('30 minutes');

// ayarlanan sona ermeyi iptal etme
$user->setExpiration(null);

Kullanıcının zaman aralığının sona ermesi nedeniyle oturumdan çıkarılıp çıkarılmadığını $user->getLogoutReason() metodu söyler, bu metot ya Nette\Security\UserStorage::LogoutInactivity (zaman sınırı aşıldı) sabitini ya da UserStorage::LogoutManual (logout() metoduyla oturumdan çıkarıldı) sabitini döndürür.

Authenticator

Bu, giriş bilgilerini, yani genellikle adı ve şifreyi doğrulayan bir nesnedir. Basit bir biçim, yapılandırmada tanımlayabileceğimiz Nette\Security\SimpleAuthenticator sınıfıdır:

security:
	users:
		# kullanıcı adı: şifre
		frantisek: gizlisifre
		katka: dahadagizlisifre

Bu çözüm daha çok test amaçlıdır. Veritabanı tablosuna karşı giriş bilgilerini doğrulayacak bir kimlik doğrulayıcının nasıl oluşturulacağını göstereceğiz.

Authenticator, authenticate() metoduna sahip Nette\Security\Authenticator arayüzünü uygulayan bir nesnedir. Görevi ya sözde kimliği döndürmek ya da Nette\Security\AuthenticationException istisnasını atmaktır. Ortaya çıkan durumu daha hassas bir şekilde ayırt etmek için hata kodu da belirtilebilir: Authenticator::IdentityNotFound ve Authenticator::InvalidCredential.

use Nette;
use Nette\Security\SimpleIdentity;

class MyAuthenticator implements Nette\Security\Authenticator
{
	public function __construct(
		private Nette\Database\Explorer $database,
		private Nette\Security\Passwords $passwords,
	) {
	}

	public function authenticate(string $username, string $password): SimpleIdentity
	{
		$row = $this->database->table('users')
			->where('username', $username)
			->fetch();

		if (!$row) {
			throw new Nette\Security\AuthenticationException('Kullanıcı bulunamadı.');
		}

		if (!$this->passwords->verify($password, $row->password)) {
			throw new Nette\Security\AuthenticationException('Geçersiz şifre.');
		}

		return new SimpleIdentity(
			$row->id,
			$row->role, // veya birden fazla rol dizisi
			['name' => $row->username],
		);
	}
}

MyAuthenticator sınıfı, Nette Database Explorer aracılığıyla veritabanıyla iletişim kurar ve username sütununda kullanıcının giriş adının ve password sütununda şifre özetinin bulunduğu users tablosuyla çalışır. Ad ve şifreyi doğruladıktan sonra, kullanıcının ID'sini, rolünü (tablodaki role sütunu, bunun hakkında daha sonra daha fazla konuşacağız) ve diğer verileri (bizim durumumuzda kullanıcı adı) taşıyan kimliği döndürür.

Authenticator'ı ayrıca DI konteynerinin bir servisi olarak yapılandırmaya ekleyeceğiz:

services:
	- MyAuthenticator

$onLoggedIn, $onLoggedOut Olayları

Nette\Security\User nesnesi, $onLoggedIn ve $onLoggedOut olaylarına sahiptir, bu nedenle başarılı girişten sonra veya kullanıcı oturumdan çıktıktan sonra çağrılacak geri aramalar ekleyebilirsiniz.

$user->onLoggedIn[] = function () {
	// kullanıcı şimdi giriş yaptı
};

Kimlik

Kimlik, kimlik doğrulayıcı tarafından döndürülen ve ardından session'da saklanan ve $user->getIdentity() kullanarak aldığımız kullanıcı hakkındaki bilgi kümesini temsil eder. Böylece, kimlik doğrulayıcıda aktardığımız gibi id, roller ve diğer kullanıcı verilerini alabiliriz:

$user->getIdentity()->getId();
// kısayol $user->getId() de çalışır;

$user->getIdentity()->getRoles();

// kullanıcı verileri özellikler olarak mevcuttur
// MyAuthenticator'da aktardığımız isim
$user->getIdentity()->name;

Önemli olan, $user->logout() kullanarak oturumu kapattığınızda kimliğin silinmemesi ve hala kullanılabilir olmasıdır. Yani, kullanıcının bir kimliği olsa bile, giriş yapmamış olabilir. Kimliği açıkça silmek istersek, kullanıcıyı logout(true) çağrısıyla oturumdan çıkarırız.

Bu sayede, bilgisayarda hangi kullanıcının olduğunu tahmin etmeye devam edebilir ve örneğin e-ticaret sitesinde ona kişiselleştirilmiş teklifler gösterebilirsiniz, ancak kişisel verilerini yalnızca giriş yaptıktan sonra görüntüleyebilirsiniz.

Kimlik, Nette\Security\IIdentity arayüzünü uygulayan bir nesnedir, varsayılan uygulama Nette\Security\SimpleIdentity'dir. Ve bahsedildiği gibi, session'da tutulur, bu nedenle örneğin giriş yapmış kullanıcılardan birinin rolünü değiştirirsek, eski veriler tekrar giriş yapana kadar kimliğinde kalır.

Giriş Yapmış Kullanıcının Depolama Alanı

Kullanıcı hakkındaki iki temel bilgi, yani giriş yapıp yapmadığı ve identita'sı genellikle session'da aktarılır. Bu değiştirilebilir. Bu bilgilerin saklanmasından Nette\Security\UserStorage arayüzünü uygulayan bir nesne sorumludur. İki standart uygulama mevcuttur, ilki verileri session'da, ikincisi cookie'de aktarır. Bunlar Nette\Bridges\SecurityHttp\SessionStorage ve CookieStorage sınıflarıdır. Depolama alanını seçebilir ve security › authentication yapılandırmasında çok rahat bir şekilde yapılandırabilirsiniz.

Ayrıca, kimliğin nasıl saklanacağını (sleep) ve geri yükleneceğini (wakeup) tam olarak etkileyebilirsiniz. Authenticator'ın Nette\Security\IdentityHandler arayüzünü uygulaması yeterlidir. Bunun iki metodu vardır: sleepIdentity() kimliği depolama alanına yazmadan önce çağrılır ve wakeupIdentity() okunduktan sonra çağrılır. Metotlar kimliğin içeriğini değiştirebilir veya döndüreceği yeni bir nesneyle değiştirebilir. wakeupIdentity() metodu hatta null döndürebilir, bu da kullanıcıyı oturumdan çıkarır.

Örnek olarak, sık sorulan bir soru olan session'dan yüklendikten hemen sonra kimlikteki rollerin nasıl güncelleneceği çözümünü göstereceğiz. wakeupIdentity() metodunda, kimliğe örneğin veritabanından güncel rolleri aktaracağız:

final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function sleepIdentity(IIdentity $identity): IIdentity
	{
		// burada kimliği giriş yaptıktan sonra depolama alanına yazmadan önce değiştirebiliriz,
		// ancak şimdi buna ihtiyacımız yok
		return $identity;
	}

	public function wakeupIdentity(IIdentity $identity): ?IIdentity
	{
		// kimlikteki rolleri güncelleme
		$userId = $identity->getId();
		$identity->setRoles($this->facade->getUserRoles($userId));
		return $identity;
	}

Ve şimdi cookie tabanlı depolama alanına geri dönelim. Kullanıcıların giriş yapabileceği ve session'lara ihtiyaç duymayan bir web sitesi oluşturmanıza olanak tanır. Yani diske yazmaya ihtiyaç duymaz. Sonuçta, forum dahil okuduğunuz web sitesi de bu şekilde çalışır. Bu durumda, IdentityHandler uygulaması bir zorunluluktur. Cookie'ye yalnızca giriş yapmış kullanıcıyı temsil eden rastgele bir belirteç kaydedeceğiz.

Öncelikle, yapılandırmada security › authentication › storage: cookie kullanarak istenen depolama alanını ayarlayacağız.

Veritabanında, her kullanıcının yeterli uzunlukta (en az 13 karakter) tamamen rastgele, benzersiz ve tahmin edilemez bir dizeye sahip olacağı bir authtoken sütunu oluşturacağız. CookieStorage depolama alanı, cookie'de yalnızca $identity->getId() değerini aktarır, bu nedenle sleepIdentity()'de orijinal kimliği ID'de authtoken bulunan bir vekil kimlikle değiştireceğiz, tersine wakeupIdentity() metodunda authtoken'a göre tüm kimliği veritabanından okuyacağız:

final class Authenticator implements
	Nette\Security\Authenticator, Nette\Security\IdentityHandler
{
	public function authenticate(string $username, string $password): SimpleIdentity
	{
		$row = $this->db->fetch('SELECT * FROM user WHERE username = ?', $username);
		// şifreyi doğrula
		...
		// veritabanındaki tüm verilerle kimliği döndür
		return new SimpleIdentity($row->id, null, (array) $row);
	}

	public function sleepIdentity(IIdentity $identity): SimpleIdentity
	{
		// ID'de authtoken bulunan vekil kimliği döndür
		return new SimpleIdentity($identity->authtoken);
	}

	public function wakeupIdentity(IIdentity $identity): ?SimpleIdentity
	{
		// vekil kimliği authenticate()'deki gibi tam kimlikle değiştir
		$row = $this->db->fetch('SELECT * FROM user WHERE authtoken = ?', $identity->getId());
		return $row
			? new SimpleIdentity($row->id, null, (array) $row)
			: null;
	}
}

Birden Fazla Bağımsız Giriş

Aynı web sitesi ve aynı session içinde aynı anda birkaç bağımsız giriş yapan kullanıcıya sahip olmak mümkündür. Örneğin, web sitesinde yönetim ve genel kısım için ayrı kimlik doğrulaması yapmak istiyorsak, her birine kendi adını ayarlamamız yeterlidir:

$user->getStorage()->setNamespace('backend');

Ad alanını her zaman ilgili kısma ait tüm yerlerde ayarlamayı unutmamak önemlidir. Presenter'ları kullanıyorsak, ad alanını ilgili kısım için ortak atada – genellikle BasePresenter – ayarlayacağız. Bunu checkRequirements() metodunu genişleterek yapacağız:

public function checkRequirements($element): void
{
	$this->getUser()->getStorage()->setNamespace('backend');
	parent::checkRequirements($element);
}

Birden Fazla Authenticator

Uygulamanın bağımsız girişli kısımlara bölünmesi genellikle farklı kimlik doğrulayıcılar gerektirir. Ancak, hizmet yapılandırmasında Authenticator uygulayan iki sınıf kaydedersek, Nette hangisini otomatik olarak Nette\Security\User nesnesine atayacağını bilemez ve bir hata gösterirdi. Bu nedenle, kimlik doğrulayıcılar için autowiring'i, yalnızca birisi belirli bir sınıfı, örneğin FrontAuthenticator'ı istediğinde çalışacak şekilde sınırlamamız gerekir, bunu autowired: self seçeneğiyle başarırız:

services:
	-
		create: FrontAuthenticator
		autowired: self
class SignPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private FrontAuthenticator $authenticator,
	) {
	}
}

User nesnesinin kimlik doğrulayıcısını login() metodunu çağırmadan önce ayarlayacağız, bu nedenle genellikle onu giriş yaptıran formun kodunda:

$form->onSuccess[] = function (Form $form, \stdClass $data) {
	$user = $this->getUser();
	$user->setAuthenticator($this->authenticator);
	$user->login($data->username, $data->password);
	// ...
};
versiyon: 4.0