---
title: 'Oturum Güvenliği'
source: 'https://academia.sh/tr/kurslar/kimlik-ve-yetki/oturum-guvenligi'
course: 'Kimlik Doğrulama ve Yetkilendirme'
language: tr
updated: '2026-08-17T18:06:49+00:00'
license: 'CC BY-SA 4.0'
---

# Oturum Güvenliği

Girişte oturum kimliğinin yenilenmesi, oturum sabitlemenin gözlenebilir sonucu, eşzamanlı oturumların listelenip tek tek sonlandırılması, boşta kalma ile mutlak ömür sınırlarının birlikte uygulanması ve çerez niteliklerinin rolü.

Önceki dersin son kuralı, başarılı bir sıfırlamanın hesaba ait tüm oturumları kapatmasıydı.
O cümlenin karşılığı, oturumun sunucu tarafında bir kayıt olmasıdır: kapatılabilen şey,
kaydı tutulan şeydir. Bu ders oturumun açılışından sonlandırılmasına kadar geçen ömrünü ele
alıyor.

Oturum Tabanlı Kimlik Doğrulama dersinde oturumun ne olduğu kurulmuştu: sunucu bir kayıt
tutar, kaydın kimliğini tarayıcıya çerezle verir, sonraki isteklerde o kimliği görünce
kullanıcıyı tanır. Buradaki sorular kimliğin ne zaman değiştiği, ne kadar yaşadığı ve kimin
kapatabildiğidir.

## Oturum Sabitleme ve Girişte Yenileme

Kullanıcı siteye girmeden önce de bir oturuma sahip olabilir — dil tercihi, sepet, arama
geçmişi gibi durumlar anonim bir oturumda tutulur. Kullanıcı giriş yaptığında bu oturumun
ne olacağı bir karardır ve iki seçenek vardır: aynı kimlik girişli sayılır ya da kimlik
yenilenir.

Birinci seçenek **oturum sabitleme (session fixation)** adı verilen soruna açıktır.
Kullanıcının giriş öncesindeki oturum kimliğini bilen biri, kullanıcı giriş yaptığı anda
aynı kimliğe sahip olmaya devam eder ve artık girişli bir oturumun kimliğini elinde tutar.
Kimliğin nasıl öğrenildiği ayrı bir konudur; kurulumun kusuru, öğrenilmiş bir kimliğin
giriş sonrasında da geçerli kalmasıdır.

Aşağıdaki blok iki kurulumu yerel bir sunucuda yan yana çalıştırıyor.

```bash
cat > oturum-sunucu.mjs <<'EOF'
import { createServer } from "node:http";
import { randomBytes } from "node:crypto";

const oturumlar = new Map();                  // kimlik -> { uye }

const kimlikOku = (istek) =>
  /(?:^|;\s*)oturum=([^;]+)/.exec(istek.headers.cookie ?? "")?.[1];

function yeniOturum(uye = null) {
  const kimlik = randomBytes(24).toString("base64url");
  oturumlar.set(kimlik, { uye });
  return kimlik;
}

const cerez = (kimlik) => `oturum=${kimlik}; Path=/; HttpOnly; SameSite=Lax`;

createServer((istek, yanit) => {
  const yol = new URL(istek.url, "http://yerel").pathname;
  const kimlik = kimlikOku(istek);

  if (yol === "/saglik") return yanit.writeHead(200).end("hazir\n");

  if (yol === "/") {                          // anonim ziyaret: oturum acilir
    if (kimlik && oturumlar.has(kimlik)) return yanit.writeHead(200).end(kimlik + "\n");
    const yeni = yeniOturum();
    return yanit.writeHead(200, { "set-cookie": cerez(yeni) }).end(yeni + "\n");
  }

  if (yol === "/kusurlu/giris") {              // reddedilmesi gereken kurulum
    const o = oturumlar.get(kimlik);
    if (!o) return yanit.writeHead(400).end("oturum yok\n");
    o.uye = "U-1001";                          // ayni kimlik, artik girisli
    return yanit.writeHead(200).end(kimlik + "\n");
  }

  if (yol === "/giris") {                      // dogru kurulum
    if (kimlik) oturumlar.delete(kimlik);      // eski kimlik gecersizlesir
    const yeni = yeniOturum("U-1001");
    return yanit.writeHead(200, { "set-cookie": cerez(yeni) }).end(yeni + "\n");
  }

  if (yol === "/ben") {
    const o = oturumlar.get(kimlik);
    return yanit.writeHead(o ? 200 : 401).end((o ? (o.uye ?? "anonim") : "gecersiz") + "\n");
  }

  yanit.writeHead(404).end("");
}).listen(8495, "127.0.0.1");
EOF
node oturum-sunucu.mjs & sunucu=$!
until curl -sf http://127.0.0.1:8495/saglik > /dev/null; do :; done

dene() {
  kavanoz=$(mktemp); yol=$1
  once=$(curl -s -c "$kavanoz" -b "$kavanoz" http://127.0.0.1:8495/)
  sonra=$(curl -s -c "$kavanoz" -b "$kavanoz" "http://127.0.0.1:8495$yol")
  echo "  giris oncesi kimlik : ${once:0:14}..."
  echo "  giris sonrasi kimlik: ${sonra:0:14}..."
  echo "  kimlik degisti mi   : $([ "$once" = "$sonra" ] && echo hayir || echo evet)"
  echo "  eski kimlikle /ben  : $(curl -s -H "cookie: oturum=$once" http://127.0.0.1:8495/ben)"
  rm -f "$kavanoz"
}

echo "--- reddedilmesi gereken kurulum (/kusurlu/giris) ---"; dene /kusurlu/giris
echo "--- dogru kurulum (/giris) ---";                        dene /giris

kill $sunucu
rm -f oturum-sunucu.mjs
```

