Spring Security Filter Chain
Önce şunu oku: IoC ve Dependency Injection
30 saniyede özet
Spring Security, istek controller'a varmadan önce sırayla geçtiği bir turnike dizisidir. Önce 'sen kimsin?', en sonda 'buna iznin var mı?' sorulur. 401 ile 403'ün farkı da buradan gelir.
Bir binaya girerken önce turnikelerden geçersin. Turnikede takılan birini kattaki resepsiyon hiç görmez.
Adım adım oku
- Her istek controller'a varmadan önce sırayla güvenlik filtrelerinden, yani turnikelerden geçer.
- Token'ı olmayan istek ilk turnikede, sen kimsin sorusunda durur ve 401 alır.
- Kimliği belli ama rolü yetmeyen istek son turnikede, iznin var mı sorusunda durur ve 403 alır.
- Controller ve onun @ControllerAdvice'ı bu iki isteği hiç görmez; hatalar filtrelerde cevaba çevrilir.
-
Bayt: Bütün hataları @ControllerAdvice'ta yakalıyorum. Güvenlik hataları da oraya düşer, değil mi?
-
Sen: Düşmesi gerekmez mi?
-
Bayt: Düşmüyor! 401 başka bir yerden, başka bir biçimde dönüyor.
-
Bayt: Turnikede takılan misafiri kattaki resepsiyon hiç görmez.
Spring Security bir “katman” değil, bir servlet filter chainSpring Security'nin isteği sırayla geçirdiği servlet filtreleri. `DispatcherServlet`'ten önce çalışır — bu yüzden güvenlik hataları `@ControllerAdvice` ile yakalanamaz.Sözlükte gör →dir. Tek bir FilterChainProxy servlet konteynerine kaydolur ve içinde sırayla çalışan onlarca filtre barındırır.
Bu tek gerçek, kafa karıştıran üç durumu birden açıklar.
Kendin izle
Spring Security filter chain — bir isteğin yolculuğu
Tohum 1GET /api/ordersBearer (geçerli)kural: hasRole(ADMIN)SecurityContext
null — henüz doldurulmadı
SecurityContextHolderFilterVar olan oturumdan SecurityContext yüklerbekliyorHeaderWriterFilterGüvenlik başlıklarını eklerbekliyorCorsFilterOrigin kontrolübekliyorCsrfFilterDurum değiştiren isteklerde token ararbekliyorLogoutFilter/logout isteğini yakalarbekliyorUsernamePasswordAuthenticationFilterForm login için POST /login isteğini işlerbekliyorBearerTokenAuthenticationFilterAuthorization: Bearer başlığını doğrularbekliyorAnonymousAuthenticationFilterHâlâ kimlik yoksa anonim token koyarbekliyorExceptionTranslationFilterSonraki filtrenin fırlattığını 401 veya 403’e çevirirbekliyorAuthorizationFilterYetki kararını burada verir — zincirin SONUbekliyorDispatcherServlet → Controller
Şu an ne oldu?
SecurityContextHolderFilter
Var olan oturumdan SecurityContext yükler
Aklında kalsın: SecurityContext hâlâ boş — kimlik doğrulama henüz yapılmadı.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
Varsayılan: geçerli JWT, ROLE_USER, endpoint hasRole("ADMIN") istiyor. Sekiz filtreyi geçip son filtrede 403 alıyor.
Sırayla dene:
- Kimlik bilgisini
Yokyap.AnonymousAuthenticationFilterdevreye giriyor ve sonuç 401 oluyor — 403 değil. - Rolü
ADMINyap. Aynı zincir, bu kez 200. - Süresi dolmuş JWT seç. İstek
BearerTokenAuthenticationFilter’da kesiliyor;AuthorizationFilter’a hiç sıra gelmiyor. - CSRFTarayıcının isteğe otomatik eklediği kimlik bilgilerini başka bir sitenin sömürmesi. Token elle eklenen bir başlıktaysa bu saldırı yüzeyi yoktur; cookie'deyse vardır.Sözlükte gör → token’ı olmayan POST seç. Zincirin çok başında kesiliyor — kimlik filtrelerine bile ulaşmıyor.
Kafam karıştı, daha basit anlat
İstek, sırayla dizilmiş güvenlik görevlilerinin önünden geçer. Biri kimliğine bakar, sonuncusu da “buraya girme yetkin var mı” diye sorar.
Authentication ile authorization arasındaki fark nedir?
Güvenlik, Spring MVC’den önce çalışır
Token'sız bir istek /api/accounts adresine geldi. Controller'daki metodun ilk satırı çalışır mı? Cevabı göster
Hayır. İstek, Spring MVC’ye ulaşmadan güvenlik filtre zincirinde reddedilir ve 401 döner.
Servlet Container └─ FilterChainProxy ← Spring Security burada ├─ SecurityContextHolderFilter ├─ ... (8 filtre daha) └─ AuthorizationFilter └─ DispatcherServlet ← Spring MVC ancak burada başlıyor └─ ControllerSonuç: @ControllerAdvice güvenlik istisnalarını yakalayamaz. 401 veya 403 döndüğünde istek Spring MVC’ye hiç ulaşmamıştır.
Hata gövdesini özelleştirmek istiyorsan @ControllerAdvice değil, şunlar gerekir:
http.exceptionHandling(ex -> ex // 401 — kimlik doğrulanmamış .authenticationEntryPoint(customEntryPoint) // 403 — kimlik var, yetki yok .accessDeniedHandler(customAccessDeniedHandler));Kimlik doğrulama başta, yetkilendirme sonda. AuthorizationFilter zincirin son filtresidir. Yani geçerli bir token’ın seni içeri aldığı anlamına gelmez — sadece son filtreye kadar getirir.
Bu ayrım hata ayıklarken kritiktir:
- İstek
BearerTokenAuthenticationFilter’da kesildi → kimlik sorunu - İstek
AuthorizationFilter’da kesildi → yetki sorunu
401 mi, 403 mü? Kararı ExceptionTranslationFilter verir ve kural basittir: kim olduğunu söylemediysen 401, kim olduğun belli ama iznin yoksa 403.
| Çağıran | Sonuç | Anlamı |
|---|---|---|
| Anonim | 401 Unauthorized | ”Kim olduğunu söyle” — AuthenticationEntryPoint |
| Kimliği var, yetkisi yok | 403 Forbidden | ”Kim olduğunu biliyorum, izin yok” — AccessDeniedHandler |
Karar nasıl veriliyor?· istersen atla
ExceptionTranslationFilter, kendisinden sonraki filtrenin fırlattığı AccessDeniedException’ı yakalar. Sonra çağıranın anonim olup olmadığına bakıp isteği bu iki yoldan birine gönderir.
Aşağıdaki örnek bir bankadan: aynı uygulamada mobil uygulamanın kullandığı JWT’li stateless API ve şube personelinin oturumlu paneli. Üstüne ‘bu hesap senin mi?’ kontrolü, kendi filtren ve bunları doğrulayan testler.
Derinleş · Mobil bankacılık API'si ve şube paneli: iki filter chain, bir uygulama 6 dosya · ~149 satır · ilk okumada atlayabilirsin
Kafam karıştı, daha basit anlat
Güvenlik kapısı binanın dışındadır. Kapıda geri çevrilen istek içeri hiç girmediği için, içerideki hata masası onu hiç görmez.
Spring Security filtre zinciri `DispatcherServlet`'e göre nerede durur?
Her bileşeni, filtre zincirinde mi yoksa MVC katmanında mı çalıştığına göre ayır.
Tuzaklar
var auth = SecurityContextHolder.getContext().getAuthentication();if (auth == null) { /* asla çalışmaz */ }
// Kimliksiz isteklerde bile AnonymousAuthenticationToken vardırif (auth == null || !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken) { }Anonim kullanıcı null değildir· istersen atla
AnonymousAuthenticationFilter, o ana kadar kimlik belirlenmemişse bir AnonymousAuthenticationToken koyar. Bu yüzden “giriş yapılmamış” durumunu null kontrolüyle yakalayamazsın.
CSRF: ne zaman gerekli, ne zaman değil. CSRF saldırısı, tarayıcının çerezleri otomatik göndermesine dayanır. Buradan doğrudan bir kural çıkar:
| Kimlik taşıma yöntemi | CSRF koruması |
|---|---|
Çerez tabanlı oturum (JSESSIONID) | Gerekli |
Authorization: Bearer başlığı | Gereksiz — tarayıcı başlığı otomatik göndermez |
| Çereze konmuş JWT | Gerekli — çerez olduğu an risk geri gelir |
http.csrf(AbstractHttpConfigurer::disable) .sessionManagement(s -> s.sessionCreationPolicy(STATELESS));csrf().disable() yazmadan önce son satıra bak: token’ı çerezde taşıyorsan CSRF hâlâ gereklidir.
Her durumu doğru HTTP yanıtına yerleştir.
Session vs JWT
| Oturum (session) | JWT | |
|---|---|---|
| Durum nerede | Sunucuda | Token’ın içinde |
| Ölçekleme | Sticky session veya Redis | Doğal olarak stateless |
| İptal etmek | Anında — oturumu sil | Zor — token süresi dolana kadar geçerli |
| Boyut | Küçük çerez | Her istekte taşınan yük |
Kafam karıştı, daha basit anlat
Session, sunucunun seni hatırlamasıdır. JWT ise cebinde taşıdığın imzalı bir kart. Kartı geri almak zordur, bu yüzden kısa süreli verilir.
JWT’nin en çok atlanan maliyeti iptal edilememesidir. Bir kullanıcıyı anında engellemen gerekiyorsa ya kısa ömürlü token + refresh token kullanırsın ya da bir iptal listesi tutarsın — ki bu da seni yeniden durumlu (stateful) hâle getirir.
Kendini sına
Spring Security filtreleri, istek controller'a varmadan önce çalışır.
Kimlik bilgisi taşımayan bir istek `hasRole("ADMIN")` korumalı bir endpoint'e geldi. Hangi HTTP durumu döner?
Aklında kalacak üç şey
- 1 Güvenlik, Spring MVC'den önce çalışan bir filtre zinciridir. Bu yüzden @ControllerAdvice güvenlik hatalarını yakalayamaz.
- 2 Sıra şöyle: kimlik doğrulama zincirin başında, yetki kontrolü en sonunda. Geçerli bir token seni yalnızca son filtreye kadar getirir.
- 3 Kim olduğunu söylemeyen istek 401 alır, kim olduğu belli ama izni olmayan istek 403.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- Spring Boot EkosistemiOturum Yönetimi ve Stateless ÖlçeklemeSunucu yeniden başladı ve bütün kullanıcılar çıkış yaptı. Oturumlar nerede duruyordu?Derse git
- Spring Boot EkosistemiOAuth2 ve OIDC — Şifreni Vermeden Nasıl Giriş Yapılır?Uygulama şifreni hiç görmeden seni nasıl tanıyor? Ve biri dönüş kodunu çalarsa ne olur?Derse git
- Spring Boot EkosistemiParola Saklama — Kullanıcı Tablosu Sızdı, Şimdi Ne Olacak?Kullanıcı tablosu sızdı. Parolalar SHA-256 ile saklanıyordu. Saldırgan iki kullanıcının aynı parolayı kullandığını hemen gördü. Nasıl?Derse git
- Spring Boot EkosistemiSSRF — Müşterinin Verdiği Adresi Sunucu Neden Çağırmamalı?Müşteri, faturalarının indirileceği adresi yazdı. Sunucu indirdi. İnen dosyada bulut hesabının geçici anahtarları vardı. Nasıl?Derse git