Yazılarımız

Veri Akademi

SPRİNG SECURİTY VE JWT: AUTHENTİCATİON/AUTHORİZATİON KURGUSU VE YAYGIN TUZAKLAR

JWT ile güvenlik kurgusu kurmak “token üret ve gönder” kadar basit görünse de, authentication ve authorization sınırlarını doğru çizmediğinizde sistem hızla kırılganlaşır. Spring Security ise esnekliği nedeniyle hem çok güçlü hem de yanlış anlaşılmaya açık bir çerçevedir. Bu yazıda, JWT tabanlı akışı uçtan uca ele alıp üretimde sık rastlanan tuzakları, doğru yapı taşlarıyla birlikte netleştireceğiz.

Odak noktamız, Spring Security’nin modern yaklaşımı olan SecurityFilterChain üzerinden stateless (durumsuz) bir API güvenliği kurmak: token doğrulama, claim’lerin role/authority’ye çevrilmesi, endpoint bazlı yetki kuralları ve CORS/CSRF gibi çevresel başlıklar.

Makale boyunca “kopyala-çalıştır” beklentisi yerine, doğru mimari kararları destekleyen örnekler göreceksiniz. Daha kapsamlı Spring Boot güvenliği için Spring Boot Eğitimi içeriğine de göz atabilirsiniz.


Primary keyword: Spring Security JWT ile kimlik doğrulama ve yetkilendirme

Authentication vs Authorization: iki farklı problem

Authentication, kullanıcının “kim olduğunu” kanıtlama sürecidir: kullanıcı adı/şifre, SSO, OTP veya bir token ile doğrulama. Authorization ise doğrulanan kimliğin “ne yapabildiğini” belirler: rol, izin, scope ve kaynak bazlı kurallar.

JWT, çoğunlukla authentication sonucunda verilen bir bearer token olarak kullanılır; ancak token’ın içindeki claim’ler üzerinden authorization kararları da üretilebilir. Buradaki kritik nokta: authorization kararlarını sadece token içinden okumak yerine, servisler arası tutarlılığı bozmayacak şekilde kurgulamak.

Stateless yaklaşımın bedeli ve getirisi

Stateless API’lerde sunucu tarafında oturum tutulmaz; her istek kendini kanıtlar. Bu, yatayda ölçeklenmeyi kolaylaştırır ama token ömrü, iptal (revocation), anahtar yönetimi ve saat kayması gibi konuları daha önemli hale getirir.

  • Token ömrünü kısa tutup refresh token ile dengelemek
  • JWK/anahtar rotasyonu ile güvenli imzalama
  • Clock skew toleransı ve tutarlı zaman kaynağı
  • Claim doğrulama: issuer, audience, expiry, not-before
Backend servisinde istek akışının token doğrulama ve rol kararlarına bağlandığı mimari düzen

Spring Security’de JWT Resource Server yaklaşımı

Neden “kendi filtremini yazarım” yaklaşımı riskli?

JWT doğrulama, yalnızca imza kontrolü değildir. Doğru algoritma seçimi, header manipülasyonu, claim doğrulaması, anahtar seçimi (kid), hata yönetimi ve performans gibi konular bir araya gelince “minimal filtre” çözümleri üretimde sürpriz çıkarır. Spring’in OAuth2 Resource Server modülü, JWT doğrulama zincirini sağlam bir şekilde kurmanıza yardımcı olur.

SecurityFilterChain ile temel konfigürasyon

Aşağıdaki örnek, JWT doğrulayan bir Resource Server yaklaşımını gösterir. Yetkilendirme kuralları endpoint bazında tanımlanır ve API durumsuz hale getirilir.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
      .csrf(csrf -> csrf.disable())
      .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
      .authorizeHttpRequests(auth -> auth
        .requestMatchers("/actuator/health").permitAll()
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
        .anyRequest().authenticated()
      )
      .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));

    return http.build();
  }
}

