---
title: 'İlişki Tabanlı Erişim Denetimi'
source: 'https://academia.sh/tr/kurslar/kimlik-ve-yetki/iliski-tabanli-erisim-denetimi'
course: 'Kimlik Doğrulama ve Yetkilendirme'
language: tr
updated: '2026-08-17T18:06:49+00:00'
license: 'CC BY-SA 4.0'
---

# İlişki Tabanlı Erişim Denetimi

Özneler ve nesneler arasındaki bağların birinci sınıf veri hâline gelmesi, erişim sorusunun çizge üzerinde bir ulaşılabilirlik sorgusuna dönüşmesi, geçişken erişim ve tek nesne denetimiyle listeleme sorgusunun maliyet farkı.

Önceki iki derste S7 yanlış karara bağlandı. U-1001, dokuz numaralı okuma listesini U-1002
ile paylaşmıştı; U-1002 bu listeyi görebilmeliydi. Ne rol tabanlı model ne öznitelik tabanlı
model bunu ifade edebildi, çünkü paylaşım ne öznenin ne nesnenin bir özelliğidir — ikisi
arasındaki bağdır.

**İlişki tabanlı erişim denetimi (relationship-based access control)** bu bağları birinci
sınıf veri hâline getirir. Sistemdeki özneler ve nesneler bir çizgenin düğümleri, aralarındaki
bağlar da adlandırılmış kenarlarıdır. Erişim sorusu, bu çizge üzerinde bir ulaşılabilirlik
sorgusuna dönüşür: özneden nesneye, belirli bir kenar dizisini izleyerek gidilebiliyor mu?

## Çizge, Kenarlar ve Yol Desenleri

Kütüphane ödünç servisinin ilişkileri az sayıda kenar türüyle yazılabilir. `sahip`, bir üyeyi
kendi kaydına bağlar. `ait`, bir kaydı şubesine bağlar. `calisir` ve `yonetir`, personeli
şubesine bağlar. `paylasildi`, bir listeyi paylaşıldığı kişiye ya da gruba bağlar. `uyesi`,
bir kişiyi gruba bağlar.

Erişim kuralı, bu kenarlardan oluşan bir **yol desenidir**. "Kendi ödünç kaydını
görüntüleyebilir" kuralı tek adımlık bir desendir: `sahip`. "Şube görevlisi şubesinin
kayıtlarını görüntüleyebilir" kuralı iki adımlıdır ve ikinci adımda kenar ters yönde
izlenir: personelden şubeye `calisir`, şubeden kayda `ait` kenarının tersi.

Aşağıdaki değerlendirici bu desenleri yürütüyor. Her adımda bir düğüm kümesinden komşu
kümesine geçiliyor; bu, Veri Yapıları kursundaki enine aramanın kenar türüne göre süzülmüş
biçimidir.

```bash
cat > rebac.mjs <<'EOF'
const KENARLAR = [                      // [kaynak, iliski, hedef]
  ["U-1001", "sahip", "odunc:501"], ["U-1002", "sahip", "odunc:502"],
  ["U-1005", "sahip", "odunc:503"], ["U-1001", "sahip", "liste:9"],
  ["odunc:501", "ait", "S-02"], ["odunc:502", "ait", "S-02"],
  ["odunc:503", "ait", "S-01"], ["ceza:77", "ait", "S-02"],
  ["P-2001", "calisir", "S-02"], ["Y-3001", "yonetir", "S-02"],
  ["liste:9", "paylasildi", "U-1002"], ["liste:9", "paylasildi", "grup:okuma-kulubu"],
  ["U-1006", "uyesi", "grup:okuma-kulubu"],
];

// Desendeki "~iliski", kenarin ters yonde izlenmesi demektir.
const DESENLER = {
  "goruntule:odunc": [["sahip"], ["calisir", "~ait"], ["yonetir", "~ait"]],
  "guncelle:odunc":  [["calisir", "~ait"], ["yonetir", "~ait"]],
  "olustur:odunc":   [["calisir", "~ait"], ["yonetir", "~ait"]],
  "sil:odunc":       [["yonetir", "~ait"]],
  "sil:ceza":        [["yonetir", "~ait"]],
  "goruntule:liste": [["sahip"], ["~paylasildi"], ["uyesi", "~paylasildi"]],
};

function adim(dugumler, iliski) {
  const ters = iliski.startsWith("~");
  const ad = ters ? iliski.slice(1) : iliski;
  const sonuc = new Set();
  for (const [k, i, h] of KENARLAR) {
    if (i !== ad) continue;
    if (!ters && dugumler.has(k)) sonuc.add(h);
    if (ters && dugumler.has(h)) sonuc.add(k);
  }
  return sonuc;
}

function rebac(ozne, eylem, nesne) {
  for (const desen of DESENLER[`${eylem}:${nesne.split(":")[0]}`] ?? []) {
    let cephe = new Set([ozne]);
    for (const iliski of desen) cephe = adim(cephe, iliski);
    if (cephe.has(nesne)) return [true, desen.join(" -> ")];
  }
  return [false, "ulasilabilir yol yok"];
}

const SENARYOLAR = [
  ["S1", "U-1001", "goruntule", "odunc:501", true],
  ["S2", "U-1001", "goruntule", "odunc:502", false],
  ["S3", "P-2001", "goruntule", "odunc:502", true],
  ["S4", "P-2001", "goruntule", "odunc:503", false],
  ["S5", "P-2001", "guncelle",  "odunc:502", false],
  ["S6", "Y-3001", "sil",       "ceza:77",   true],
  ["S7", "U-1002", "goruntule", "liste:9",   true],
  ["S8", "U-1005", "goruntule", "liste:9",   false],
];

console.log("senaryo | ReBAC | dogru | uyum | bulunan yol");
console.log("--------|-------|-------|------|--------------------------");
let uyan = 0;
for (const [ad, ozne, eylem, nesne, dogru] of SENARYOLAR) {
  const [karar, yol] = rebac(ozne, eylem, nesne);
  if (karar === dogru) uyan++;
  console.log(`${ad}      | ${(karar ? "izin" : "ret").padEnd(5)} | ` +
    `${(dogru ? "izin" : "ret").padEnd(5)} | ${(karar === dogru ? "+" : "-").padEnd(4)} | ${yol}`);
}
console.log(`\nReBAC dogru karar: ${uyan}/${SENARYOLAR.length}`);

console.log("\ngecisken erisim (ortak kume disinda):");
for (const kod of ["U-1006", "U-1005"]) {
  const [karar, yol] = rebac(kod, "goruntule", "liste:9");
  console.log(`  ${kod} -> liste:9 : ${karar ? "izin" : "ret"}  (${yol})`);
}
EOF
node rebac.mjs
```

```text
senaryo | ReBAC | dogru | uyum | bulunan yol
--------|-------|-------|------|--------------------------
S1      | izin  | izin  | +    | sahip
S2      | ret   | ret   | +    | ulasilabilir yol yok
S3      | izin  | izin  | +    | calisir -> ~ait
S4      | ret   | ret   | +    | ulasilabilir yol yok
S5      | izin  | ret   | -    | calisir -> ~ait
S6      | izin  | izin  | +    | yonetir -> ~ait
S7      | izin  | izin  | +    | ~paylasildi
S8      | ret   | ret   | +    | ulasilabilir yol yok

ReBAC dogru karar: 7/8

gecisken erisim (ortak kume disinda):
  U-1006 -> liste:9 : izin  (uyesi -> ~paylasildi)
  U-1005 -> liste:9 : ret  (ulasilabilir yol yok)
```

S7 üç derste ilk kez doğru nedenle çözüldü: listeye U-1002'den bir `paylasildi` kenarı
gidiyor. S8 aynı desenle reddedildi, çünkü U-1005'e giden kenar yok.

Son iki satır modelin asıl gücünü gösteriyor. U-1006 listeye doğrudan bağlı değil; okuma
kulübü grubunun üyesi ve liste o grupla paylaşılmış. İki adımlık yol bu bağı kuruyor.
Öznitelik tabanlı modelde aynı sonuca ulaşmak için grubun tüm üyelerinin nesnenin öznitelik
alanına kopyalanması ve grup her değiştiğinde bu kopyanın güncellenmesi gerekirdi. Çizgede
grup üyeliği tek bir kenardır ve kenarın eklenmesi tüm listeleri etkiler.

## Modelin Ulaşamadığı Yer

S5 bu modelde yanlış. Ödünç kaydının mesai dışında güncellenmemesi bir bağlam kısıtıdır ve
çizgede karşılığı yoktur — saat, iki düğüm arasındaki bir kenar değildir.

