---
title: 'Kimlik Doğrulama ve Yetkilendirme'
source: 'https://academia.sh/tr/kurslar/web-uygulama-cercevesi/kimlik-dogrulama-ve-yetkilendirme'
course: 'Web Uygulama Çerçevesi'
language: tr
updated: '2026-10-21T05:03:38+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Kimlik doğrulama ile yetkilendirmeyi ayırır ve ikisinin hangi adımda koştuğunu gösterir'
    - 'Yetki kapısının politika sonucuna göre eylemi atladığını ve 403 verdiğini ölçer'
    - 'Tanımsız bir yeteneğe kapının ne döndürdüğünü ölçer ve reddetmenin neden varsayılan olması gerektiğini açıklar'
---

# Kimlik Doğrulama ve Yetkilendirme

Parolanın özetle saklanması ve doğrulanması, oturumdaki kullanıcı, yetki kapısı ve yetki politikası; meşru bir kullanıcının başkasının yazısını düzenleme isteğinde kapının 403 vermesi ve eylemin atlanması; tanımsız yeteneğin reddedilmesi ilkesi.

Sayfalama ve Kaynak Dönüşümü dersinde yanıta yeni bir alanın bağlanmadığını gördük: model değişmişti, yanıt 200 dönmüştü, ama değişiklik yanıtın içine ulaşmamıştı. Bu ders uygulama servislerinin ilkidir ve soru şudur: **isteği yapan kişi bu işi yapabilir mi ve yapamıyorsa eylem koştu mu?**

İki ayrı soru var ve çerçeveler ikisini ayrı adımlara koyar. **Kimlik doğrulama (authentication)** isteği yapanın kim olduğunu belirler: kullanıcı adı ve parola doğrudur, oturum bir kullanıcıya bağlıdır. **Yetkilendirme (authorization)** kimliği bilinen kullanıcının bu işi yapıp yapamayacağına karar verir. İlki 302 ile `/giris` sayfasına gönderir, ikincisi 403 ile reddeder. Birinin kimliğini doğrulamış olmak, ona her kaydı düzenleme hakkı vermez.

**US1.** Model ürün adı kullanmaz ve parolaları dışarıdan okumaz: parola `password_hash()` ile özetlenir, `password_verify()` ile doğrulanır; özetin kendisi hiçbir çıktıda basılmaz, çünkü her çağrıda farklı bir değer üretir. Oturum, çekirdekteki `Istek` sınıfının `oturum` alanının küçültülmüş bir biçimidir: bu derste alan oturumdaki kullanıcının kimliğini taşır ve `session_*` fonksiyonları çağrılmaz. Parola kırma, oturum çalma, belirteç taklidi ve yetki aşımı modellenmez; koşan kod savunma tarafındadır.

## Ölçüm Çekirdeği

Kurs boyunca her derste aynı ölçüm kullanılır. `kostu.php` beklenen bir parçanın bu istekte koşup koşmadığını kaydeder; dosya her derste birebir verilir ve çıktı bloğu yoktur, yalnız diske yazılır. `Gunluk` üç liste tutar: isteğin yoluna konanlar, istek içinde çağrılanlar ve isteğin dışına bırakılanlar. `sinifla()` bunlara sırayla bakar. **Karar sırası `sinifla()`'nın kendisidir:** koştu, ertelendi, atlandı, bağlanmadı. Parçayı geliştirici adlandırır; ölçülen, geliştiricinin bu istekte koşmasını beklediği parçalardır.

```php
<?php
// kostu.php — beklenen bir parça bu istekte koştu mu?
declare(strict_types=1);

final class Gunluk {
    public static array $yol = [];         // isteğin yoluna konan parçalar
    public static array $kostu = [];       // istek içinde çağrılanlar
    public static array $ertelenen = [];   // isteğin dışına bırakılanlar
    public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; }
    public static function yola(string $parca): void { self::$yol[] = $parca; }
    public static function kostu(string $parca): void { self::$kostu[] = $parca; }
    public static function ertele(string $parca): void { self::$ertelenen[] = $parca; }
}

function sinifla(string $parca): string {
    return match (true) {
        in_array($parca, Gunluk::$kostu, true) => 'koştu',
        in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi',
        in_array($parca, Gunluk::$yol, true) => 'atlandı',
        default => 'bağlanmadı',
    };
}
```

Bu ders çekirdeğe üç çağrıyla bağlanır: yola koyduğunda `Gunluk::yola()`, çağırdığında `Gunluk::kostu()`; ertelenen bir parça bu derste yoktur. Çerçevenin tamamı yeniden kurulmaz, yalnız bu dersin dilimi yazılır.

