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
Ö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);
// ...
};