Bu, önceki dersin aynasıdır. Öznitelik tabanlı model bağlamı görüyor ama ilişkiyi
göremiyordu; ilişki tabanlı model ilişkiyi görüyor ama bağlamı göremiyor. İki modelin
eksikleri örtüşmüyor, tamamlıyor. Bu gözlem sonraki dersin çıkış noktası.

## İki Ayrı Soru ve Maliyetleri

İlişki tabanlı modelde iki farklı soru sorulur ve maliyetleri aynı değildir.

Birincisi **denetimdir**: "bu özne bu nesneye erişebilir mi?" Yanıt bulunduğu anda arama
durur. İkincisi **listelemedir**: "bu öznenin erişebildiği nesneler hangileri?" Bu soru bir
liste sayfası çizmek için gerekir ve tüm desenlerin yürütülmesini gerektirir.

```bash
cat > rebac-maliyet.mjs <<'EOF'
const KENARLAR = [
  ["U-1001", "sahip", "odunc:501"], ["U-1002", "sahip", "odunc:502"],
  ["U-1005", "sahip", "odunc:503"], ["U-1001", "sahip", "liste:9"],
  ["odunc:501", "ait", "S-02"], ["odunc:502", "ait", "S-02"],
  ["odunc:503", "ait", "S-01"], ["ceza:77", "ait", "S-02"],
  ["P-2001", "calisir", "S-02"], ["Y-3001", "yonetir", "S-02"],
  ["liste:9", "paylasildi", "U-1002"], ["liste:9", "paylasildi", "grup:okuma-kulubu"],
  ["U-1006", "uyesi", "grup:okuma-kulubu"],
];
const DESENLER = {
  "goruntule:odunc": [["sahip"], ["calisir", "~ait"], ["yonetir", "~ait"]],
  "goruntule:liste": [["sahip"], ["~paylasildi"], ["uyesi", "~paylasildi"]],
};

let kenarOkuma = 0;
function adim(dugumler, iliski) {
  const ters = iliski.startsWith("~"), ad = ters ? iliski.slice(1) : iliski;
  const sonuc = new Set();
  for (const [k, i, h] of KENARLAR) {
    kenarOkuma++;
    if (i !== ad) continue;
    if (!ters && dugumler.has(k)) sonuc.add(h);
    if (ters && dugumler.has(h)) sonuc.add(k);
  }
  return sonuc;
}

const yuru = (ozne, desen) => {
  let cephe = new Set([ozne]);
  for (const iliski of desen) cephe = adim(cephe, iliski);
  return cephe;
};

function denetle(ozne, izin, nesne) {              // ilk eslesen desende durur
  for (const desen of DESENLER[izin] ?? []) if (yuru(ozne, desen).has(nesne)) return true;
  return false;
}

function erisilebilirler(ozne, izin) {             // tum desenler yurunur
  const hepsi = new Set();
  for (const desen of DESENLER[izin] ?? []) for (const d of yuru(ozne, desen)) hepsi.add(d);
  const tur = izin.split(":")[1] + ":";
  return [...hepsi].filter((d) => d.startsWith(tur)).sort();
}

console.log("tek nesne denetimi (ilk eslesen desende durur):");
for (const [ozne, nesne] of [["P-2001", "odunc:502"], ["U-1001", "odunc:501"],
                             ["U-1005", "odunc:502"]]) {
  kenarOkuma = 0;
  const k = denetle(ozne, "goruntule:odunc", nesne);
  console.log(`  ${ozne} -> ${nesne} : ${(k ? "izin" : "ret").padEnd(4)} ` +
    `(${String(kenarOkuma).padStart(2)} kenar okumasi)`);
}

console.log("\nerisilebilir nesnelerin listesi (tum desenler yurunur):");
for (const [ozne, izin] of [["P-2001", "goruntule:odunc"], ["U-1001", "goruntule:odunc"],
                            ["U-1006", "goruntule:liste"], ["U-1005", "goruntule:liste"]]) {
  kenarOkuma = 0;
  const liste = erisilebilirler(ozne, izin);
  console.log(`  ${ozne} ${izin.padEnd(16)} -> [${(liste.join(", ") || "bos").padEnd(22)}]` +
    ` (${kenarOkuma} kenar okumasi)`);
}
EOF
node rebac-maliyet.mjs
```

