İçeriğe geç
academia.sh

Ders 01 / 23

Kimlik Doğrulama ve Yetkilendirme Ayrımı

İsteğin arkasındaki asılın belirlenmesi ile o asılın ne yapabileceğine karar verilmesinin iki ayrı soru olduğunun gösterilmesi: yalnız birincisini soran sunucunun gözlenebilir sonucu, 401 ile 403 ayrımı ve kararın öntanımlı ret üzerine kurulması.

İçindekiler

Önceki kurs boyunca ödünç servisinin sözleşmesi kuruldu: hangi adres hangi kaynağı verir, hangi yöntem neyi değiştirir, hata hangi biçimde bildirilir, sözleşme nasıl belgelenir ve sınanır. Bütün bu kararlar tek bir soruyu hiç sormadı: bu isteği kim yolluyor? Sahte sunucu her isteğe aynı yanıtı verdi, sözleşme sınaması her etkileşimi aynı ayrıcalıkla çalıştırdı. Sözleşme, çağıranın kim olduğundan bağımsız yazılmıştı.

Gerçek serviste bu bağımsızlık sürmez. Ödünç kaydını üye kendisi için okuyabilir, gecikme raporunu yalnız personel görebilir, bir üyenin cezasını yalnız şube yöneticisi silebilir. Bu cümlelerin her biri iki ayrı bilgi içerir: isteğin arkasındaki kimliğin ne olduğu ve o kimliğin ne yapmaya izinli olduğu. Bu kurs bu iki bilgiyi ayırmakla başlar, çünkü ikisini tek soru sanan bir sunucu her zaman aynı biçimde bozulur.

İki Ayrı Soru

Kimlik doğrulama (authentication) “sen kimsin?” sorusunu yanıtlar. Sunucu, isteğin taşıdığı kimlik bilgisinden (credential) yola çıkarak isteğin arkasındaki asılı (principal) belirler. Asıl bir kişi olabilir, bir servis hesabı olabilir, bir raf terminali olabilir. Bu adımın çıktısı tek bir şeydir: bir kimlik ya da hiç.

Yetkilendirme (authorization) “bunu yapabilir misin?” sorusunu yanıtlar. Girdisi asıl, istenen eylem ve eylemin hedefidir; çıktısı bir karardır: izin ver ya da reddet. Bu adım kimliği kendisi bulmaz, birinci adımın bulduğunu kullanır.

Sıra tersine çevrilemez. Kimliği bilmeden yetki kararı verilemez, çünkü kararın girdisi eksiktir. Ama tersi sık görülür: kimlik doğru belirlenir, sonra yetki sorusu hiç sorulmaz. Sunucu asılın var olmasını yeterli sayar ve “kayıtlı olan her şeyi yapabilir” kuralını farkında olmadan uygular. Bu kuralın sonucu kodda görünmez; ancak istekler çalıştırılınca görünür.

Yalnız Birinci Sorunun Sorulduğu Sunucu

Aşağıdaki sunucu üç uç sunar ve kimliği doğru belirler: tanınmayan bir kimlik bilgisi 401 alır. Yetki denetimi ise bir bayrağın arkasındadır; bayrak verilmediğinde ikinci soru hiç sorulmaz.

// odunc.mjs — odunc servisi; kimlik dogrulama var, yetkilendirme secmeli
// Kullanim: node odunc.mjs <port> [yetki]
//   yetki: verilirse rol denetimi de calisir
import { createServer } from "node:http";

const PORT = Number(process.argv[2] ?? 8501);
const YETKI = process.argv[3] === "yetki";

// Kimlik bilgisi -> asil (principal). Bu esleme sonraki derslerin konusu;
// burada kimligin belirlenmis oldugu varsayiliyor.
const KIMLIKLER = new Map([
  ["k-uye-1001", { kod: "U-1001", ad: "Deniz Aral", rol: "uye" }],
  ["k-personel-7", { kod: "P-0007", ad: "Selin Kaya", rol: "personel" }],
  ["k-yonetici-2", { kod: "Y-0002", ad: "Ahmet Ünal", rol: "sube-yoneticisi" }],
]);