Burada önemli detay: hasRole ile kontrol yapıyorsanız, Spring varsayılan olarak role değerini ROLE_ prefiksiyle eşleştirir. Token claim’leriniz “ADMIN” ise “ROLE_ADMIN” dönüşümü doğru yapılmazsa 403 hataları kaçınılmaz olur.


Claim’leri Authority’ye dönüştürme: rol/scope haritalama

En sık hata: claim adı ve formatı varsaymak

Kimlik sağlayıcınız “roles”, “role”, “authorities”, “scope” veya “scp” gibi farklı claim isimleri kullanabilir. Ayrıca claim bazen string, bazen dizi, bazen de boşlukla ayrılmış tek bir metin olur. Bu yüzden mapping katmanını açıkça tanımlamak güvenilir sonuç verir.

JwtAuthenticationConverter ile özelleştirme

Aşağıdaki örnek, “roles” claim’ini authority listesine çevirir ve ROLE_ prefiksini ekler. Böylece hasRole/hasAuthority kuralları tutarlı çalışır.

import java.util.Collection;
import java.util.stream.Collectors;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;

@Component
public class RolesClaimJwtConverter implements Converter<Jwt, AbstractAuthenticationToken> {

  @Override
  public AbstractAuthenticationToken convert(Jwt jwt) {
    Collection<String> roles = jwt.getClaimAsStringList("roles");
    Collection<GrantedAuthority> authorities = roles == null ? java.util.List.of()
      : roles.stream()
          .map(r -> r.startsWith("ROLE_") ? r : "ROLE_" + r)
          .map(SimpleGrantedAuthority::new)
          .collect(Collectors.toList());

    return new JwtAuthenticationToken(jwt, authorities, jwt.getSubject());
  }
}

Bu yaklaşımın faydası, authorization kurallarının token üreticisine bağımlı “tahminlerle” değil, açık bir dönüştürme mantığıyla çalışmasıdır. Üretimde claim formatı değiştiğinde yalnızca converter katmanı güncellenir.

Role ve scope claimlerinin yetki listesine dönüştürülerek endpoint kurallarına bağlanmasını anlatan şema

Token doğrulama derinliği: issuer, audience, expiry ve anahtar yönetimi

Sadece imzayı doğrulamak yetmez

İmza doğrulaması, token’ın bir anahtarla üretildiğini kanıtlar; fakat token’ın kime ve hangi amaçla verildiğini tek başına garanti etmez. Bu yüzden issuer (iss) ve audience (aud) kontrolleri, özellikle çoklu istemci ve çoklu servis mimarisinde kritik hale gelir.

JWK ve key rotation: “kid” ile doğru anahtar seçimi

Modern sağlayıcılar JWK endpoint’i üzerinden anahtarları yayınlar ve düzenli rotasyon yapar. Resource Server bu anahtarları cache’leyerek doğrulama yapar. Buradaki yaygın tuzaklar:

  • JWK cache süresini aşırı uzun tutarak rotasyon sonrası doğrulama hatalarına yol açmak
  • Tek anahtara bağımlı kalıp acil anahtar değişiminde servis kesintisi yaşamak
  • Algoritma doğrulamasını gevşek bırakmak (ör. beklenmeyen alg kabul etmek)

Doğru kurguda, hem beklenen issuer/audience sabitlenir hem de algoritma ve anahtar seçimi netleşir. Ayrıca “token süresi doldu” ve “imza doğrulanamadı” gibi hata tipleri gözlemlenebilir olmalıdır; aksi halde hatayı kullanıcıya yanlış mesaj olarak yansıtma riski doğar.


CSRF, CORS ve “çalışıyor ama güvenli değil” senaryoları

CSRF ne zaman kapatılmalı, ne zaman düşünülmeli?

Stateless JWT ile korunan saf REST API’lerde, cookie tabanlı oturum kullanılmıyorsa CSRF çoğu senaryoda devre dışı bırakılır. Ancak aynı domain altında hem web oturumu hem API çağrıları varsa, “CSRF kapalı” kararı beklenmedik saldırı yüzeyi açabilir. Burada ana belirleyici, kimlik bilgisinin taşıma yöntemi (cookie mi, Authorization header mı) ve tarayıcı davranışlarıdır.

