Birçok projede, özellikle de mikroservis mimarilerine geçiş yapan sistemlerde, kimlik doğrulama için JWT (JSON Web Token) kullanıyoruz. JWT’nin temel vaadi, sunucu tarafında oturum durumu (session state) tutmaya gerek kalmadan, yani “stateless” bir şekilde kullanıcı kimliğini doğrulamak. Bu durum, özellikle ölçeklenebilirlik ve dağıtık sistemler açısından çok cazip. Ancak, bu “stateless” yapının bir bedeli var: token’ı iptal etme ihtiyacı doğduğunda işler biraz karmaşıklaşıyor.
Yıllar içinde birçok farklı senaryoda JWT kullandım ve her seferinde “peki ya bu token’ı hemen iptal etmem gerekirse?” sorusuyla karşılaştım. Bir kullanıcının şifresi değiştiğinde, yetkileri güncellendiğinde veya en kötüsü, token’ı çalındığında, o token’ın geçerliliğini anında yitirmesi gerekir. İşte bu noktada JWT’nin stateless doğası ile gerçek dünyanın acil durumları arasında bir gerilim yaşanıyor. Bu yazıda, bu gerilimi nasıl yönettiğimi, hangi yaklaşımları denediğimi ve pratikte hangi çözümlerin işime yaradığını anlatacağım.
JWT’nin Stateless Vaadi ve Gerçekleri
JWT’ler, aslında birer dijital imzalı veri paketidir. İçinde kullanıcının kimliği, yetkileri, token’ın ne zaman oluşturulduğu (iat) ve ne zaman sona ereceği (exp) gibi bilgiler bulunur. Bu bilgiler bir anahtarla imzalanır ve istemciye gönderilir. İstemci her istekte bu token’ı sunucuya geri gönderir, sunucu da sadece imzanın geçerliliğini ve token’ın süresinin dolup dolmadığını kontrol eder. Eğer imza doğruysa ve süre dolmamışsa, token’ın içeriğindeki bilgilere güvenilir ve işlemi yapar.
Bu modelin güzelliği, sunucunun her istekte bir veritabanına veya önbelleğe gidip “bu kullanıcı şu anda giriş yapmış mıydı?” diye sormak zorunda kalmamasıdır. Bu, özellikle yüksek trafikli uygulamalar veya birden fazla sunucunun çalıştığı dağıtık mimariler için büyük bir performans ve ölçeklenebilirlik avantajı sağlar. On binlerce kullanıcının aynı anda işlem yaptığı bir ortamda, her isteği veritabanından kontrol etmek yerine JWT kullanmak, sistemin genel yanıt süresini ve kaynak tüketimini belirgin biçimde düşürür.
Ancak, bu stateless yapı, aynı zamanda token’ın sunucu tarafından aktif olarak takip edilmediği anlamına gelir. Bir JWT bir kez imzalandığında ve istemciye verildiğinde, son kullanma süresi dolana kadar geçerliliğini korur. İşte bu durum, acil bir iptal ihtiyacı doğduğunda bizi zor durumda bırakır. Örneğin, bir üretim firmasının ERP sisteminde, bir operatörün vardiya bitiminde yetkisinin derhal kalkması gerektiğinde, 8 saatlik bir token’ın bu sürenin sonuna kadar geçerli kalması ciddi bir güvenlik açığı oluşturabilirdi.
İptal İhtiyacı Neden Doğar?
Token iptali, sadece teorik bir endişe değil, günlük operasyonlarda sıkça karşılaşılan pratik bir gereksinimdir. Kendi yan ürünlerimin backend’inde veya müşteri projelerinde karşılaştığım bazı senaryoları şöyle sıralayabilirim:
- Kullanıcı Çıkışı (Logout): Kullanıcı “çıkış yap” butonuna tıkladığında, o anda elinde tuttuğu tüm token’ların geçerliliğini yitirmesi beklenir. Aksi takdirde, tarayıcı geçmişinden veya önbellekten elde edilen bir token, kullanıcı tekrar oturum açmadan bile erişim sağlayabilir. Bir mobil uygulamamda, kullanıcı çıkış yaptığında token’ın sunucuda iptal edilmemesi, bazen kullanıcının tekrar giriş yapmadan eski oturumuna dönmesine neden oluyordu, bu da hem bir güvenlik hem de kullanıcı deneyimi sorunu yaratıyordu.
- Şifre Değişikliği: Bir kullanıcı şifresini değiştirdiğinde, eski şifreyle elde edilmiş tüm token’ların hemen geçersiz kılınması kritik bir güvenlik gereksinimidir. Bu, olası bir şifre çalınması durumunda saldırganın eski token’larla sisteme erişmesini engeller.
- Yetki Değişiklikleri: Kullanıcının rolleri veya yetkileri güncellendiğinde, elindeki token’ın eski yetkileri taşıması istenmez. Yeni yetkilerin hemen devreye girmesi için token’ın yenilenmesi veya iptal edilmesi gerekir. Bir yöneticinin yetkisi alındığında bu değişikliğin anında etkili olması, sistemin bütünlüğü için kritiktir.
- Güvenlik İhlali (Token Çalınması): Belki de en kritik senaryo budur. Bir kullanıcının JWT’si bir şekilde çalınırsa, saldırgan bu token ile kullanıcının yetkileriyle sisteme erişebilir. Böyle bir durumda, çalınan token’ın derhal iptal edilmesi gerekir. Benim deneyimimde, böyle bir olay yaşandığında, hızlı bir iptal mekanizması olmaması, potansiyel veri sızıntılarına veya yetkisiz işlemlere yol açabilirdi.
- Hesap Kapatma veya Askıya Alma: Kullanıcının hesabı kapatıldığında veya geçici olarak askıya alındığında, o hesapla ilişkili tüm aktif oturumların ve token’ların sonlandırılması gerekir.
Bu senaryolar, JWT’nin “stateless” vaadinin her zaman yeterli olmadığını açıkça gösteriyor. Gerçek dünya, çoğu zaman durum takibi (stateful) gereksinimlerini beraberinde getiriyor.
Token İptal Yöntemleri ve Trade-off’ları
JWT’nin doğası gereği stateless olsa da, bu iptal ihtiyaçlarını karşılamak için çeşitli yöntemler geliştirilmiştir. Her bir yöntemin kendine göre avantajları ve dezavantajları vardır ve seçim, projenin güvenlik gereksinimleri, performans hedefleri ve mimari kısıtlamalarına bağlıdır.
1. Blacklisting (Kara Liste)
Bu yöntem, süresi henüz dolmamış, ancak geçersiz kılınması istenen token’ları merkezi bir yerde (genellikle hızlı bir önbellek sistemi olan Redis’te) tutmaya dayanır. Bir token iptal edildiğinde, onun benzersiz ID’si (JTI - JWT ID claim’i) veya doğrudan token’ın kendisi bu kara listeye eklenir. Her gelen istekte, sunucu önce token’ın imzasını ve süresini kontrol eder, ardından kara listede olup olmadığını sorgular. Eğer listedeyse, token geçersiz sayılır.
Artıları:
- Anında İptal: Bir token kara listeye alındığında, neredeyse anında geçersiz hale gelir.
- Basit Uygulama: Redis gibi sistemlerle kurulumu nispeten kolaydır.
Eksileri:
- Stateless Yapıdan Uzaklaşma: Bu yöntem, her ne kadar ayrı bir servis olsa da, sonuçta sunucunun bir “durum” takip etmesini gerektirir. Bu, JWT’nin temel vaadinden ödün vermek demektir.
- Performans Overhead: Her istekte kara liste kontrolü yapmak, sistem üzerinde ek bir yük oluşturur. Özellikle yüksek trafikli sistemlerde, Redis sunucusuna yapılan her çağrı, gecikmeye neden olabilir.
- Depolama Maliyeti: Eğer token’lar uzun ömürlüyse ve çok sayıda kullanıcı varsa, kara liste zamanla büyüyebilir ve Redis üzerinde önemli bir bellek tüketimine neden olabilir. Ancak, token’ların
expsüresi kadar TTL (Time-To-Live) ile saklanması bu maliyeti yönetilebilir kılar.
Bir müşteri projesinde, güvenlik gereksinimleri çok sıkı olduğu için blacklisting kullanmak zorunda kaldım. Her istekte küçük bir ek gecikme oluştu, ancak bu, projenin toplam yanıt süresi hedefleri içinde kabul edilebilir bir değerdi.
# FastAPI örneği: Blacklist kontrolü
from fastapi import Depends, HTTPException, status
from jose import jwt, JWTError
from datetime import datetime
from redis import Redis
# ... (JWT ayarları, SECRET_KEY, ALGORITHM, vb.) ...
redis_client = Redis(host='localhost', port=6379, db=0)
async def verify_token(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
jti: str = payload.get("jti")
if jti is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token (missing JTI)")
# Blacklist kontrolü
if redis_client.exists(f"blacklist:{jti}"):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token revoked")
# Diğer kontroller (exp, user_id, vb.)
return payload
except JWTError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
# Token'ı blacklist'e alma fonksiyonu
def revoke_token(jti: str, exp_timestamp: int):
# Token'ın kalan ömrü kadar Redis'te tut
ttl = exp_timestamp - int(datetime.utcnow().timestamp())
if ttl > 0:
redis_client.setex(f"blacklist:{jti}", ttl, "revoked")
2. Short-Lived Access Tokens + Refresh Tokens
Bu yaklaşım, JWT’nin stateless doğasını korurken iptal sorununu hafifletmeyi amaçlar. Kullanıcı ilk giriş yaptığında, iki farklı token alır:
- Kısa Ömürlü Access Token: Genellikle 5-15 dakika gibi kısa bir süre geçerli olan token. API istekleri için kullanılır.
- Uzun Ömürlü Refresh Token: Genellikle günler veya haftalarca geçerli olabilir. Yeni bir access token almak için kullanılır. Bu token, sunucu tarafında bir veritabanında veya önbellekte depolanır ve aktif olarak takip edilir.
Access token’ın ömrü kısa olduğu için, çalınsa bile çok kısa bir süre geçerli kalır. Bir token’ı iptal etme ihtiyacı doğduğunda, sadece refresh token’ı veritabanından silmek yeterlidir. Bu, kullanıcının yeni access token almasını engeller ve mevcut access token süresi dolduğunda otomatik olarak geçersiz hale gelir.
Artıları:
- Daha Güvenli: Access token’ların kısa ömrü, çalınmaları durumunda oluşturacakları zararı minimize eder.
- Performans: API isteklerinin çoğu hala tamamen stateless olarak işlenir, çünkü access token’lar için veritabanı kontrolü yapılmaz.
- İptal Mekanizması: Refresh token’lar stateful olduğu için kolayca iptal edilebilir.
Eksileri:
- Ek Karmaşıklık: İki farklı token türünü yönetmek (oluşturma, yenileme, depolama) sistem mimarisine ek karmaşıklık getirir.
- Refresh Token Güvenliği: Refresh token’lar uzun ömürlü olduğu için, bunların çalınması daha ciddi bir güvenlik riskidir. Bu yüzden, refresh token’ların güvenli bir şekilde depolanması ve iletilmesi kritik öneme sahiptir (HTTP-only cookie’ler, kriptolu depolama vb.).
- Anında İptal Değil: Eğer bir access token çalınırsa, süresi dolana kadar (örn. 5-15 dakika) geçerli kalır. Bu süre zarfında saldırgan sistemde işlem yapabilir. Bu durum, acil iptal gerektiren senaryolarda (örn. güvenlik ihlali) bir dezavantajdır.
Kendi yan ürünümün mobil uygulamasının backend’inde bu yaklaşımı kullandım. Kullanıcıların sık sık giriş yapmasını engellerken, refresh token’ı güvenli bir HTTP-only cookie içinde tutarak güvenlik riskini azalttım. Access token’lar 15 dakika ömürlüydü, refresh token’lar ise 30 gün.
3. Stateful Session Management (Geleneksel Yaklaşım)
Bazı durumlarda, JWT’nin vaat ettiği stateless yapıdan tamamen vazgeçip, geleneksel stateful oturum yönetimine geri dönmek, özellikle yüksek güvenlik gerektiren veya karmaşık yetkilendirme senaryoları olan projelerde daha mantıklı olabilir. Bu yaklaşımda, sunucu her kullanıcının oturum durumunu kendi veritabanında veya önbelleğinde tutar (örneğin, bir oturum ID’si ve bu ID’ye karşılık gelen kullanıcı bilgileri). İstemciye sadece bir oturum ID’si (cookie içinde) gönderilir ve her istekte bu ID ile sunucudan oturum bilgileri sorgulanır.
Artıları:
- Tam Kontrol: Oturumların tam kontrolü sunucuda olduğu için, anında ve kolayca iptal edilebilir. Kullanıcı yetkileri anında güncellenebilir.
- Daha Az Karmaşıklık (Bazı Açılardan): JWT’nin imza doğrulama, süre yönetimi, refresh token mekanizması gibi detaylarıyla uğraşmaya gerek kalmaz.
- Güvenlik: Oturum ID’si çalınsa bile, sunucu tarafında ek kontroller (IP adresi, user-agent kontrolü vb.) yaparak yetkisiz erişimi engellemek daha kolaydır.
Eksileri:
- Ölçeklenebilirlik Zorlukları: Her istekte sunucu tarafında durum sorgulaması yapılması gerektiği için, yüksek trafikli veya dağıtık sistemlerde performans ve ölçeklenebilirlik sorunları yaratabilir. Oturum verilerinin birden fazla sunucu arasında paylaşılması (sticky sessions veya merkezi bir oturum deposu) ek karmaşıklık getirir.
- Performans Overhead: Her API çağrısında bir veritabanı veya önbellek sorgusu yapmak, gecikmeyi artırır.
Para transferleri gibi bazı kritik modüller için JWT yerine stateful oturum yönetimi tercih edilebilir. Performanstan biraz ödün verilse de, güvenlik ve anında iptal gereksinimleri çoğu zaman bu yaklaşımı zorunlu kılar. Oturumları Redis Cluster üzerinde tutmak, ölçeklenebilirlik sorununu bir nebze hafifletir.
4. Distributed Cache (Dağıtık Önbellek) Kullanımı
Yukarıdaki blacklisting ve refresh token yaklaşımlarında sıkça adı geçen Redis veya Memcached gibi dağıtık önbellek sistemleri, JWT iptal mekanizmalarının temelini oluşturur. Bu sistemler, yüksek performanslı anahtar-değer depolama yetenekleri sayesinde, token ID’lerini hızlıca kontrol etmek için idealdir.
Artıları:
- Hız: Bellek içi depolama sayesinde çok düşük gecikmeli sorgular sunarlar.
- Ölçeklenebilirlik: Cluster veya Sentinel modları ile yatayda ölçeklenebilirler, bu da yüksek trafikli sistemler için önemlidir.
- TTL Desteği: Token’ların
expsüresi kadar otomatik olarak silinmesini sağlayan TTL özelliği, depolama maliyetlerini optimize eder.
Eksileri:
- Ek Altyapı: Sisteme ek bir bileşen (Redis sunucusu veya cluster) eklemek, kurulum ve yönetim maliyeti demektir.
- Tutarlılık: Dağıtık sistemlerde önbellek tutarlılığı bazen sorun olabilir, ancak JWT iptali için genellikle “eventual consistency” kabul edilebilir.
Halihazırda başka amaçlarla (örneğin uygulama verisini önbelleğe almak için) çalışan bir Redis Cluster varsa, aynı altyapıyı JWT refresh token’larının takibi ve acil durumlarda access token blacklisting için de kullanmak, mevcut altyapıdan maksimum verim almayı sağlar.
Pragmatik Bir Yaklaşım: Hybrid Çözümler
Tecrübelerime göre, tek bir yaklaşım tüm senaryoları mükemmel bir şekilde karşılamıyor. Çoğu zaman, farklı ihtiyaçlara yönelik hibrit çözümler geliştirmek gerekiyor. Benim genellikle tercih ettiğim yaklaşım şudur:
- Kısa Ömürlü Access Token’lar ve Uzun Ömürlü Refresh Token’lar: Bu, temel güvenlik ve performans dengesini sağlar. Access token’lar 5-10 dakikalık bir ömre sahipken, refresh token’lar 1 hafta veya 1 ay gibi daha uzun ömürlüdür. Refresh token’lar, veritabanında veya Redis gibi güvenli bir önbellekte depolanır ve aktif olarak takip edilir.
- Kullanıcı Çıkışı ve Şifre Değişikliği İçin Refresh Token İptali: Bir kullanıcı çıkış yaptığında veya şifresini değiştirdiğinde, ilgili kullanıcının tüm aktif refresh token’larını veritabanından silerim. Bu, kullanıcının yeni access token almasını engeller. Mevcut access token’lar süresi dolana kadar geçerli kalır, ancak bu süre zaten kısadır.
- Acil Durumlar (Token Çalınması) İçin Blacklisting: Eğer bir güvenlik ihlali yaşanır ve bir access token’ın çalındığı tespit edilirse, bu token’ın JTI’sini (JWT ID) hemen Redis’teki bir kara listeye eklerim. Bu, çalınan token’ın anında geçersiz kılınmasını sağlar. Bu senaryo nadir olduğu için, her istekteki ek overhead kabul edilebilir bir maliyettir. Blacklist’teki token’lara da kalan ömrü kadar TTL veririm, böylece Redis belleği gereksiz yere şişmez.
Bu hibrit yaklaşım, hem JWT’nin stateless avantajlarından yararlanmamı hem de kritik güvenlik senaryolarında anında iptal yeteneğine sahip olmamı sağlıyor. Bir müşteri projesinde, bu yaklaşımı uygulayarak, hem geliştirme ekibinin hem de güvenlik ekibinin beklentilerini karşılayabildim.
# Örnek: Refresh token ve Access token oluşturma
import datetime
from jose import jwt
SECRET_KEY = "mysecretkey"
ALGORITHM = "HS256"
def create_access_token(data: dict, expires_delta: datetime.timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.datetime.utcnow() + expires_delta
else:
expire = datetime.datetime.utcnow() + datetime.timedelta(minutes=15) # Kısa ömürlü
to_encode.update({"exp": expire, "iat": datetime.datetime.utcnow(), "jti": str(uuid.uuid4())}) # JTI ekliyoruz
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def create_refresh_token(data: dict, expires_delta: datetime.timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.datetime.utcnow() + expires_delta
else:
expire = datetime.datetime.utcnow() + datetime.timedelta(days=7) # Uzun ömürlü
to_encode.update({"exp": expire, "iat": datetime.datetime.utcnow(), "jti": str(uuid.uuid4())}) # JTI ekliyoruz
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# Kullanıcı şifre değiştirdiğinde tüm refresh token'ları sil
def revoke_all_user_refresh_tokens(user_id: int):
# Veritabanından user_id'ye ait tüm refresh token'ları sil
# Örn: db.session.query(RefreshToken).filter_by(user_id=user_id).delete()
print(f"Kullanıcı {user_id} için tüm refresh token'lar silindi.")
# Access token'ı acil durumda blacklist'e al
def blacklist_access_token(jti: str, exp_timestamp: int):
ttl = exp_timestamp - int(datetime.datetime.utcnow().timestamp())
if ttl > 0:
redis_client.setex(f"blacklist:{jti}", ttl, "revoked")
print(f"Token {jti} blacklist'e alındı, {ttl} saniye sonra silinecek.")
Yukarıdaki kod snippet’leri, bir FastAPI uygulamasında bu mantığı nasıl uyguladığıma dair basit bir örnek teşkil ediyor. create_access_token ve create_refresh_token fonksiyonları, farklı sürelerde token’lar üretirken, her ikisine de benzersiz bir jti (JWT ID) ekliyorum. Bu jti, özellikle blacklist mekanizmasında token’ı tekil olarak tanımlamak için kullanılıyor. revoke_all_user_refresh_tokens ile bir kullanıcının tüm oturumlarını sonlandırabilirken, blacklist_access_token ile acil bir durumda belirli bir access token’ı anında geçersiz kılabiliyorum. Bu yapılar, özellikle production ortamında, hem esneklik hem de güvenlik sağlıyor.
Performans ve Ölçeklenebilirlik Endişeleri
Token iptali için durum takibi eklediğimizde, doğal olarak performans ve ölçeklenebilirlik endişeleri ortaya çıkar. Özellikle blacklisting yaklaşımında, her gelen isteğin Redis’e gidip token’ın kara listede olup olmadığını sorgulaması, ek bir gecikme yaratır. Bu gecikme, milisaniyeler düzeyinde olsa bile, yüksek trafikli sistemlerde saniyede on binlerce istekte birikerek önemli bir darboğaz haline gelebilir.
Yüksek trafikli bir API Gateway’de, her isteğe bir Redis sorgusu eklemek toplam yanıt süresini hissedilir ölçüde artırabiliyor. Bu yüzden, bu tür kontrolleri optimize etmek kritik hale geliyor:
- TTL Yönetimi: Blacklist’e alınan token’ları sadece kalan geçerlilik süreleri kadar Redis’te tutmak, depolama maliyetini ve Redis’in bellek tüketimini azaltır. Eğer bir token’ın ömrü 10 dakika kalmışsa, kara listede de maksimum 10 dakika kalmalıdır.
- Önbellekleme: Eğer mümkünse, kara liste kontrollerini daha üst katmanlarda (örn. API Gateway’de) önbelleğe almak, Redis’e yapılan çağrı sayısını azaltabilir. Ancak bu, kara listenin güncellenmesi ile önbelleğin güncellenmesi arasındaki tutarlılık sorununu beraberinde getirir.
- Asenkron İptal: Bazı senaryolarda, anında iptal yerine “neredeyse anında” veya “eventual consistency” kabul edilebilir. Örneğin, bir kullanıcının yetkileri değiştiğinde, mevcut access token’ının süresinin dolması beklenir ve yeni access token ile güncel yetkilere erişir. Sadece acil güvenlik ihlallerinde anında blacklisting tercih edilir.
- Verimli Veri Yapıları: Redis’te blacklist için
SETveyaHASHgibi veri yapılarını kullanmak,EXISTSsorgularınınO(1)zaman karmaşıklığında çalışmasını sağlar, bu da performansı maksimize eder. - Ağ Gecikmesi: Redis sunucusunun veya kümesinin uygulama sunucularına yakın konumlandırılması, ağ gecikmesini azaltarak performansı artırır. AWS’te aynı Availability Zone içinde konumlandırmak gibi.
Pratikte iyi işleyen denge şudur: blacklisting’i yalnızca kritik güvenlik durumlarında (token çalınması) devreye almak; normal çıkış işlemlerinde ise refresh token’ı veritabanından silip kısa ömürlü access token’ların süresinin dolmasını beklemek. Bu yaklaşım, sistemin genel performansını korurken acil durumlar için de bir güvenlik ağı sağlar.
Sonuç
JWT’ler, dağıtık sistemlerde kimlik doğrulama için güçlü bir araçtır ve stateless vaadi önemli avantajlar sunar. Ancak, gerçek dünya senaryolarında token iptali ihtiyacı doğduğunda, bu stateless yapı tek başına yeterli olmaz. Benim deneyimimde, saf bir “stateless” yaklaşım yerine, ihtiyaçlara göre harmanlanmış “hybrid” bir çözüm en dengeli sonucu veriyor.
Kısa ömürlü access token’lar ve stateful refresh token’lar kullanmak, çoğu senaryo için yeterli güvenlik ve performans sağlarken, acil durumlar için Redis tabanlı bir blacklist mekanizması bulundurmak, çalınan token’ların anında geçersiz kılınması yeteneğini sunar. Önemli olan, projenizin güvenlik gereksinimlerini, performans hedeflerini ve mimari kısıtlamalarını iyi analiz ederek, sizin için en uygun trade-off’u belirlemektir.
Unutmayın, hiçbir güvenlik çözümü %100 kusursuz değildir. Önemli olan, riskleri anlamak ve bu riskleri yönetilebilir bir seviyeye indirmektir. Bir sonraki yazımda, bir üretim ERP’sinde karşılaştığım “phantom transaction” sorununu ve bunun çözümü için kullandığım event-sourcing desenini anlatacağım.