const ODUNC = new Map([
  ["O-1", { id: "O-1", uye: "U-1001", isbn: "978-0262033848", sonTarih: "2026-03-20", durum: "acik" }],
  ["O-2", { id: "O-2", uye: "U-1042", isbn: "978-0201896831", sonTarih: "2026-02-11", durum: "gecikmis" }],
]);

// Uc -> o ucu cagirabilecek roller
const IZIN = new Map([
  ["GET /odunc", ["uye", "personel", "sube-yoneticisi"]],
  ["GET /raporlar/gecikmeler", ["personel", "sube-yoneticisi"]],
  ["POST /cezalar/silme", ["sube-yoneticisi"]],
]);

createServer((istek, yanit) => {
  yanit.sendDate = false;
  const yol = istek.url.split("?")[0];
  const json = (kod, govde, baslik = {}) => {
    yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8", ...baslik });
    yanit.end(JSON.stringify(govde));
  };

  // 1. soru: kim? — kimlik bilgisi yoksa ya da taninmiyorsa 401
  const bilgi = (istek.headers.authorization ?? "").replace(/^Kimlik /, "");
  const asil = KIMLIKLER.get(bilgi);
  if (!asil) {
    return json(401, { hata: "kimlik dogrulanamadi" }, { "www-authenticate": 'Kimlik realm="odunc"' });
  }

  // Ucun adi
  const uc = yol.startsWith("/odunc/") ? "GET /odunc"
    : yol === "/raporlar/gecikmeler" ? "GET /raporlar/gecikmeler"
    : yol === "/cezalar/silme" ? "POST /cezalar/silme"
    : null;
  if (!uc || !uc.startsWith(istek.method)) return json(404, { hata: "kaynak yok" });

  // 2. soru: ne yapabilir? — yalnizca YETKI acikken soruluyor
  if (YETKI && !IZIN.get(uc).includes(asil.rol)) {
    return json(403, { hata: "bu islem icin yetkiniz yok", rol: asil.rol, gerekli: IZIN.get(uc) });
  }

  if (uc === "GET /odunc") {
    const kayit = ODUNC.get(yol.slice("/odunc/".length));
    return kayit ? json(200, kayit) : json(404, { hata: "kayit yok" });
  }
  if (uc === "GET /raporlar/gecikmeler") {
    return json(200, { gecikmis: [...ODUNC.values()].filter((k) => k.durum === "gecikmis") });
  }
  return json(200, { silindi: true, isleyen: asil.kod });
}).listen(PORT, "127.0.0.1", () => console.log(`odunc 127.0.0.1:${PORT} yetki=${YETKI}`));

Karar tablosunu okumak için her rolü her uçta deneyen küçük bir istemci yeter.

// dizge.mjs — her rolun her uctan aldigi durum kodunu tablolar
// Kullanim: node odunc.mjs 8501 &  ve  node odunc.mjs 8502 yetki &  calisirken
//           node dizge.mjs 8501    /    node dizge.mjs 8502
const PORT = Number(process.argv[2] ?? 8501);
const TABAN = `http://127.0.0.1:${PORT}`;

const ROLLER = [["uye", "k-uye-1001"], ["personel", "k-personel-7"], ["yönetici", "k-yonetici-2"], ["tanımsız", "k-yok"]];
const UCLAR = [["GET", "/odunc/O-1"], ["GET", "/raporlar/gecikmeler"], ["POST", "/cezalar/silme"]];

console.log("rol".padEnd(10) + UCLAR.map(([y, u]) => `${y} ${u}`.padEnd(26)).join(""));
for (const [ad, bilgi] of ROLLER) {
  const kodlar = [];
  for (const [yontem, yol] of UCLAR) {
    const cevap = await fetch(TABAN + yol, { method: yontem, headers: { authorization: `Kimlik ${bilgi}` } });
    kodlar.push(String(cevap.status).padEnd(26));
  }
  console.log(ad.padEnd(10) + kodlar.join(""));
}

İki sunucu aynı anda ayağa kaldırılır; biri yetki denetimsiz, diğeri denetimli.

