JSON Web Token'ları (JWT'ler), modern web uygulamalarında API'leri güvence altına almak ve kullanıcı kimlik doğrulamasını yönetmek için de facto standart haline gelmiştir. JWT'ler, sunucunun oturum verilerini saklamasına gerek olmayan durumsuz bir mimari cazibesini sunarken, bu kolaylık önemli güvenlik zorluklarını da beraberinde getirir. Özellikle, bir JWT çalındığında, süresi dolana kadar geçerliliğini korur. Bu yazıda, JWT döndürme ve iptal etme için sağlam stratejileri inceliyor ve bu mekanizmaların hem durumlu hem de durumsuz ortamlarda nasıl işlev gösterdiğini karşılaştırıyoruz.
Temel Sorun: Token Hırsızlığı ve Süre Dolumu
Temel bir JWT uygulamasında, bir token verildikten sonra sunucu, exp (süre dolumu) bildirimi karşılanana kadar imzasına güvenir. Bir saldırgan bu token'ı ele geçirirse, bu pencere kapanana kadar yetkisiz erişime sahip olur. Bunu azaltmak için token döndürme (yeni token'lar verme) ve iptal etme (token'ları erken geçersiz kılma) uygulamamız gerekir.
Strateji 1: Kısa Ömürlü Erişim Token'ları ile Durumsuz Yaklaşım
Saf bir durumsuz mimaride sunucu, bir token'ın iptal edilip edilmediğini görmek için merkezi bir veritabanını kontrol edemez. Bu nedenle, birincil savunma, saldırganlar için fırsat penceresini azaltmaktır. Bunu, kısa ömürlü Erişim Token'larını uzun ömürlü Yenileme Token'larıyla eşleyerek gerçekleştiririz.
Nasıl çalışır:
- İstemci, kısa ömürlü bir Erişim Token'ı (örn. 15 dakika) alır.
- Erişim Token'ı süresi dolduğunda, istemci yeni bir Erişim Token'ı almak için Yenileme Token'ını kullanır.
- Yenileme Token'ı güvenli bir şekilde saklanır (örn. HttpOnly çerez) ve daha uzun bir ömre sahiptir (örn. 7 gün).
Saf bir durumsuz kurulumda bile, Redis gibi hızlı bir anahtar-değer deposunda saklanan bir "token kara listesi" veya "son aktif" zaman damgasını kontrol ederek minimal iptal etme uygulayabiliriz. Bu, geleneksel oturum yönetimine kıyasla minimal olsa da, hafif bir durum bağımlılığı getirir.
// Hızlı bir Redis kontrolü ile bir JWT doğrulama için sahte kod
async function verifyToken(token, redisClient) {
const decoded = jwt.verify(token, SECRET_KEY);
// Token ID'nin kısa süreli bir kara listede olup olmadığını veya güncellenip güncellenmediğini kontrol et
const tokenRecord = await redisClient.get(`token:${decoded.jti}`);
if (tokenRecord === 'revoked') {
throw new Error('Token iptal edildi');
}
// İsteğe bağlı: Kullanıcının "son şifre değiştirme" zamanının token çıkışından daha yeni olup olmadığını kontrol et
const user = await getUser(decoded.sub);
if (decoded.iat < user.lastPasswordChange) {
throw new Error('Token şifre değişikliğinden önce oluşturuldu');
}
return decoded;
}
Strateji 2: Sunucu Tarafı Oturumları ile Durumlu Yaklaşım
Durumlu bir mimaride sunucu, aktif oturumların kaydını tutar. Bu, token geçerliliği üzerinde granüler kontrol sağlar ancak daha fazla altyapı gerektirir. Burada JWT'ler hala bildirimlerin taşıyıcısı olarak kullanılabilir, ancak sunucu bunları aktif oturumların bir veritabanına karşı doğrular.
İptal Mekanikleri:
- Çıkış Yapma: Bir kullanıcı çıkış yaptığında, sunucu veritabanından oturum kaydını siler. O JWT ile yapılan gelecekteki tüm istekler, sunucu ilişkili oturumu bulamadığı için doğrulamayı başarısız kılar.
- Zorla Çıkış Yapma: Sunucu, belirli bir kullanıcı kimliği için tüm oturumları silebilir ve tüm cihazlardaki tüm aktif token'ları hemen geçersiz kılabilir.
Uygulama: Node.js'de Token Kara Listesi
Durumlu veya durumsuz (hafif durum ile) olsun, acil iptal için bir kara liste uygulamak kritik öneme sahiptir. Aşağıda, Node.js ve Redis kullanarak bir JWT'yi nasıl kara listeye alacağınızın bir örneği verilmiştir. jti (JWT ID) bildirimi anahtar olarak saklanır ve süre sonumu, token'ın kalan geçerlilik süresine eşit olarak ayarlanır.
const jwt = require('jsonwebtoken');
const redis = require('redis');
const redisClient = redis.createClient();
// Çıkış yapıldığında bir token'ı kara listeye al
async function blacklistToken(req, res) {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.decode(token); // jti'yi almak için doğrulama olmadan kodu çöz
if (!decoded || !decoded.jti) {
return res.status(400).send('Geçersiz token yapısı');
}
// Token'ın ne zaman süreceğini baz alarak TTL hesapla
const now = Math.floor(Date.now() / 1000);
const ttl = decoded.exp - now;
// jti'yi bir süre sonumu ile Redis'te sakla
await redisClient.setex(`blacklist:${decoded.jti}`, ttl, 'revoked');
res.status(200).send('Başarıyla çıkış yapıldı');
}
// İstek doğrulaması sırasında kara listeyi kontrol eden ara yazılım
async function checkBlacklist(req, res, next) {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.decode(token);
if (decoded && decoded.jti) {
const isBlacklisted = await redisClient.get(`blacklist:${decoded.jti}`);
if (isBlacklisted) {
return res.status(401).send('Token iptal edildi');
}
}
next();
}
Güvenli Döndürme İçin En İyi Uygulamalar
- Eşsiz Kimlikler Kullanın: JWT'lerinizde her zaman eşsiz bir
jtibildirimi dahil edin. Bu, iptal amaçları için token'ların hassas bir şekilde tanımlanmasını sağlar. - Yenileme Token'larını Döndürün: Bir erişim token'ı yenilendiğinde, yeni bir yenileme token'ı verin ve eskisini geçersiz kılın. Bu, bir yenileme token'ı kullanıldıktan sonra çalınırsa tekrar oynatma saldırılarını önler.
- Güvenli Depolama: Uygulamanız XSS'e karşı savunmasızsa JWT'leri
localStorage'ta saklamayın. Çapraz site komut dosyası risklerini azaltmak için bunun yerineHttpOnlyveSecureçerezleri kullanın. - Anomali İzleme: Farklı coğrafi konumlardan birden fazla eşzamanlı giriş gibi, token kullanımının olağan dışı desenlerini tespit etmek için günlük kaydı ve izleme uygulayın.
Sonuç
Güvenli JWT döndürme ve iptal etme uygulamak, güvenliği performansla dengelemeyi gerektirir. Durumsuz mimariler, tam oturumları saklamadan iptal etmeyi işlemek için kısa token ömürlerine ve verimli önbellekleme katmanlarına (Redis gibi) heavily rely eder. Durumlu mimariler, artan sunucu belleği ve veritabanı yükü pahasına daha kolay iptal etme sunar. Çoğu modern uygulama için, kısa ömürlü erişim token'ları kullanmak, yenileme token'larını döndürmek ve token kara listesi için hızlı bir anahtar-değer deposu kullanmak gibi hibrit bir yaklaşım; güvenlik, ölçeklenebilirlik ve kullanıcı deneyimi arasında en iyi dengeyi sağlar.