Spring Boot, stateless şifrelenmiş JWT’leri (JWE) kullanarak API’lerinizi güvence altına almanızı ve kullanıcı kimliklerini ile rollerini JPA tabanlı bir veritabanında saklamanızı sağlar.
Yazan
Şuayb Şimşek
Spring Boot, güvenlik, mikroservis ve cloud-native mimari konularında pratik teknik notlar paylaşan backend odaklı fullstack geliştirici.
Bu bölümde, Spring Boot uygulamanızı H2/PostgreSQL, JPA, Liquibase değişiklik changelog, veri yüklemeleri ve JWE anahtar özellikleri ile yapılandırmak için gereken tüm uygulama ve veritabanı düzeyindeki yapılandırma dosyalarını tanımlıyoruz:
application.yml
Spring veri kaynağı (datasource), H2 konsolu, JPA/Hibernate, Liquibase changelog pathi ve tüm JWT/JWE anahtar, issuer ve geçerlilik süresi (expiration) ayarlarını içerir.
db/master.xml
Liquibase master changelog dosyası; H2 ve PostgreSQL için DBMS özel özellikler ve alt changelog tanımlarını barındırır.
db/changelog/changelog-user.xmluser_identity, authority ve user_authority_mapping tabloları, indeksler, yabancı anahtarlar (FK) ve ilk <loadData> adımlarını tanımlayan temel şema değişiklik changelogu.
db/data/user.csv
Kullanıcı kayıtları (UUID, kullanıcı adı, bcrypt ile şifrelenmiş parola, e-posta, enabled flagi, zaman damgaları ve oluşturan kullanıcı bilgisi).
db/data/authority.csv
Yetki kayıtları (UUID, ad, açıklama, zaman damgaları ve oluşturan kullanıcı bilgisi).
db/data/user_authority_mapping.csv
Kullanıcılar ile yetkiler arasındaki mappingler (composite birincil anahtar, zaman damgaları ve oluşturan kullanıcı bilgisi).
Bu bölümde, JWE tabanlı kimlik doğrulama için RSA anahtarları, HTTP güvenlik filtreleri ve JPA repository / auditing yapılandırmasını tanımlayan bean'leri ve ayarları oluşturuyoruz:
JwtProperties: JWT düzenleyicisi (issuer), geçerlilik süresi ve imzalama/şifreleme anahtar çiftlerini yapılandırır.
SecurityConfig: DomainUserDetailsService ile entegre olur, kimlik doğrulama yöneticisi (authentication manager), password encoder ve JWE desteği ile stateless güvenlik filtre zincirini yapılandırır.
DatabaseConfig: JPA repository'lerini, auditing ve transaction yönetimini etkinleştirir.
SecurityJwtConfig
SecurityConfig
JwtProperties
DatabaseConfig
🛠️ Adım 4: JPA Entegrasyonu
Bu bölümde, kullanıcıları, rollerleri ve bunların eşlemelerini temsil eden JPA entity’lerini ve kullanıcıları yetkileriyle birlikte yüklemek için Spring Data JPA deposunu tanımlıyoruz.
BaseEntity: Audit alanları (createdAt, createdBy, updatedAt, updatedBy) sağlayan soyut üst sınıf.
Authority: Rol verilerini saklayan authority tablosu entity’si.
User: Kullanıcı kimlik bilgileri ve profili saklayan user_identity tablosu entity’si.
UserAuthorityMapping: Kullanıcıları ve rolleri birbirine bağlayan user_authority_mapping ilişki tablosunun entity’si.
UserAuthorityMappingId: UserAuthorityMapping için composite anahtar sınıfı.
UserRepository: Entity graph kullanarak kullanıcı ve yetkilerini getiren Spring Data JPA repository.
BaseEntity
Authority
User
UserAuthorityMapping
UserAuthorityMappingId
UserRepository
🛠️ Adım 5: Güvenli JWE Token Yardımcı Sınıfları Oluşturun
Bu bölümde, Spring Boot uygulamanızda JSON Web Encryption (JWE) tokenları oluşturmak, şifrelemek ve çözmek için gerekli temel yardımcı sınıfları ve sabitleri tanımlıyoruz. Ayrıca auditing entegrasyonu ve JPA tabanlı UserDetailsService de ekliyoruz:
AuthoritiesConstants: ROLE_ ön ekiyle rol isimlerini merkezileştirir.
CookieBearerTokenResolver: Bearer token’ları yetkilendirme başlıklarından veya HTTP çerezlerinden çözer.
CookieUtils: Erişim token’ları için HTTP-only ve secure çerezler oluşturur.
JweUtil: Nimbus kütüphanesi ile RSA anahtarları kullanarak JWT’leri imzalar (JWS) ve şifreler (JWE).
KeyUtils: PEM formatındaki anahtar çiftinden RSA JWK’leri oluşturur.
SecurityUtils: SecurityContext oturum açan kullanıcının bilgisini sunar.
SpringSecurityAuditorAware: Auditing için oturum açan kullanıcıyı sağlayan AuditorAware implementasyonu.
DomainUserDetailsService: JPA tabanlı UserDetailsService, kullanıcı kimlik bilgilerini ve yetkilerini getirir.
Bu yardımcılar, Spring Security ile durumsuz (stateless) JWE tabanlı bir kimlik doğrulama akışının temelini oluşturur.
AuthoritiesConstants
CookieBearerTokenResolver
CookieUtils
JweUtil
KeyUtils
SecurityUtils
DomainUserDetailsService
SpringSecurityAuditorAware
🧪 Adım 6: Kimlik Doğrulama ve Güvenli Endpointler
Bu bölümde, aşağıdakileri gerçekleştirmek için gerekli REST controller ve DTO’ları tanımlıyoruz:
AuthController: Kullanıcıları doğrular, JWE token’ları oluşturur ve güvenli cookie ayarlar.
HelloController: Kimliği doğrulanmış kullanıcılar ve yalnızca admine özel pathler için güvenli endpointler sunar.
LoginRequestDTO: Login isteği payloadını (kullanıcı adı/parola) modelleyen DTO.
TokenDTO: Token ve geçerlilik süresini içeren kimlik doğrulama yanıtını modelleyen DTO.
Bu bileşenler, login işlemi, token oluşturma, cookie yönetimi ve kaynak korumasını işleyerek stateless(durumsuz) kimlik doğrulama akışını tamamlar.
Bu bölümde, kullanıcı kimlik doğrulamasını, token oluşturmayı ve korunan kaynak erişimini yönetmek için REST controller ve DTO’ları oluşturuyoruz.
AuthController
HelloController
LoginRequestDTO
TokenDTO
▶️ Uygulamayı Çalıştır
BASH
./mvnw spring-boot:run
# or
gradle bootRun
🧪 Endpoint Testi
Bu bölümde Endpoint Testi konusunu netleştirip uygulamada kullanacağınız temel noktaları özetliyoruz.
Admin Akışı
admin olarak giriş yapın ve Set-Cookie başlığından JWE tokeni yakalayın:
Artık Spring Boot JPA ile JWE Kimlik Doğrulaması için üretim odaklı bir Spring Boot temeliniz var. Sonraki adımda ayarları kendi domainine uyarlayıp test ve gözlemlenebilirlik katmanını ekleyerek gerçek trafik altında doğrulayın.
package io.github.susimsek.springbootjweauthjpademo.security;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AuthoritiesConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
}
KOTLINAuthoritiesConstants.kt
package io.github.susimsek.springbootjweauthjpademo.security
object AuthoritiesConstants {
const val ADMIN = "ROLE_ADMIN"
const val USER = "ROLE_USER"
const val ANONYMOUS = "ROLE_ANONYMOUS"
}
package io.github.susimsek.springbootjweauthjpademo.controller;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
import static io.github.susimsek.springbootjweauthjpademo.security.SecurityUtils.AUTHORITIES_KEY;
@RestController
@RequestMapping("/api/hello")
public class HelloController {
@GetMapping
public String helloAll(@AuthenticationPrincipal Jwt jwt) {
String user = jwt.getSubject();
var roles = jwt.getClaimAsStringList(AUTHORITIES_KEY);
return "Hello, " + user + "! Your roles: " + roles;
}
@GetMapping("/admin")
public String helloAdmin(@AuthenticationPrincipal Jwt jwt) {
return "Hello Admin, " + jwt.getSubject() + "!";
}
}
KOTLINHelloController.kt
package io.github.susimsek.springbootjweauthjpademo.controller
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.web.bind.annotation.*
import io.github.susimsek.springbootjweauthjpademo.security.SecurityUtils.AUTHORITIES_KEY
@RestController
@RequestMapping("/api/hello")
class HelloController {
@GetMapping
fun helloAll(@AuthenticationPrincipal jwt: Jwt): String {
val user = jwt.subject
val roles = jwt.getClaimAsStringList(AUTHORITIES_KEY)
return "Hello, \$user! Your roles: \$roles"
}
@GetMapping("/admin")
fun helloAdmin(@AuthenticationPrincipal jwt: Jwt): String {
return "Hello Admin, \${jwt.subject}!"
}
}
JAVALoginRequestDTO.java
package io.github.susimsek.springbootjweauthjpademo.dto;
public record LoginRequestDTO(
String username,
String password
) { }
KOTLINLoginRequestDTO.kt
package io.github.susimsek.springbootjweauthjpademo.dto
data class LoginRequestDTO(
val username: String,
val password: String
)
JAVATokenDTO.java
package io.github.susimsek.springbootjweauthjpademo.dto;
public record TokenDTO(
String accessToken,
String tokenType,
long accessTokenExpiresIn
) {}
KOTLINTokenDTO.kt
package io.github.susimsek.springbootjweauthjpademo.dto
import kotlin.Long
data class TokenDTO(
val accessToken: String,
val tokenType: String,
val accessTokenExpiresIn: Long
)