node odunc.mjs 8501 > /dev/null & DENETIMSIZ=$!
node odunc.mjs 8502 yetki > /dev/null & DENETIMLI=$!
sleep 1
echo "— yetki denetimi yok —"
node dizge.mjs 8501
echo "— yetki denetimi var —"
node dizge.mjs 8502
kill $DENETIMSIZ $DENETIMLI
— yetki denetimi yok —
rol       GET /odunc/O-1            GET /raporlar/gecikmeler  POST /cezalar/silme
uye       200                       200                       200
personel  200                       200                       200
yönetici  200                       200                       200
tanımsız  401                       401                       401
— yetki denetimi var —
rol       GET /odunc/O-1            GET /raporlar/gecikmeler  POST /cezalar/silme
uye       200                       403                       403
personel  200                       200                       403
yönetici  200                       200                       200
tanımsız  401                       401                       401

İlk tabloda dört satırın üçü aynıdır. Kimlik doğrulama çalışıyor: tanınmayan kimlik bilgisi her uçta 401 alıyor. Denetimin verdiği tek ayrım “kayıtlı” ile “kayıtsız” arasındadır. Üye kimliğiyle gelen bir istek gecikme raporunun tamamını okuyor ve bir cezayı siliyor; yanıtın isleyen alanı silme işleminin U-1001 adına yapıldığını söylüyor.

Bu, kimlik doğrulamanın bir kusuru değildir; kimlik doğrulama görevini yapmıştır. Kusur, yetki kararının hiç verilmemiş olmasıdır. İkinci tabloda aynı istekler 403 alır ve tablo artık üç farklı satır içerir. Aradaki tek fark, isteği işleyen kodun ikinci soruyu sormasıdır.

401 ve 403 Ayrımı

İki durum kodu iki farklı soruya karşılık gelir ve birbirinin yerine kullanılamaz.

401, birinci sorunun yanıtsız kaldığını bildirir: kimlik bilgisi yok, biçimsiz ya da tanınmıyor. Yanıt, kimliğin nasıl sunulacağını söyleyen WWW-Authenticate başlığını taşımak zorundadır; bu başlık olmadan istemcinin ne yapacağı belirsiz kalır.

node odunc.mjs 8502 yetki > /dev/null & SUNUCU=$!
sleep 1
curl -s -D - -o /dev/null http://127.0.0.1:8502/cezalar/silme | head -3
kill $SUNUCU
HTTP/1.1 401 Unauthorized
content-type: application/json; charset=utf-8
www-authenticate: Kimlik realm="odunc"

403, birinci sorunun yanıtlandığını ama ikincisinin ret verdiğini bildirir. Kimlik bellidir ve geçerlidir; eksik olan yetkidir. Aynı kimlik bilgisini yeniden yollamak sonucu değiştirmez, çünkü sorun kimlik bilgisinde değildir.

node odunc.mjs 8502 yetki > /dev/null & SUNUCU=$!
sleep 1
curl -s -w ' %{http_code}\n' -X POST -H 'Authorization: Kimlik k-uye-1001' http://127.0.0.1:8502/cezalar/silme
kill $SUNUCU
{"hata":"bu islem icin yetkiniz yok","rol":"uye","gerekli":["sube-yoneticisi"]} 403

Bu ayrım istemcinin davranışını belirler. 401 alan istemci kimlik bilgisini tazeleyip yeniden dener; 403 alan istemci yeniden denemez, kullanıcıya yetkisinin yetmediğini söyler. Kodları karıştıran bir sunucu, 403 yerine 401 döndürerek istemciyi sonsuz bir tazeleme döngüsüne sokar.

Üçüncü bir seçenek de vardır: bir kaynağın varlığının kendisi gizli bilgiyse, yetkisiz isteğe 403 yerine 404 dönmek gerekebilir. 403, “böyle bir kaynak var ama sana kapalı” der ve bu cümle bazı durumlarda söylenmemesi gereken bir bilgidir. Bu seçim bilinçli yapılmalıdır; her yerde 404 dönmek de hata ayıklamayı sağır eder.

Kararın Nerede Verildiği

Yetki kararının kod içinde bir karar noktası vardır. Yukarıdaki sunucuda bu nokta tek bir if bloğudur ve isteği işleyen koddan önce gelir. İki özelliği önemlidir.