```text
tek nesne denetimi (ilk eslesen desende durur):
  P-2001 -> odunc:502 : izin (39 kenar okumasi)
  U-1001 -> odunc:501 : izin (13 kenar okumasi)
  U-1005 -> odunc:502 : ret  (65 kenar okumasi)

erisilebilir nesnelerin listesi (tum desenler yurunur):
  P-2001 goruntule:odunc  -> [odunc:501, odunc:502  ] (65 kenar okumasi)
  U-1001 goruntule:odunc  -> [odunc:501             ] (65 kenar okumasi)
  U-1006 goruntule:liste  -> [liste:9               ] (52 kenar okumasi)
  U-1005 goruntule:liste  -> [bos                   ] (52 kenar okumasi)
```

Kenar okuma sayıları bu küçük çizgeye özgüdür; oranların anlamı değişmez. İki gözlem
belirleyicidir.

Birincisi, **ret kararı en pahalı karardır**. İzin kararı ilk eşleşen desende durur; ret
kararı verilebilmesi için tüm desenlerin tüketilmiş olması gerekir. U-1005'in reddi altmış
beş kenar okumasına mal oldu, U-1001'in izni on üçe. Yetkisiz istek trafiğinin denetim
katmanına yüklediği maliyet, yetkili trafikten yüksektir; hız sınırı bu yüzden denetim
katmanının önünde durur.

İkincisi, listeleme sorgusu denetim sorgusunun tekrarı değildir. Bir liste sayfasını
"tüm kayıtları çek, her biri için denetim çağır" biçiminde çizmek, sayfa boyutu kadar
denetim çağrısı üretir. Doğru yapı, erişilebilir nesne kümesini bir kez üretip veri
sorgusunu o kümeyle sınırlamaktır. Bu kümenin sorguya nasıl geçirileceği ise ölçeğe bağlı
bir tasarım kararıdır: küçük kümeler doğrudan bir kimlik listesi olarak, büyük kümeler
kaynak sorgusuna eklenen bir birleştirme koşulu olarak taşınır.

## Çizgenin Bakımı

İlişki tabanlı modelin işletimdeki maliyeti kenarların bakımıdır.

**Kenar, kaynak veriyle birlikte yazılır.** Bir ödünç kaydı oluşturulduğunda `sahip` kenarı
da oluşur; ikisi ayrı işlemlerde yazılırsa arada kalan istekler yanlış karara bağlanır.
Kenar yazımı, kaydın yazıldığı işlemin içinde olmalıdır.

**Kenar, kaynak veriyle birlikte silinir.** Silinmemiş kenarlar sessizce erişim açık
bırakır ve hiçbir hata üretmedikleri için fark edilmezler. Çizgede kaynağı olmayan
kenarların düzenli olarak taranması, İlişkisel Veritabanı Yönetimi kursundaki yetim kayıt
denetiminin buradaki karşılığıdır.

**Yol uzunluğu sınırlanır.** Desen ne kadar uzunsa arama o kadar geniş bir düğüm kümesine
yayılır. Sınırsız derinlikli bir devralma zinciri — grubun grubu, onun da grubu — hem
maliyeti öngörülemez kılar hem de kimin neye eriştiğini insan gözüyle izlenemez hâle
getirir.

## Özet

- İlişki tabanlı erişim denetimi bağları birinci sınıf veri yapar; erişim sorusu çizge
  üzerinde bir ulaşılabilirlik sorgusuna dönüşür.
- Erişim kuralı bir yol desenidir; kenarlar ileri ya da ters yönde izlenebilir.
- Geçişken erişim — grupla paylaşılan bir kaydın grup üyelerine açılması — tek bir kenarla
  ifade edilir; öznitelik alanına kopyalama gerektirmez.
- Model bağlam kısıtlarını ifade edemez; saat gibi bilgilerin çizgede karşılığı yoktur.
- Ret kararı tüm desenlerin tüketilmesini gerektirdiği için izin kararından pahalıdır;
  listeleme sorgusu tek tek denetim çağrılarıyla değil, erişilebilir küme üretilerek yapılır.

## Sonraki Adım

Üç modelin üçü de aynı senaryo kümesinde farklı yerlerden eksik kaldı ve eksikleri
birbirini tamamlıyor. Bu, dördüncü bir modele değil, kuralların yazıldığı yere ilişkin bir
soruya götürüyor: bu kuralların hepsi uygulama kodunun içinde dağınık biçimde mi duracak?
Sonraki ders kararı koddan ayıran politika tabanlı yetkilendirmeyi ele alıyor ve dört
modelin kararlarını aynı tabloda karşılaştırıyor.