```text
--- reddedilmesi gereken kurulum (/kusurlu/giris) ---
  giris oncesi kimlik : BpWfAGGoOo1Zvs...
  giris sonrasi kimlik: BpWfAGGoOo1Zvs...
  kimlik degisti mi   : hayir
  eski kimlikle /ben  : U-1001
--- dogru kurulum (/giris) ---
  giris oncesi kimlik : qmM42rssGO2qEm...
  giris sonrasi kimlik: w1CarutBqemvD1...
  kimlik degisti mi   : evet
  eski kimlikle /ben  : gecersiz
```

Kimlik değerleri her çalıştırmada değişir; iki kurulumun farkı değişmez. Reddedilmesi
gereken kurulumda giriş öncesinde ele geçmiş bir kimlik, giriş sonrasında U-1001 olarak
yanıt alıyor. Doğru kurulumda aynı kimlik `gecersiz` dönüyor, çünkü giriş anında eski
kayıt silinip yenisi açılıyor.

Kuralın yazılışı tek cümledir: **yetki düzeyi değiştiren her işlemde oturum kimliği
yenilenir.** Giriş bunun en belirgin örneğidir; parola değişikliği, ikinci etmenin
tamamlanması ve rol yükselmesi de aynı kuralın kapsamındadır. Yenileme sırasında anonim
oturumdaki taşınması gereken veriler yeni kayda kopyalanır — dil tercihi gibi. Taşınmayan
şey kimliğin kendisidir.

## Eşzamanlı Oturumlar

Bir üye aynı anda birden çok yerden girmiş olabilir: şubedeki masaüstünden, telefonundan,
salon terminalinden. Bunların her biri ayrı bir oturum kaydıdır ve ayrı ayrı
sonlandırılabilir olmalıdır. Kullanıcıya gösterilen "açık oturumlar" listesi, hesabında
beklenmedik bir erişim olup olmadığını görebildiği tek yerdir.

Kayıt, oturum kimliğinin kendisini değil karmasını tutar. Gerekçe önceki dersteki sıfırlama
belirteciyle aynıdır: veritabanını okuyabilen biri, açık oturumları kullanabilecek durumda
olmamalıdır.