Birincisi, karar noktası öntanımlı ret üzerine kurulur. IZIN tablosunda karşılığı olmayan bir uç, kimseye açık değildir. Bunun tersi olan kurulumda — “tabloda yasaklanmayan her şey serbest” — tabloya eklenmeyi unutmuş her yeni uç, yazıldığı anda herkese açılır. Yeni uç eklemek sık bir iştir; unutmak da öyle. Öntanımlı ret, unutmanın sonucunu erişim açığından çalışmayan uca çevirir: sonuç yine hatadır ama gözlenebilir ve zararsız bir hatadır.

İkincisi, karar noktası tek olmalıdır. Aynı kararın iki yerde verildiği bir kodda ikisi zamanla ayrışır ve hangisinin geçerli olduğu ancak isteği çalıştırarak bulunur. Ara katman zinciri — Sunucu Tarafı Temelleri kursunda kurulan yapı — bu tekliği sağlamanın doğal yeridir: kimliği belirleyen katman isteğe asılı iliştirir, yetki katmanı o asıla bakar, iş mantığı ikisini de yeniden hesaplamaz.

Bu iki soru, denetimin tamamı değildir. İkinci tablodaki uye satırında GET /odunc/O-1 hücresi 200’dür ve doğrudur: üyeler ödünç kaydı okuyabilir. Ama aynı üye başkasının kaydını da okuyabilir.

node odunc.mjs 8502 yetki > /dev/null & SUNUCU=$!
sleep 1
curl -s -w ' %{http_code}\n' -H 'Authorization: Kimlik k-uye-1001' http://127.0.0.1:8502/odunc/O-2
kill $SUNUCU
{"id":"O-2","uye":"U-1042","isbn":"978-0201896831","sonTarih":"2026-02-11","durum":"gecikmis"} 200

U-1001 kimliğiyle gelen istek, U-1042 üyesinin kaydını aldı. Rol denetimi geçti çünkü soru “üyeler ödünç kaydı okuyabilir mi?” biçiminde soruldu; sorulması gereken “bu üye bu kaydı okuyabilir mi?” idi. Rol, eylem türünü yetkilendirir; nesnenin sahipliğini yetkilendirmez. Bu ayrım Yetkilendirme Modelleri konusunun kendi başına bir dersidir; burada yalnız şu tespit yeterlidir: yetki sorusu bir uç adına değil, bir asıl–eylem–nesne üçlüsüne sorulur.

Özet

  • Kimlik doğrulama isteğin arkasındaki asılı belirler; yetkilendirme o asılın belirli bir eylemi yapıp yapamayacağına karar verir. İkincisinin girdisi birincisinin çıktısıdır.
  • Yalnız kimlik doğrulayan bir sunucu “kayıtlı olan her şeyi yapabilir” kuralını uygular; bu kural kodda değil, erişim tablosunda görünür.
  • 401 kimlik bilgisinin eksik ya da tanınmaz olduğunu bildirir ve WWW-Authenticate başlığını taşır; 403 kimliğin bilindiğini ama yetkinin yetmediğini bildirir.
  • Kaynağın varlığı gizli bilgiyse yetkisiz isteğe 404 dönmek bilinçli bir seçenektir; varsayılan değildir.
  • Karar noktası öntanımlı ret üzerine kurulur ve tektir; tabloya eklenmeyen uç kapalı kalır.
  • Rol denetimi eylem türünü yetkilendirir, nesnenin sahipliğini yetkilendirmez; yetki sorusu asıl–eylem–nesne üçlüsüne sorulur.

Sonraki Adım

Bu derste kimliğin nasıl belirlendiği bir eşleme tablosunun arkasına saklandı: istek bir dizge taşıdı, sunucu onu bir asıla çevirdi. Gerçek bir kurulumda bu dizgenin nereden geldiği, nasıl taşındığı ve ne kadar süre geçerli olduğu ayrı kararlardır. En eski ve en küçük yanıt, HTTP’nin kendi içinde tanımlı temel şemadır: kullanıcı adı ve parola her istekte Authorization başlığında taşınır. Sonraki ders bu şemayı çalıştırarak kurar, taşınan değerin niçin şifrelenmiş sayılmadığını gösterir ve şemanın nerede yeterli, nerede yetersiz kaldığını sınırlarıyla belirler.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat