İçeriğe geç
academia.sh

Ders 03 / 16

Kaynaklar Arası Erişim Denetimi

Kaynaklar arası paylaşımın sunucu tarafındaki yapılandırması: yedi kökenin dört ayar altında kaç tanesinin yanıtı kullanabildiği, joker ayarın açtığı köken sayısı, kimlik bilgisi taşıyan istekte joker ayarın sunucuda 200 döndürüp istemcide sessizce atılması, ön denetim isteğinin gidiş-dönüş maliyeti ve politikanın kaç uçta kayıtlı olduğu.

İçindekiler

Önceki iki ders isteğin gövdesine baktı. Bu ders isteğin nereden geldiğine bakıyor ve sunucunun bu konuda söyleyebileceği tek şeyi ölçüyor: yanıtın başlıkları.

Kaynaklar arası paylaşımın istemci tarafı — tarayıcının hangi isteği neden engellediği, politikanın sayfa üzerindeki etkisi — Frontend Kalitesi kursunda kuruldu ve burada tekrarlanmaz. Bu derste ölçülen şey sunucu tarafındaki yapılandırmadır: ayar hangi katmanda duruyor, joker bir değer kaç kökene kapı açıyor, kimlik bilgisi taşıyan bir istekte yanlış ayar ne yapıyor ve ön denetim isteği kaça mal oluyor.

Sunucunun yaptığı iş küçüktür: gelen kökene bakar, birkaç yanıt başlığı ekler ya da eklemez. Kararı istemci verir. Bu ayrım, dersin bütün ölçümlerinin dayandığı noktadır.

BG5 (varsayım): istemcinin kuralı bu derste kendi yazıldı. Sunucu ayarının doğruluğu, o kurala göre yanıtın kullanılıp kullanılmadığıyla ölçülüyor; ölçüm gerçek bir istemcinin değil, kuralın gerçekleştiriminin sonucudur.

Sunucunun Tek Sözü

// politika.mjs — sunucunun kaynaklar arasi paylasim konusundaki tek sozu: yanit basliklari
export const AYAR = {
  kokenler: ["https://odunc.kutuphane", "https://panel.kutuphane"],   // "*" joker demektir
  yontemler: ["GET", "POST"],
  basliklar: ["content-type", "x-odunc-izlem"],
  kimlikBilgisi: false,
  onDenetimOmru: 600,
};

export function basliklar(istek, ayar) {
  const c = {};
  if (!istek.koken) return c;                       // ayni kokenli istek; soz gerekmez
  const joker = ayar.kokenler === "*";
  if (!joker && !ayar.kokenler.includes(istek.koken)) return c;   // suskunluk = ret
  c["access-control-allow-origin"] = joker ? "*" : istek.koken;
  if (ayar.kimlikBilgisi) c["access-control-allow-credentials"] = "true";
  if (!joker) c["vary"] = "origin";                 // yanit kokene gore degisiyor
  if (istek.yontem === "OPTIONS") {
    c["access-control-allow-methods"] = ayar.yontemler.join(",");
    c["access-control-allow-headers"] = ayar.basliklar.join(",");
    c["access-control-max-age"] = String(ayar.onDenetimOmru);
  }
  return c;
}
// istemci.mjs — uyumlu bir istemcinin yanit basliklarina bakarak verdigi karar
// Kural burada kendi yazildi; sunucu ayarinin dogrulugu bu karara gore olculuyor.
export function karar(istek, b) {
  if (!istek.koken) return { kullanildi: true, neden: "ayni koken" };
  const izin = b["access-control-allow-origin"];
  if (!izin) return { kullanildi: false, neden: "izin basligi yok" };
  if (izin !== "*" && izin !== istek.koken) return { kullanildi: false, neden: "koken uyusmuyor" };
  if (istek.kimlikBilgisi) {
    if (izin === "*") return { kullanildi: false, neden: "joker + kimlik bilgisi" };
    if (b["access-control-allow-credentials"] !== "true")
      return { kullanildi: false, neden: "kimlik bilgisi izni yok" };
  }
  return { kullanildi: true, neden: "" };
}

Politika katmanının reddi bir hata değil, suskunluktur: tanımadığı bir kökene hiçbir başlık eklemez. Sunucu yine 200 döner, gövde yine yazılır. Bu tasarım kararı dersin geri kalanındaki bütün sessiz sonuçların kaynağıdır.

Joker Ayarın Açtığı Köken

Yedi köken alınıyor: ödünç sisteminin kendi arayüzü, iç panel, aynı adın şema ve kapı farkları, bir alt ad, ilgisiz bir köken ve köken başlığı hiç taşımayan aynı kökenli istek.

// koken.mjs — yedi koken, dort ayar; sunucunun dondugu ile istemcinin kullandigi
import { AYAR, basliklar } from "./politika.mjs";
import { karar } from "./istemci.mjs";