```bash
cat > oturum-depo.mjs <<'EOF'
import { randomBytes, createHash } from "node:crypto";
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync(":memory:");
db.exec(`CREATE TABLE oturum(
  karma TEXT PRIMARY KEY, uye TEXT NOT NULL, aygit TEXT NOT NULL,
  acilis INTEGER NOT NULL, son_erisim INTEGER NOT NULL)`);

const karmala = (k) => createHash("sha256").update(k).digest("hex");
const BOSTA_MS = 30 * 60 * 1000;              // bosta kalma siniri: 30 dakika
const MUTLAK_MS = 12 * 60 * 60 * 1000;        // mutlak omur: 12 saat
const T0 = Date.parse("2026-03-10T09:00:00Z");   // ornegin "simdi"si

function ac(uye, aygit, acilisFark, erisimFark) {
  const kimlik = randomBytes(24).toString("base64url");
  db.prepare("INSERT INTO oturum VALUES (?,?,?,?,?)")
    .run(karmala(kimlik), uye, aygit, T0 - acilisFark, T0 - erisimFark);
  return kimlik;
}

const dk = 60 * 1000, sa = 60 * dk;
ac("U-1001", "masaustu-sube-1", 2 * sa, 3 * dk);
const telefon = ac("U-1001", "telefon", 1 * sa, 45 * dk);
const eski    = ac("U-1001", "salon-terminali", 14 * sa, 5 * dk);
ac("U-1002", "telefon", 30 * dk, 1 * dk);

const durum = `CASE
  WHEN ? - son_erisim > ${BOSTA_MS} THEN 'bosta doldu'
  WHEN ? - acilis     > ${MUTLAK_MS} THEN 'mutlak omur doldu'
  ELSE 'gecerli' END`;

const listele = (uye) => db.prepare(
  `SELECT aygit, (? - acilis)/60000 AS acilis_dk, (? - son_erisim)/60000 AS bosta_dk,
          ${durum} AS durum
   FROM oturum WHERE uye = ? ORDER BY acilis`).all(T0, T0, T0, T0, uye);

const yaz = (baslik, uye) => {
  console.log(baslik);
  for (const s of listele(uye)) {
    console.log(`  ${s.aygit.padEnd(16)} acilis=${String(s.acilis_dk).padStart(3)} dk  ` +
      `bosta=${String(s.bosta_dk).padStart(2)} dk  ${s.durum}`);
  }
};

yaz("U-1001 oturumlari:", "U-1001");

const silinen = db.prepare("DELETE FROM oturum WHERE karma = ? AND uye = ?")
  .run(karmala(eski), "U-1001").changes;
console.log(`\ntek oturum sonlandirildi (etkilenen satir: ${silinen})`);
yaz("U-1001 oturumlari:", "U-1001");

const hepsi = db.prepare("DELETE FROM oturum WHERE uye = ?").run("U-1001").changes;
console.log(`\nsifirlama sonrasi tum oturumlar kapatildi (etkilenen satir: ${hepsi})`);
console.log("U-1001 kalan oturum :",
  db.prepare("SELECT COUNT(*) s FROM oturum WHERE uye='U-1001'").get().s);
console.log("U-1002 kalan oturum :",
  db.prepare("SELECT COUNT(*) s FROM oturum WHERE uye='U-1002'").get().s);
console.log("telefon kimligi hala gecerli mi:",
  db.prepare("SELECT COUNT(*) s FROM oturum WHERE karma = ?").get(karmala(telefon)).s > 0);
EOF
node oturum-depo.mjs
```

```text
U-1001 oturumlari:
  salon-terminali  acilis=840 dk  bosta= 5 dk  mutlak omur doldu
  masaustu-sube-1  acilis=120 dk  bosta= 3 dk  gecerli
  telefon          acilis= 60 dk  bosta=45 dk  bosta doldu

tek oturum sonlandirildi (etkilenen satir: 1)
U-1001 oturumlari:
  masaustu-sube-1  acilis=120 dk  bosta= 3 dk  gecerli
  telefon          acilis= 60 dk  bosta=45 dk  bosta doldu

sifirlama sonrasi tum oturumlar kapatildi (etkilenen satir: 2)
U-1001 kalan oturum : 0
U-1002 kalan oturum : 1
telefon kimligi hala gecerli mi: false
```

Silme deyimindeki `AND uye = ?` koşulu, sonraki konunun tamamının habercisidir: kullanıcı
yalnız kendi oturumunu kapatabilmelidir. Koşul olmadan, kimliği bilen herkes herhangi bir
oturumu kapatabilir. Bu, nesne düzeyinde erişim denetiminin bu dersteki ilk görünümüdür.

Son üç satır sıfırlama kuralının kanıtıdır: U-1001'in tüm oturumları kapandı, U-1002'nin
oturumu etkilenmedi ve daha önce elde tutulan telefon kimliği artık kayıtta yok.

## İki Ömür Sınırı

Çıktının durum sütunu iki ayrı sınırı birlikte değerlendiriyor.

**Boşta kalma sınırı**, son erişimden bu yana geçen süreyi ölçer. Amacı, kullanıcının başında
olmadığı bir aygıtta oturumun açık kalmasını engellemektir. Telefon oturumu bu sınıra takıldı:
kırk beş dakikadır kullanılmıyor.