## Parola Özeti

Kimlik doğrulamanın ilk adımı parolanın saklanış biçimidir. Parola olduğu gibi saklanmaz; `password_hash()` ona bir tuz ekleyip tek yönlü bir özet üretir ve `password_verify()` verilen parolayı özetle karşılaştırır. Tuz her çağrıda yenidir, bu yüzden aynı parolanın iki özeti farklıdır ama ikisi de doğrulanır. Aşağıdaki ölçüm özetin kendisini basmaz, yalnız karşılaştırma sonuçlarını basar.

```php
<?php
// parola.php — aynı parolanın iki özeti
$ilk = password_hash('sifre-ayse', PASSWORD_DEFAULT);
$ikinci = password_hash('sifre-ayse', PASSWORD_DEFAULT);
echo 'iki özet eşit mi: ', $ilk === $ikinci ? 'evet' : 'hayır', "\n";
echo 'ilk özet doğrulanır mı: ', password_verify('sifre-ayse', $ilk) ? 'evet' : 'hayır', "\n";
echo 'ikinci özet doğrulanır mı: ', password_verify('sifre-ayse', $ikinci) ? 'evet' : 'hayır', "\n";
echo 'yanlış parola doğrulanır mı: ', password_verify('sifre-burak', $ilk) ? 'evet' : 'hayır', "\n";
```

```
iki özet eşit mi: hayır
ilk özet doğrulanır mı: evet
ikinci özet doğrulanır mı: evet
yanlış parola doğrulanır mı: hayır
```

Özetlerin eşit olmaması, özet karşılaştırmasının `===` ile değil `password_verify()` ile yapılması gerektiğini söyler. `KimlikDeposu::giris()` bu yüzden kullanıcı adını eşitlikle, parolayı doğrulama fonksiyonuyla sınar. Depoda yalnız `parolaOzeti` alanı bulunur; düz parola hiçbir nesnede tutulmaz.

## Kapı ve Politika

`yetki.php` dilimin tamamıdır ve dört parçadan oluşur. `KimlikDeposu` parolayı özet olarak saklar ve `giris()` ile doğrular. **Yetki kapısı (gate)**, yani `Kapi`, bir yeteneğin adını (`yazi.duzenle`) bir **yetki politikasına (policy)** bağlar; politika kullanıcıyı ve hedef nesneyi alıp doğru ya da yanlış döndüren küçük bir fonksiyondur. `YaziPolitikasi::duzenle()` kuralı tek satırla söyler: yazıyı yalnız sahibi düzenler.

`YaziUygulamasi::duzenle()` üç beklenen parçayı yola koyar: `kimlik` (oturumdaki kullanıcıyı bulma), `kapi` (izin sorusu) ve `duzenle` (başlığı değiştiren eylem). Her adım çağrıldığında `Gunluk::kostu()` ile işaretlenir. Kimlik yoksa istek 302 ile döner, kapı reddederse 403 ile döner ve eylem hiç çağrılmaz.

```php
<?php
// yetki.php — kimlik doğrulama, yetki kapısı ve yetki politikası
declare(strict_types=1);
require_once 'kostu.php';

final class Kullanici {
    public function __construct(public int $id, public string $ad, public string $parolaOzeti) {}
}

final class Yazi {
    public function __construct(public int $id, public int $sahipId, public string $baslik) {}
}

final class Istek {
    public function __construct(public ?int $oturum = null, public array $govde = []) {}
}

final class KimlikDeposu {
    private array $kullanicilar = [];
    public function kaydet(int $id, string $ad, string $parola): void {
        $this->kullanicilar[$id] = new Kullanici($id, $ad, password_hash($parola, PASSWORD_DEFAULT));
    }
    public function giris(string $ad, string $parola): ?Kullanici {
        foreach ($this->kullanicilar as $kullanici) {
            if ($kullanici->ad === $ad && password_verify($parola, $kullanici->parolaOzeti)) return $kullanici;
        }
        return null;
    }
    public function bul(int $id): ?Kullanici { return $this->kullanicilar[$id] ?? null; }
}

final class Kapi {
    private array $politikalar = [];
    public function __construct(private bool $tanimsizGecer = false) {}
    public function tanimla(string $yetenek, Closure $politika): void { $this->politikalar[$yetenek] = $politika; }
    public function izinVerir(string $yetenek, Kullanici $kullanici, object $hedef): bool {
        if (!isset($this->politikalar[$yetenek])) return $this->tanimsizGecer;
        return ($this->politikalar[$yetenek])($kullanici, $hedef);
    }
}

final class YaziPolitikasi {
    public static function duzenle(Kullanici $kullanici, Yazi $yazi): bool {
        return $kullanici->id === $yazi->sahipId;
    }
}

final class YaziUygulamasi {
    public function __construct(private KimlikDeposu $kimlik, private Kapi $kapi, public array $yazilar) {}
    public function duzenle(Istek $istek, int $yaziId, string $yetenek = 'yazi.duzenle'): array {
        Gunluk::yeniIstek();
        foreach (['kimlik', 'kapi', 'duzenle'] as $parca) Gunluk::yola($parca);
        Gunluk::kostu('kimlik');
        $kullanici = $istek->oturum === null ? null : $this->kimlik->bul($istek->oturum);
        if ($kullanici === null) return [302, '/giris'];
        Gunluk::kostu('kapi');
        $yazi = $this->yazilar[$yaziId];
        if (!$this->kapi->izinVerir($yetenek, $kullanici, $yazi)) return [403, 'yetki yok'];
        Gunluk::kostu('duzenle');
        $yazi->baslik = $istek->govde['baslik'];
        return [200, 'guncellendi'];
    }
}
```