const KOKENLER = [
  null,                                // ayni koken (baslik yok)
  "https://odunc.kutuphane",           // kendi arayuzu
  "https://panel.kutuphane",           // ic panel
  "http://odunc.kutuphane",            // sema farkli
  "https://odunc.kutuphane:8443",      // kapi farkli
  "https://alt.odunc.kutuphane",       // alt ad
  "https://baska.site",                // ilgisiz
];

const AYARLAR = [
  ["liste, kimliksiz", { ...AYAR }],
  ["joker, kimliksiz", { ...AYAR, kokenler: "*" }],
  ["joker, kimlikli", { ...AYAR, kokenler: "*", kimlikBilgisi: true }],
  ["liste, kimlikli", { ...AYAR, kimlikBilgisi: true }],
];

const gec = (ayar, koken, kimlikBilgisi) => {
  const istek = { koken, yontem: "GET", kimlikBilgisi };
  return karar(istek, basliklar(istek, ayar)).kullanildi;
};

console.log(`${KOKENLER.length} koken (1 ayni koken + ${KOKENLER.length - 1} dis koken), ${AYARLAR.length} ayar`);
console.log(`\n${"ayar".padEnd(19)}${"sunucu 2xx".padStart(11)}${"istemci kullandi: kimliksiz".padStart(28)}${"kimlikli".padStart(10)}`);
for (const [ad, ayar] of AYARLAR) {
  const k0 = KOKENLER.filter((k) => gec(ayar, k, false)).length;
  const k1 = KOKENLER.filter((k) => gec(ayar, k, true)).length;
  console.log(ad.padEnd(19) + String(KOKENLER.length).padStart(11) + String(k0).padStart(28) + String(k1).padStart(10));
}

const jokerAcik = KOKENLER.filter((k) => k && gec({ ...AYAR, kokenler: "*" }, k, false));
const listeAcik = KOKENLER.filter((k) => k && gec(AYAR, k, false));
console.log(`\nliste ayarinin actigi dis koken: ${listeAcik.length} (${listeAcik.join(", ")})`);
console.log(`joker ayarinin bu kumede actigi dis koken: ${jokerAcik.length}; kume disinda sinir yok`);

console.log(`\n'joker, kimlikli' ayarinda kimlik bilgisi tasiyan istekler:`);
for (const k of KOKENLER.slice(1, 4)) {
  const istek = { koken: k, yontem: "GET", kimlikBilgisi: true };
  const b = basliklar(istek, { ...AYAR, kokenler: "*", kimlikBilgisi: true });
  const c = karar(istek, b);
  console.log(`  ${k.padEnd(30)} sunucu 200 + ${Object.keys(b).length} baslik -> istemci: ${c.kullanildi ? "kullandi" : "atti (" + c.neden + ")"}`);
}
7 koken (1 ayni koken + 6 dis koken), 4 ayar

ayar                sunucu 2xx istemci kullandi: kimliksiz  kimlikli
liste, kimliksiz             7                           3         1
joker, kimliksiz             7                           7         1
joker, kimlikli              7                           7         1
liste, kimlikli              7                           3         3