CORS’u “permitAll” sanmak

CORS bir yetkilendirme mekanizması değildir; tarayıcının çapraz origin isteklerine dair bir politikadır. Sunucu CORS başlıklarını doğru set etmiyorsa, istemci tarafında “Network error” görürsünüz; ama bu durum endpoint’in sunucu tarafında güvenli olduğu anlamına gelmez. Güvenlik kararları her zaman Spring Security kurallarıyla verilmelidir.

Üretimde sık görülen bir hata: CORS’u sadece development origin’ine göre ayarlayıp staging/production’da unutmak. Sonuç olarak kullanıcılar login olur ama API çağrıları tarayıcı tarafından bloklanır; ekipler sorunu JWT’de aramaya başlar.


Method Security ve kaynak bazlı yetkilendirme

Endpoint bazlı kurallar yetmezse

Endpoint bazlı authorization, çoğu uygulamada başlangıç için yeterlidir. Ancak “kendi kaydını görebilir”, “aynı tenant içindeki kaynaklara erişebilir” gibi kaynak bazlı ihtiyaçlar ortaya çıktığında, method security güçlü bir tamamlayıcıdır.

@PreAuthorize ile okunabilir kurallar

Örneğin kullanıcı, sadece kendi hesabına ait kaydı okuyabilsin. Token’dan gelen subject (sub) ya da bir kullanıcı id claim’i ile parametre eşleştirilir. Bu yaklaşım, controller seviyesinde if bloklarıyla yetki yazmak yerine, güvenlik politikasını deklaratif hale getirir.

Önemli tuzak: Method security ifadeleriyle erişilen bilgilerin null olma ihtimali ve exception yönetimi göz ardı edilirse, 403 yerine 500 görmek mümkündür. Bu yüzden güvenlik ifadeleri “mutlu yol” varsayımıyla değil, güvenli fall-back davranışlarıyla tasarlanmalıdır.

Servis katmanında method security kurallarıyla kullanıcı kimliği ve kaynak sahipliğinin eşleştirilmesi yaklaşımı

Yaygın tuzaklar ve pratik kontrol listesi

403/401 ayrımını doğru yapmak

401, kimliğin doğrulanamadığını (token yok, hatalı, süresi dolmuş) anlatır. 403 ise kimlik doğrulandı ama yetki yok demektir. Bu ayrım, istemci uygulamalarında doğru davranışı (yeniden giriş mi, yetki mesajı mı) tetikler. Hata yönetiminde bu ayrımı korumak, kullanıcı deneyimini ve gözlemlenebilirliği iyileştirir.

Token boyutu, loglar ve veri sızıntısı

JWT’ye gereğinden fazla claim koymak token’ı şişirir, ağ maliyetini artırır ve loglarda hassas verinin sızmasına sebep olabilir. Token içeriği base64 ile kodlansa da şifrelenmiş değildir; hassas veriler claim olarak taşınmamalıdır. Bu noktada minimum gerekli claim yaklaşımı güvenli bir çizgidir.

Kısa bir kontrol listesi

  1. Issuer (iss) ve audience (aud) doğrulaması net mi?
  2. Algoritma beklenen değerle sınırlandırıldı mı?
  3. Role/scope claim mapping açıkça tanımlandı mı?
  4. Token ömrü, refresh stratejisi ve iptal senaryosu düşünülmüş mü?
  5. CORS ayarları ortam bazında yönetiliyor mu?
  6. 401/403 ayrımı ve hata mesajları tutarlı mı?

Sonuç olarak, Spring Security ile JWT kurgusu kurarken en iyi yaklaşım “kolay çalıştırma” yerine doğru doğrulama, tutarlı yetkilendirme ve gözlemlenebilir hata yönetimi üçlüsünü birlikte ele almaktır. Bu sayede hem saldırı yüzeyi daralır hem de ekipler operasyonel sorunları daha hızlı teşhis eder.

 VERİ AKADEMİ