```php
<?php
// kapi.php — iki kullanıcı, bir yazı, üç istek
require_once 'yetki.php';

$kimlik = new KimlikDeposu();
$kimlik->kaydet(1, 'ayse', 'sifre-ayse');
$kimlik->kaydet(2, 'burak', 'sifre-burak');
echo 'giriş doğru parola: ', $kimlik->giris('burak', 'sifre-burak')?->ad ?? 'yok', "\n";
echo 'giriş yanlış parola: ', $kimlik->giris('burak', 'sifre-ayse')?->ad ?? 'yok', "\n";

$kapi = new Kapi();
$kapi->tanimla('yazi.duzenle', YaziPolitikasi::duzenle(...));
$uygulama = new YaziUygulamasi($kimlik, $kapi, [10 => new Yazi(10, 1, 'Merhaba')]);

function olc(string $ad, array $yanit): void {
    printf("%s -> %d %s\n", $ad, $yanit[0], $yanit[1]);
    foreach (['kimlik', 'kapi', 'duzenle'] as $parca) printf("  %-8s %s\n", $parca, sinifla($parca));
}

olc('oturumsuz', $uygulama->duzenle(new Istek(null, ['baslik' => 'Yeni']), 10));
olc('burak', $uygulama->duzenle(new Istek(2, ['baslik' => 'Yeni']), 10));
olc('ayse', $uygulama->duzenle(new Istek(1, ['baslik' => 'Yeni']), 10));
echo 'başlık: ', $uygulama->yazilar[10]->baslik, "\n";
```

```
giriş doğru parola: burak
giriş yanlış parola: yok
oturumsuz -> 302 /giris
  kimlik   koştu
  kapi     atlandı
  duzenle  atlandı
burak -> 403 yetki yok
  kimlik   koştu
  kapi     koştu
  duzenle  atlandı
ayse -> 200 guncellendi
  kimlik   koştu
  kapi     koştu
  duzenle  koştu
başlık: Yeni
```

İlk satırlar `password_verify()` sonucudur: doğru parola kullanıcıyı döndürür, başka birinin parolası döndürmez. Asıl gözlem istek satırlarındadır. Oturumsuz istekte `kimlik` koştu, kapı ve eylem yoldaydı ama çağrılmadı: ikisi de **atlandı**, durum kodu 302. İkinci istek bu dersin gözlemidir: Burak oturum açmış, kimliği doğrulanmış, meşru bir kullanıcıdır; Ayşe'nin yazısını düzenlemek ister. `kimlik` ve `kapi` koştu, kapı politikayı çalıştırdı, politika yanlış döndürdü, durum 403 oldu ve `duzenle` **atlandı**. Son satırdaki başlığın `Merhaba` değil `Yeni` olması, değişikliğin yalnız üçüncü istekten geldiğini söyler.

Atlanan parça kendini durum koduyla ele verir. Burak'ın isteğinde 403 görmek, eylemin neden koşmadığını açıklar; hata fırlatılmadı, çünkü ret beklenen bir sonuçtur.

## Karşıt: Yazının Sahibi

Üçüncü istek aynı kod yolundan geçer ve tek fark kullanıcının yazının sahibi olmasıdır. Politika doğru döndürür, kapı geçirir, `duzenle` **koştu** ve durum 200 olur. Kapıda hiçbir şey değişmedi; değişen, politikaya verilen kullanıcı ile yazının `sahipId` alanının eşleşmesidir. Kapı bir engel değil bir karardır ve karar her istekte yeniden verilir.