**Mutlak ömür**, oturumun açılışından bu yana geçen süreyi ölçer ve kullanım ne olursa olsun
uygulanır. Amacı, sürekli kullanılan bir oturumun süresiz yaşamasını engellemektir. Salon
terminalindeki oturum bu sınıra takıldı: beş dakika önce kullanılmış olmasına karşın on dört
saattir açık.

İkisi birlikte bulunur çünkü ayrı sorunları çözer. Yalnız boşta kalma sınırı olan bir
kurulumda, ele geçirilmiş bir oturum düzenli isteklerle sonsuza kadar tazelenebilir. Yalnız
mutlak ömür olan bir kurulumda, terk edilmiş bir aygıttaki oturum sınıra kadar açık kalır.

Değerlerin seçimi hesabın yetkisine göre değişir. Ödünç masasındaki personel oturumu için
kısa boşta kalma sınırı, üye oturumu için daha uzun bir sınır seçilebilir. Ölçüt, oturumun
açabildiği kapının genişliğidir.

Sınırların uygulanması bir sorguda yapılır; ayrı bir temizleme görevi ise süresi dolmuş
kayıtları siler. İkisi birbirinin yerine geçmez: temizleme görevi gecikirse sorgu yine
doğru karar verir, sorgu gecikmeli çalışırsa temizleme kaydı yine de kaldırır.

## Kimliğin Taşınması

Oturum kimliği tarayıcıyla sunucu arasında çerezle taşınır ve çerezin nitelikleri kimliğin
korunmasının bir parçasıdır. Yukarıdaki sunucu üç nitelik yazıyor.

`HttpOnly`, çerezin sayfadaki betiklerden okunmasını engeller. İstemci Güvenliği konusunda
ele alınan siteler arası betik çalıştırma bir sayfada gerçekleştiğinde, bu nitelik oturum
kimliğinin doğrudan okunmasının önüne geçer.

`SameSite`, çerezin başka sitelerden başlatılan isteklere eklenmesini sınırlar ve siteler
arası istek sahteciliğine karşı ilk savunmadır.

`Secure` niteliği yukarıdaki örnekte yoktur, çünkü yerel sunucu düz HTTP konuşuyor. Gerçek
bir dağıtımda bu nitelik zorunludur: çerez yalnız güvenli taşıma üzerinden gönderilir.
Taşıma güvenliği olmadan diğer iki niteliğin değeri sınırlıdır — kimlik ağ üzerinde açık
gider.

Bu üç nitelik, kimliğin başkasının eline geçmesini — **oturumun ele geçirilmesini (session
hijacking)** — zorlaştırır ama tümüyle engellemez. Bu yüzden sunucu tarafında ikinci bir
katman bulunur: oturum kaydında aygıt ve kaynak bilgisi tutulur, beklenmedik bir değişimde
oturum sonlandırılıp yeniden kimlik doğrulama istenir. Bu denetimin sıkılığı bir
ödünleşimdir; ağ değiştiren bir kullanıcıyı sürekli dışarı atan bir kural, kullanıcıyı
oturumu hiç kapatmamaya iter.

## Özet

- Yetki düzeyi değiştiren her işlemde oturum kimliği yenilenir; yenilenmeyen kurulumda giriş
  öncesi bilinen bir kimlik giriş sonrasında da geçerli kalır.
- Oturumlar sunucu tarafında kayıt olarak tutulur ve kimliğin karması saklanır; kapatılabilen
  şey kaydı tutulan şeydir.
- Bir hesabın eşzamanlı oturumları listelenebilir, tek tek kapatılabilir ve sıfırlama
  sırasında toplu olarak sonlandırılabilir.
- Boşta kalma sınırı ile mutlak ömür ayrı sorunları çözer ve birlikte uygulanır.
- Çerez nitelikleri kimliğin taşınmasını korur; sunucu tarafındaki aygıt denetimi ikinci
  katmandır ve sıkılığı kullanıcı davranışıyla dengelenir.

## Sonraki Adım

Bu konu boyunca soru tekti: sunucu, karşısındakinin kim olduğunu nasıl güvenle biliyor?
Oturum silme sorgusundaki `AND uye = ?` koşulu ise başka bir soruyu işaret etti — kim
olduğu bilinen birinin neyi yapabileceği. Sonraki konu bu ikinci soruya geçiyor ve rol
tabanlı erişim denetimiyle başlıyor: izinlerin kişiye değil göreve bağlanması, rol
devralmasının etkin izin kümesini nasıl gizlediği ve kütüphane personelinin yetkilerinin
bir çizelgeden nasıl türetildiği.