liste ayarinin actigi dis koken: 2 (https://odunc.kutuphane, https://panel.kutuphane)
joker ayarinin bu kumede actigi dis koken: 6; kume disinda sinir yok

'joker, kimlikli' ayarinda kimlik bilgisi tasiyan istekler:
  https://odunc.kutuphane        sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi)
  https://panel.kutuphane        sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi)
  http://odunc.kutuphane         sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi)

sunucu 2xx sütunu dört ayarda da yedidir. Sunucu hiçbir isteği reddetmiyor; ayar yalnız istemcinin yanıtı kullanıp kullanmayacağını belirliyor. Liste ayarı iki dış kökene açıktır; joker ayarı bu kümedeki altısına ve kümenin dışında sınır yoktur — şema farkı, kapı farkı ve alt ad dahil, adı hiç yazılmamış her köken içeri girer. Joker ile listenin farkı bu satırda 2’ye karşı 6’dır; gerçekte 2’ye karşı sınırsızdır.

Üçüncü satır bu dersin sessiz ayarıdır. kimlikBilgisi açılmış, sunucu iki başlık birden gönderiyor, istek 200 dönüyor — ve istemci yanıtı atıyor. joker, kimlikli satırı joker, kimliksiz satırıyla birebir aynı: ayarı açmak hiçbir şeyi değiştirmedi. Bir kayıt yığınında bu iki ayar ayırt edilemez; iki durumda da 200 vardır, hata yoktur, gecikme yoktur. Yalnız iç panel çalışmaz ve nedeni sunucu tarafında görünmez.

Doğru ayar son satırdır: köken listesi ve kimlik bilgisi izni birlikte. Üç kimlikli istek ancak orada kullanılıyor.

Ön Denetimin Maliyeti

Politika katmanı gerçek bir süreçte ölçülüyor. Özel bir başlık taşıyan istek, gönderilmeden önce bir ön denetim isteği gerektirir; ön denetimin sonucu bir ömür boyunca saklanabilir.

// sunucu.mjs — odunc uclarini dinleyen yerel surec; politika katmani uclarin onunde durur
import http from "node:http";
import { basliklar } from "./politika.mjs";

export const UCLAR = ["/katalog", "/odunc", "/uye", "/rapor", "/arsiv"];

export function baslat(ayar, kapsanan = UCLAR) {
  const sayac = { istek: 0, onDenetim: 0, bayt: 0 };
  const s = http.createServer((q, y) => {
    sayac.istek++;
    if (q.method === "OPTIONS") sayac.onDenetim++;
    const istek = { koken: q.headers.origin ?? null, yontem: q.method };
    const b = kapsanan.includes(q.url) ? basliklar(istek, ayar) : {};
    const govde = q.method === "OPTIONS" ? "" : JSON.stringify({ uc: q.url, durum: "ok" });
    sayac.bayt += govde.length +
      Object.entries(b).reduce((t, [k, v]) => t + k.length + v.length + 4, 0);
    y.writeHead(q.method === "OPTIONS" ? 204 : 200, { "content-type": "application/json", ...b });
    y.end(govde);
  });
  return new Promise((c) => s.listen(0, "127.0.0.1", () =>
    c({ port: s.address().port, sayac, kapat: () => s.close() })));
}
// olcum.mjs — gercek istekler: on denetim maliyeti ve katman kapsaminin olcusu
import { baslat, UCLAR } from "./sunucu.mjs";
import { AYAR } from "./politika.mjs";
import { karar } from "./istemci.mjs";

const KOKEN = "https://panel.kutuphane";
const N = 6;

// Uyumlu istemci: ozel baslikli istek once on denetim ister, sonuc omru boyunca onbelleklenir.
async function gonder(port, uc, { ozel, onbellek, kimlikBilgisi = false }) {
  const kok = `http://127.0.0.1:${port}`;
  if (ozel && !(onbellek && onbellek.has(uc))) {
    const o = await fetch(kok + uc, { method: "OPTIONS", headers: { origin: KOKEN } });
    if (onbellek) onbellek.set(uc, Number(o.headers.get("access-control-max-age") ?? 0));
  }
  const basliklar = { origin: KOKEN, ...(ozel ? { "x-odunc-izlem": "1" } : {}) };
  const y = await fetch(kok + uc, { headers: basliklar });
  const b = Object.fromEntries(y.headers);
  return { durum: y.status, karar: karar({ koken: KOKEN, kimlikBilgisi }, b) };
}

const { port, sayac, kapat } = await baslat(AYAR);

const kosum = async (ad, secenek) => {
  const once = { ...sayac };
  for (let i = 0; i < N; i++) await gonder(port, "/katalog", secenek);
  console.log(`${ad.padEnd(28)}${String(sayac.istek - once.istek).padStart(8)}` +
    `${String(sayac.onDenetim - once.onDenetim).padStart(12)}${String(sayac.bayt - once.bayt).padStart(9)}`);
};

console.log(`${N} gercek istek, tek uc\n`);
console.log(`${"istek bicimi".padEnd(28)}${"gidis-donus".padStart(8)}${"on denetim".padStart(12)}${"bayt".padStart(9)}`);
await kosum("basit (on denetim gerekmez)", { ozel: false });
await kosum("ozel baslikli, onbelleksiz", { ozel: true });
await kosum("ozel baslikli, onbellekli", { ozel: true, onbellek: new Map() });
console.log(`on denetim omru: ${AYAR.onDenetimOmru} sn -> omur boyunca uc basina 1 on denetim`);
kapat();

// Katman kapsami: politika bes ucun dordune kaydedilmis.
const EKSIK = UCLAR.filter((u) => u !== "/arsiv");
const iki = await baslat({ ...AYAR, kimlikBilgisi: true }, EKSIK);
console.log(`\npolitika kaydi ${EKSIK.length}/${UCLAR.length} ucta\n`);
console.log(`${"uc".padEnd(10)}${"sunucu durumu".padStart(14)}${"istemci".padStart(9)}  neden`);
let atilan = 0;
for (const uc of UCLAR) {
  const r = await gonder(iki.port, uc, { ozel: false, kimlikBilgisi: true });
  if (!r.karar.kullanildi) atilan++;
  console.log(uc.padEnd(10) + String(r.durum).padStart(14) +
    (r.karar.kullanildi ? "kullandi" : "atti").padStart(9) + "  " + (r.karar.neden || "-"));
}
console.log(`\n${UCLAR.length} ucun ${atilan} tanesinde yanit istemcide atildi; sunucunun dondugu hata sayisi: 0`);
iki.kapat();
6 gercek istek, tek uc

istek bicimi                gidis-donus  on denetim     bayt
basit (on denetim gerekmez)        6           0      588
ozel baslikli, onbelleksiz        12           6     1758
ozel baslikli, onbellekli          7           1      783
on denetim omru: 600 sn -> omur boyunca uc basina 1 on denetim

politika kaydi 4/5 ucta

uc         sunucu durumu  istemci  neden
/katalog             200 kullandi  -
/odunc               200 kullandi  -
/uye                 200 kullandi  -
/rapor               200 kullandi  -
/arsiv               200     atti  izin basligi yok

5 ucun 1 tanesinde yanit istemcide atildi; sunucunun dondugu hata sayisi: 0

Ön denetimin maliyeti koşumdan bağımsız bir sayıdır: önbelleksiz altı özel istek on iki gidiş-dönüş yapıyor, yani iki katı. Ömür ayarı devreye girdiğinde sayı yediye düşüyor — uç başına bir ön denetim. Politika baytı da üçe katlanıyor: 588’e karşı 1758.

Bu maliyet yalnız özel başlıklı isteklere aittir. İlk satır bunu gösteriyor: özel başlık kullanmayan altı istek hiç ön denetim üretmiyor. Ön denetimden kaçınmanın yolu ayarı gevşetmek değil, isteğin biçimini sadeleştirmektir.

Ayarın Yeri

Son tablo katman sorusudur. Politika beş ucun dördünde kayıtlı; /arsiv ucu unutulmuş. O uç istekleri kabul ediyor, gövdeyi yazıyor, 200 dönüyor. Yanıt istemcide atılıyor ve sunucu tarafında bunun hiçbir izi yok: hata sayısı sıfır, durum kodu diğer dört uçla aynı.

Ayarın kaç yerde tekrarlandığı doğrudan bu riski belirliyor. Politika her uç işleyicisine ayrı kaydediliyorsa beş kayıt noktası vardır ve altıncı uç eklendiğinde altıncı kayıt elle yazılmalıdır. Politika uçların önünde tek bir katman olarak duruyorsa kayıt noktası birdir ve yeni uç kendiliğinden kapsanır — sunucu.mjs içindeki kapsanan listesi tam da bu iki seçenek arasındaki farktır.

Özet

  • Sunucunun kaynaklar arası paylaşımdaki tek sözü yanıt başlıklarıdır; reddi bir hata değil suskunluktur. Dört ayarın dördünde de sunucu yedi isteğin yedisine 2xx döndü.
  • Köken listesi 2 dış kökene açıktır; joker ayar ölçülen kümedeki 6 kökene ve küme dışında sınırsız sayıda kökene açıktır — şema farkı, kapı farkı ve alt ad dahil.
  • Joker ayara kimlik bilgisi izni eklemek hiçbir şeyi değiştirmedi: joker, kimlikli satırı joker, kimliksiz ile birebir aynı çıktı. Sunucu iki başlık gönderdi, 200 döndü, istemci yanıtı attı. Doğru ayar köken listesi ile kimlik bilgisi iznini birlikte kullanır.
  • Ön denetimin maliyeti koşumdan bağımsızdır: önbelleksiz 6 özel istek 12 gidiş-dönüş ve 1758 bayt, ömür ayarıyla 7 gidiş-dönüş ve 783 bayt, özel başlıksız 6 istek 0 ön denetim.
  • Politika 5 ucun 4’üne kaydedilince atlanan uçtan gelen yanıt istemcide atıldı ve sunucuda 0 hata göründü. Uç başına kayıt 5 noktada tekrarlanır; uçların önündeki tek katman 1 noktada durur ve yeni ucu kendiliğinden kapsar.

Sonraki Adım

Kaynaklar arası paylaşım ayarı bir yanıt başlığı kümesinden ibaretti ve ölçüm gösterdi ki asıl soru başlığın hangi katmanda üretildiğidir. Aynı soru, yanıtın taşıdığı öteki başlıklar için de geçerlidir: bağlantının nasıl kurulacağını, içeriğin nasıl yorumlanacağını ve tarayıcının neyi yükleyebileceğini söyleyen başlıklar da aynı biçimde uç uç dağılabilir ya da tek yerden konabilir. Sonraki ders bu başlıkları sayar: kaç uçta tekrarlanıyorlar, tek katmana taşındığında kaç dosyaya dokunuluyor, eksik bir başlık hangi davranışı sessizce açık bırakıyor ve şifreli bağlantının sonlandığı nokta bu resmin neresinde duruyor.

İ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