Burada ayrımı koymak gerekir. `kimlik` her üç istekte de koştu: kimlik doğrulama başarıldı ya da başarısız olduysa bile adım çağrıldı. Oturumsuz istekte kapının atlanmasıyla Burak'ın isteğinde eylemin atlanması farklı nedenlerdendir, ama sınıf aynıdır: yol vardı, önündeki adım yanıtı erken verdi. Sınıfı sebep değil `sinifla()` belirler.

## Sınırlayıcı Ölçüm

Kapıya hiç tanımlanmamış bir yetenek sorulursa ne olur? Gerçek uygulamalarda bu bir yazım hatasıyla olur: politika `yazi.duzenle` adına bağlanmış, eylem `yazi.duzenel` diye sorar. Model iki kapı kurar: biri tanımsız yeteneği reddeder (`Kapi` varsayılanı), öteki geçirir (`tanimsizGecer: true`). Aynı yazım hatalı yetenek adı hem yazının sahibi hem sahibi olmayan kullanıcı için sorulur.

```php
<?php
// tanimsiz.php — kapıya tanımlanmamış bir yetenek sorulur
require_once 'yetki.php';

function kur(bool $tanimsizGecer): YaziUygulamasi {
    $kimlik = new KimlikDeposu();
    $kimlik->kaydet(1, 'ayse', 'sifre-ayse');
    $kimlik->kaydet(2, 'burak', 'sifre-burak');
    $kapi = new Kapi($tanimsizGecer);
    $kapi->tanimla('yazi.duzenle', YaziPolitikasi::duzenle(...));
    return new YaziUygulamasi($kimlik, $kapi, [10 => new Yazi(10, 1, 'Merhaba')]);
}

foreach ([false, true] as $gecer) {
    foreach ([[1, 'ayse'], [2, 'burak']] as [$oturum, $ad]) {
        $uygulama = kur($gecer);
        $yanit = $uygulama->duzenle(new Istek($oturum, ['baslik' => 'Yeni']), 10, 'yazi.duzenel');
        printf("%-10s %-6s -> %d  kapi %-8s duzenle %s\n", $gecer ? 'geciriyor' : 'reddediyor', $ad,
            $yanit[0], sinifla('kapi'), sinifla('duzenle'));
    }
}
```

```
reddediyor ayse   -> 403  kapi koştu   duzenle atlandı
reddediyor burak  -> 403  kapi koştu   duzenle atlandı
geciriyor  ayse   -> 200  kapi koştu   duzenle koştu
geciriyor  burak  -> 200  kapi koştu   duzenle koştu
```

Sonuç iki yönlüdür. Reddeden kapıda yazı sahibi bile 403 alır: hata yüksek sesle görünür ve geliştirici ilk denemede ad uyuşmazlığını fark eder. Geçiren kapıda ise Burak'ın isteği 200 ile döner ve eylem koşar; kapı koştu, ama hiçbir politika çalışmadı, yani kapı varlığı ile korumayı aynı şey sanmak yanıltır. **Tanımsız yetenek reddedilmelidir** ilkesi buradan çıkar: bilinmeyen bir soruya "evet" demek, korumanın sessizce kalkması demektir ve yanıtta hiçbir iz bırakmaz.

## Özet

- Bu dersin ölçü sınıfı **atlandı**: Burak'ın isteğinde `kimlik` ve `kapi` koştu, `duzenle` yoldaydı ama kapı 403 verdi ve eylem çağrılmadı.
- Kimlik doğrulama kim olduğunu, yetkilendirme ne yapabileceğini belirler; ilki 302 ile, ikincisi 403 ile kendini ele verir.
- Karşıtı: yazının sahibinde politika doğru döndürdü, `duzenle` **koştu** ve durum 200 oldu.
- Sınırlayıcı ölçümde tanımsız yetenek reddeden kapıda iki kullanıcı da 403 aldı; geçiren kapıda ikisi de 200 aldı ve eylem koştu. Tanımsız yetenek reddedilmelidir.
- Parola `password_hash()` ile saklanır ve `password_verify()` ile doğrulanır; özet çıktıya yazılmaz.

## Sonraki Adım

Kapı reddedince eylem atlandı ve yanıt hemen döndü. Peki eylem koşacaksa ve içindeki bir iş yanıtı geciktirecekse, bu işin yanıtı beklemesi şart mı? Kuyruklar ve İşler dersi uzun bir işi isteğin dışına bırakır ve yanıtın onu beklemediğini ölçer.
