Pratik Java Uygulamaları

Spring Security ile adım adım JWT Tabanlı Kimlik Doğrulama ve Yetkilendirme Süreçleri

ierdoganierdogan11 Aralık 2025
Spring Security ile adım adım JWT Tabanlı Kimlik Doğrulama ve Yetkilendirme SüreçleriPratik Java Uygulamaları • 11 Aralık 2025Spring Security ile adım adım JWT TabanlıKimlik Doğrulama ve Yetkilendirme SüreçleriPratik Java Uygulamaları • 11 Aralık 2025

Bilgisayar çağında , verinin ve bilginin artışı, bilginin korunmasını sağlama konusunda kritik bir gereksinim ortaya çıkarmıştır. Araştırmalar, dijital çağda üretilen veri ve bilgi miktarının, insanlık tarihi boyunca üretilen toplam veri miktarını aştığını göstermektedir.

1*EYKbL8aMQJLJKVO2UIVFOQ.jpeg

-Yıllara göre , veri hacmindeki dağılım-

Web’in gelişimiyle birlikte, statik tek sayfalık web sitelerinin yerini dinamik web sayfaları almıştır. Bu dönüşümün bir sonucu olarak, bilgi kaynaklarına erişimin güvenli hale getirilmesi ihtiyacı doğmuş, erişimlerimizin belirli güvenlik çerçeveleri aracılığıyla kontrol altına alınması sağlanmıştır. Bu güvenlik çerçeveleri, bilgi kaynaklarını yetkisiz kullanıcılar veya sistemlerden korumak amacıyla oluşturulmuştur.

Bu makalede, Spring projelerinde güvenliğin standardı olan Spring Security kullanarak servislerimize ve bilgi kaynaklarımıza erişimi nasıl izleyip düzenleyebileceğimizi inceleyeceğiz. Ayrıca, bu izleme mekanizmalarının nasıl etkili bir şekilde yapılandırılabileceğini de basit bir örnek üzerinden ele alacak , geliştirilmiş olan bu yöntemleri uygulayarak servislerimizi yetkisiz erişimlerden koruyacağız.

Uygulamamızı geliştirmeye başlamadan önce;

Bir Web Sitesinin Temel Güvenlik Gereksinimleri Nelerdir? sorusuna cevap verelim.

Yapılandırmalara geçmeden önce, bir web sitesinin güvenlik gereksinimlerini nelerin oluşturduğunu inceleyelim.
Bir web sitesinin temel güvenlik ihtiyaçları aşağıdaki bileşenlerden oluşur:

  1. Authentication

  2. Authorization

  3. Data Encryption

  4. Logging

  5. Firewall, IDS, and IPS Systems

  6. DDoS Protection

  7. Software Updates

  8. Database Security

  9. Error Management

  10. Backup Solutions

  11. Access Control Lists (ACLs)

  12. Security Measures Against Attacks like XSS, CSRF, etc.

Güvenlik ihtiyaçlarımızı karşılamak için, bu makalede kimlik doğrulama (authentication) ve yetkilendirme (authorization) süreçlerinin Spring Security kullanılarak nasıl yapıldığını inceleceğiz.

1- Spring Boot’ta Kimlik Doğrulama Süreçleri / Authentication Processes in Spring Boot

1*Fi3d-6buKECWDGMVn3ynNQ.jpeg

Genel olarak, kimlik doğrulama yaşam döngüsü yukarıda belirtilen adımları içermektedir. Ancak, kullanılan bileşenler , uyguladığınız senaryoya bağlı olarak değişebilecektir.

1-HttpRequest

Kimlik doğrulama süreci, kullanıcının uygulamada korunan bir kaynağa erişmek için bir HTTP isteği (HttpRequest) göndermesiyle başlar. Bu genellikle bir giriş formu (kullanıcı adı ve parola gönderimi) ya da bir API isteği aracılığıyla gerçekleşir.

2-Authentication Filter (Kimlik Doğrulama Filtresi)

İlk olarak kullanıcının isteği bir Authentication Filter (Kimlik Doğrulama Filtresi) üzerinden geçer.

örneğin: UsernamePasswordAuthenticationFilter, kullanıcının girdiği kullanıcı adı ve parolayı yakalar.

Token tabanlı bir sistemde ise bu filtre, bir JWT (JSON Web Token) token veya başka bir token türünü doğrular.

Ardından, kullanıcının giriş bilgilerini içeren bir UsernamePasswordAuthenticationToken nesnesi oluşturur.

3-Authentication Token (Kimlik Doğrulama token'ı)

Bu aşamada, filtre tarafından oluşturulan kimlik doğrulama token'ı (örneğin,UsernamePasswordAuthenticationToken ) Spring Security’nin Authentication Manager bileşenine iletilir. Bu token, kullanıcı adı ve parola gibi kimlik bilgilerini içerir.

4-Authentication Manager (Kimlik Doğrulama Yöneticisi)

Merkezi yönlendirici olarak görev yapan bu bileşen, kimlik doğrulama token'ını doğrulamak için uygun Authentication Provider’ı seçer.
Örneğin, bir uygulama hem DaoAuthenticationProvider hem de LdapAuthenticationProvider kullanıyorsa, Authentication Manager, token için hangi sağlayıcının kullanılacağını belirler.

5 - Authentication Provider (Kimlik Doğrulama Sağlayıcısı)

Kimlik doğrulama süreci bu aşamada gerçekleştirilir: Seçilen Authentication Provider, gerçek kimlik doğrulama işlemini yapar.
Birden fazla sağlayıcı yapılandırılabilir.

Örneğin:

DaoAuthenticationProvider: Kullanıcı kimlik bilgilerini bir veritabanı (örneğin JDBC veya JPA kullanarak) üzerinden doğrular.

LdapAuthenticationProvider: Kullanıcıları bir LDAP (Lightweight Directory Access Protocol) sunucusu aracılığıyla kimlik doğrulamadan geçirir.

CasAuthenticationProvider: Kimlik doğrulamayı CAS (Central Authentication Server – Merkezi Kimlik Doğrulama Sunucusu) protokolüyle entegre eder.

6-UserDetailsService (Kullanıcı Detay Servisi)

Eğer, DaoAuthenticationProvider kullanılıyorsa, kullanıcı bilgilerini almak için UserDetailsService çağrılır:

MemoryUserDetailsService: Kullanıcıların bellekte (memory) saklandığı durumlarda kullanılır.

CustomUserDetailsService: Kullanıcı bilgilerini bir veritabanı veya başka bir veri kaynağından çeker.

Kullanıcı bilgileri başarıyla alındıktan sonra bir UserDetails nesnesi döndürülür.

7-UserDetails (Kullanıcı Detayları)

UserDetails nesnesi aşağıdaki bilgileri içerir:

Kullanıcı adı (username)

Parola (password)

Kullanıcıya atanmış roller (örneğin: ROLE_USER, ROLE_ADMIN)

8 - Authentication Provider (devamı)
Kullanıcı bilgileri başarıyla doğrulandıktan sonra, Authentication Provider, kimlik doğrulama sonucunu Authentication Manager’a geri döndürür.

9 - Provider Manager
Elde edilen sonuç Provider Manager tarafından değerlendirilir:

Eğer kimlik doğrulama başarılıysa, bir Authentication nesnesi oluşturulur.

Eğer kimlik doğrulama başarısız olursa, BadCredentialsException gibi bir istisna fırlatılır.

Uygulamanın tüm yaşam döngüsü boyunca, kullanıcı kimlik bilgileri SecurityContext içerisinde saklanır.
Bu bağlam genellikle SecurityContextHolder üzerinden erişilerek yönetilir.

Genel İş Akışı:

  1. Kullanıcı bir istek (request) gönderir.

  2. Authentication Filter, bir kimlik doğrulama token'ı oluşturur ve bunu Authentication Manager’a iletir.

  3. Authentication Manager, uygun Authentication Provider’ı seçer.

  4. Authentication Provider, UserDetailsService aracılığıyla kullanıcıyı doğrular.

  5. Kimlik doğrulama başarılı olduktan sonra, kullanıcı bilgileri Security Context içinde saklanır.

Bu akış, genellikle Spring Boot uygulamalarında spring-boot-starter-security bağımlılığı dahil edilerek uygulanır.
Özelleştirme gerektiğinde, UserDetailsService, AuthenticationProvider veya AuthenticationFilter gibi bileşenler (yeniden tanımlanarak) özelleştirilebilir.

Spring Security’de Kimlik Doğrulama Mekanizmaları

Spring Security, zengin ve kapsamlı bir kimlik doğrulama mekanizması seti sunar.
2023 yılı itibarıyla, Spring Security tarafından desteklenen başlıca kimlik doğrulama mekanizmaları şunlardır:

Genel Kimlik Doğrulama Mekanizmaları:

  • Username and Password (Kullanıcı Adı ve Parola)

  • OAuth 2.0 Login

  • SAML 2.0 Login

  • Central Authentication Server (CAS) (Merkezi Kimlik Doğrulama Sunucusu)

  • Remember Me (Beni Hatırla )

  • JAAS (Java Authentication and Authorization Service) Authentication

  • OpenID

  • Pre-Authentication Scenarios (Ön Kimlik Doğrulama Senaryoları)

  • X509 Authentication (X.509 Sertifika Tabanlı Kimlik Doğrulama)

Username and Password-Based Authentication Methods (Kullanıcı Adı ve Parola Tabanlı Kimlik Doğrulama Yöntemleri)

Form Login (Form Girişi):
Bu yöntem, kullanıcıların kullanıcı adı ve parola bilgilerini girebileceği özel bir arayüz sunar. Kimlik doğrulama işlemi, bu kullanıcı dostu giriş formu aracılığıyla gerçekleştirilir.

Basic Authentication (Temel Kimlik Doğrulama):
HTTP Basic Authentication olarak da bilinen bu yöntemde, kullanıcı adı ve parola Base64 formatında kodlanarak HTTP isteğiyle birlikte gönderilir. Basitliği nedeniyle genellikle API entegrasyonlarında kullanılır.

Digest Authentication (Özet Kimlik Doğrulama):
Bu yöntemde, HTTP Digest Authentication, kullanıcı adı, parola ve sunucu tarafından sağlanan ek bilgileri kullanarak bir hash (özet) oluşturur. Kimlik doğrulama bu hash üzerinden yapılır ve bu sayede Basic Authentication’a göre daha güvenlidir.

JDBC Authentication (Veritabanı Tabanlı Kimlik Doğrulama):
Kullanıcı kimlik bilgilerinin veritabanında saklandığı senaryolar için uygundur. Kimlik doğrulama sırasında sistem, veritabanıyla etkileşime girerek kullanıcının bilgilerini doğrular.

LDAP Authentication (LDAP Tabanlı Kimlik Doğrulama):
Lightweight Directory Access Protocol (LDAP) temeline dayanan bu yöntem, kurumlarda kullanıcı ve yetkilendirme bilgilerinin merkezi olarak saklandığı LDAP sunucuları ile entegre çalışır. Kimlik doğrulama doğrudan LDAP sunucusu üzerinden gerçekleştirilir.

Makalemize , Kullanıcı adı ve parola tabanlı , kimlik doğrulamaya dayalı bir Spring Boot uygulama örneği ile devam edeceğiz.

Uygulamamızda, JWT tabanlı kimlik doğrulama ve yetkilendirme mekanizmaları kullanılacaktır. Bu uygulamayla, bir satış tablosunun yönetimsel işlemlerini temsilen oluşturulan api'lere , güvenli bir şekilde erişim sağlamak hedeflenmektedir.

Sisteme kayıt olan ve giriş yapan kullanıcılar, JWT token aracılığıyla ürün tablolarına istek gönderebilir.
Bu sayede, ürün ve satış listelerine erişerek ilgili bilgilere ulaşabilirler. Eğer kimlik bilgileri hatalıysa, kullanıcıya bilgilendirici hata mesajları gösterilir. Ayrıca, token süresi dolmuşsa ilgili tablolara erişim engellenir ve böylece sistemin güvenliği sağlanır.

Mutfaktan bir ses : Projemiz, Spring Boot framework'u üzerinde geliştirilmiştir. API dokümantasyonu, Swagger kullanılarak oluşturulmuştur; isteklerin testleri ise Postman aracılığıyla gerçekleştirilmiştir.
Veritabanı olarak MySQL kullanılmış olup, veritabanı modülleri DBeaver ile görselleştirilmiştir.

Uygulama İş Akışı:
Kullanıcı Kaydı → Giriş → JWT ile İşlemler

Kullanıcı Kaydı (User Registration)

Sisteme kullanıcı eklemek için bir API endpoint’i (/register) oluşturulur.

Kullanıcıdan kullanıcı adı ve parola gibi bilgiler alınır.

Parola, BCryptPasswordEncoder kullanılarak şifrelenir ve güvenli bir şekilde veritabanında saklanır

Kayıtlı Kullanıcıyla Giriş (Login with a Registered User)

Kullanıcı, giriş esnasında kullanıcı adı ve parolasını gönderir.

Giriş başarılı olursa, kullanıcıya bir JWT token verilir.

Bu token sayesinde kullanıcı, korunan endpoint’lere erişim sağlayabilir.

JWT ile İşlemler (Operations with JWT)

Kullanıcı, yaptığı isteklerde Authorization başlığına JWT token’ını ekler.

Token doğrulandıktan ve kullanıcının kimliği onaylandıktan sonra, endpoint’e erişim izni verilir.

Uygulamamızı katmanlı mimari (layered architecture) temelinde tanımlamak, yapısal bütünlüğü ve anlamsal açıklığı güçlendirerek sistemi daha anlaşılır hale getirecektir.

1-Entities (User , Product , Sale)

2-Repository(UserRepository,ProductRepository,SaleRepository)

3-Service(ProductService,UserService)

4-Controller(AuthController, ProductController)

5-Configuration (SecurityConfig,JwtAuthenticationFilter)

6-Domain(LoginRequest,RegisterRequest)

7-Util(JwtTokenUtil)

Uygulamamızın Paket Hiyerarşisi (Package Hierarchy) Aşağıdaki Gibidir:

1*K42wBjHDII0SK8lJn6MaUQ.png

Projemizde Kullanılan Bağımlılıklar (Dependencies) Şu Şekildedir:

<dependencies>
<!-- Spring Boot Starter Dependencies -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Devtools for Development -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- Testing Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<!-- Database Dependency -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<!-- JWT Dependencies -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version> <!-- or latest -->
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version> <!-- or latest -->
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version> <!-- or latest -->
<scope>runtime</scope>
</dependency>

<!-- Lombok for Boilerplate Code -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<!-- Swagger Dependencies-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>

Bağımlılıklarımızın liste hali aşağıdaki gibidir;

spring-boot-starter-data-jpa

spring-boot-starter-security

spring-boot-starter-web

spring-boot-devtools

spring-boot-starter-test

spring-security-test

mysql-connector-j

jjwt-api

jjwt-impl

jjwt-jackson

lombok

springdoc-openapi-starter-webmvc-ui

Uygulamamızın properties dosyasında, veritabanı bağlantı tanımlamaları, JPA yapılandırmaları ve Swagger ayarları yer almaktadır.

spring.application.name=com.timeissecuritytime

spring.datasource.url=jdbc:mysql://localhost:3307/springsecurityexamples?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=test1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.api-docs.path=/v3/api-docs

Yukarıda paylaştığım , Katmanlı mimari modeline göre ilerleyecek olursak, öncelikle entity (varlık) sınıflarını ve bunlara karşılık gelen SQL kodlarını paylaşacağım.

User (Entity)

package com.timeissecuritytime.entity;
import lombok.*;
import jakarta.persistence.*;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
}

Entity'mize karşılık db'imizde kullanacağımız sql kodu ise aşağıdaki gibidir;

CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf16;

User tablosu, sisteme kayıtlı kullanıcıların kullanıcı adı ve parola bilgilerini saklamak üzere tasarlanmıştır.
Bu tablo, uygulama içinde kullanılan DTO (Data Transfer Object) ve entity yapılarıyla doğrudan ilişkilidir.

Veritabanı tablosu ile DTO’lar arasındaki bu ilişki, uygulama katmanları arasında kesintisiz veri akışı sağlamaktadır; böylece veritabanı kayıtlarının, servis ve controller katmanlarında kullanılan nesnelere etkin bir şekilde eşlenmesi (mapping) mümkün olur.

Product (Entity)

package com.timeissecuritytime.entity;

import lombok.Data;
import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;

@Entity
@Data
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private String productDescription;
private Double price;
private Integer stockQuantity;
}

Product Entity'mize karşılık db'imizde kullanacağımız sql kodu ise aşağıdaki gibidir;

CREATE TABLE `product` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`product_name` VARCHAR(255) NOT NULL,
`product_description` TEXT,
`price` DOUBLE NOT NULL,
`stock_quantity` INT NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


Sale (Entity)

package com.timeissecuritytime.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.Data;

@Entity
@Data
public class Sale {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
private Integer saleQuantity;
private java.util.Date saleDate;
}

Sale Entity'mize karşılık db'imizde kullanacağımız sql kodu ise aşağıdaki gibidir;

CREATE TABLE `sale` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`product_id` BIGINT NOT NULL,
`sale_quantity` INT NOT NULL,
`sale_date` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `product_id` (`product_id`),
CONSTRAINT `sales_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

Product ve Sale entity’leri ve db objeleri makalemizin ana konusu değildir. Yalnızca giriş yapmış kullanıcıların erişebildiği bazı endpoint’leri gösterebilmek amacıyla bu entity’ler oluşturulmuştur.

Bu örnekle, JWT kullanımını pratik bir şekilde göstermek ve oluşturulan servisler aracılığıyla erişim kontrol mekanizmalarını vurgulamak hedeflendi.

Bu doğrultuda , uygulamamız için ProductRepository, UserRepository ve SaleRepository oluşturulmuştur.

package com.timeissecuritytime.repository;


import com.timeissecuritytime.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}
package com.timeissecuritytime.repository;
import com.timeissecuritytime.entity.Sale;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface SaleRepository extends JpaRepository<Sale, Long> {
List<Sale> findByProductId(Long productId);
}
package com.timeissecuritytime.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.timeissecuritytime.entity.User;

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}

Product ve Sale tabloları için örnek veri kümelerini (data instance) aşağıdaki SQL scriptleri çalıştırarak oluşturabilirsiniz:

INSERT INTO `product` (`product_name`, `product_description`, `price`, `stock_quantity`)
VALUES
('Laptop', 'High-performance gaming laptop', 1500.00, 10),
('Smartphone', 'Latest model smartphone with advanced features', 999.99, 20);

INSERT INTO `sale` (`product_id`, `sale_quantity`, `sale_date`)
VALUES
(1, 2, '2025-01-28 12:00:00'),
(2, 1, '2025-01-28 13:30:00');

Şimdi, projemizin service (servis) dosyalarının tanımlanmasına geçiyoruz.

UserService

package com.timeissecuritytime.service;

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.timeissecuritytime.entity.User;
import com.timeissecuritytime.repository.UserRepository;
import com.timeissecuritytime.Constants;

@RequiredArgsConstructor
@Service
public class UserService implements UserDetailsService {

private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;

public User registerUser(String username, String password) {
User user = new User();
user.setUsername(username);
user.setPassword(bCryptPasswordEncoder.encode(password));
return userRepository.save(user);
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(Constants.USER_NOT_FOUND + username));

return org.springframework.security.core.userdetails.User
.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles("USER")
.build();
}

public User authenticateUser(String username, String password) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(Constants.USER_NOT_FOUND + username));

if (!bCryptPasswordEncoder.matches(password, user.getPassword())) {
throw new IllegalArgumentException(Constants.INVALID_PASSWORD);
}
return user;
}
}

UserService Sınıfındaki Temel Metotlarımız ve Teknik İşlevleri aşağıdaki gibidir;

1-registerUser(String username, String password)

Bu metot, sisteme yeni bir kullanıcı kaydetmek için kullanılır.

Teknik akış:

Gelen kullanıcı adı ve parola parametrelerini alır.

Bir User nesnesi oluşturulur ve parametrelerden gelen veriler nesneye atanır.

Parola, BCryptPasswordEncoder kullanılarak şifrelenir (hashlenir) ve User nesnesine set edilir.

userRepository.save(user) metodu aracılığıyla kullanıcı nesnesi veritabanına kaydedilir.

Özetle ,kullanıcının parolasının güvenli şekilde saklanmasını sağlamak ve sisteme yeni kullanıcı eklemek amacıyla oluşturulmuştur.

2-loadUserByUsername(String username)

(Spring Security’nin UserDetailsService Uygulaması / Implementation of Spring Security’s UserDetailsService)

Kullanıcı bilgilerini toplamak ve Spring Security’nin kimlik doğrulama mekanizmalarıyla uyumlu bir UserDetails nesnesi döndürmek amacıyla geliştirilmiştir.Spring Security’nin kimlik doğrulama süreci sırasında otomatik olarak çağrılır.

Teknik akış:

Kullanıcı, userRepository.findByUsername(username) metodu kullanılarak veritabanında aranır.

Eğer kullanıcı bulunamazsa, UsernameNotFoundException fırlatılır.

Kullanıcı bulunursa, Spring Security’nin User sınıfı kullanılarak bir UserDetails nesnesi oluşturulur.
Bu nesne, kullanıcı adı, parola ve varsayılan bir rol (USER) bilgilerini içerir.

3-authenticateUser(String username, String password)

Kullanıcının giriş bilgilerini(login credentials) doğrulamak için geliştirilmiştir. Kullanıcı bilgilerinin doğruluğunu manuel olarak kontrol etmek için çağrılır.

Teknik akış:

Kullanıcı, userRepository.findByUsername(username) metodu kullanılarak veritabanında aranır.
Eğer kullanıcı bulunamazsa, UsernameNotFoundException fırlatılır.

Girilen parola, veritabanında saklanan hashlenmiş parola ile BCryptPasswordEncoder.matches metodu kullanılarak karşılaştırılır.

Parolalar eşleşmiyorsa, IllegalArgumentException fırlatılır.

Doğrulama başarılı olursa, ilgili User nesnesi döndürülür.

registerUser: Kullanıcıyı kaydetmemize yardımcı olan methoddur. Kullanıcı parolasını hashleyerek , güvenli bir şekilde saklamamıza olanak tanır.
loadUserByUsername: Spring Security’nin kimlik doğrulama süreci için bir UserDetails nesnesi bizlere sağlar.
authenticateUser: Kullanıcı kimlik bilgilerini manuel olarak doğrular; özel kimlik doğrulama mantığının gerektiği durumlarda genellikle tercih edilir.

Bu metotlar, uygulamamızda kullanıcı kimlik doğrulama ve yetkilendirme altyapısı oluşturmak amacıyla tasarlanmıştır.

ProductService

package com.timeissecuritytime.service;

import com.timeissecuritytime.entity.Product;
import com.timeissecuritytime.entity.Sale;
import com.timeissecuritytime.repository.ProductRepository;
import com.timeissecuritytime.repository.SaleRepository;
import com.timeissecuritytime.Constants;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@RequiredArgsConstructor
public class ProductService {

private final ProductRepository productRepository;
private final SaleRepository saleRepository;

@PreAuthorize("hasRole('USER')")
@Transactional(readOnly = true)
public List<Product> findAllProducts() {
return productRepository.findAll();
}

@Transactional(readOnly = true)
public Product findProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException(Constants.PRODUCT_NOT_FOUND + id));
}

@Transactional
@PreAuthorize("hasRole('ADMIN')")
public Product saveProduct(Product product) {
return productRepository.save(product);
}

@Transactional(readOnly = true)
@PreAuthorize("hasRole('USER')")
public List<Sale> findSalesByProductId(Long productId) {
return saleRepository.findByProductId(productId);
}
}

Bu servis dosyamızda ürün ve satış verileri üzerinde işlem yapmak için kullanacağımız temel servis metotları bulunmaktadır. Bu api'ler , makalemizin ana odak noktasını oluşturmamaktadır.

Teknik olarak methodlarımız:

findAllProducts(): Tüm ürünleri listeler ve yalnızca USER rolüne sahip kullanıcılar tarafından erişilebilir.

findProductById(Long id): Verilen ID değerine göre bir ürünü bulur. Ürün bulunamazsa hata fırlatır.

saveProduct(Product product): Yeni bir ürünü kaydeder ve yalnızca ADMIN rolüne sahip kullanıcılar tarafından erişilebilir.

findSalesByProductId(Long productId): Belirli bir ürüne ait satışları listeler ve yalnızca USER rolündeki kullanıcıların erişimine açıktır.

Şimdi, projemizin Controller dosyalarının tanımlanmasına geçiyoruz.

AuthController

package com.timeissecuritytime.controller;

import lombok.RequiredArgsConstructor;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.timeissecuritytime.util.JwtTokenUtil;
import com.timeissecuritytime.domain.LoginRequest;
import com.timeissecuritytime.domain.RegisterRequest;
import com.timeissecuritytime.entity.User;
import com.timeissecuritytime.service.UserService;
import com.timeissecuritytime.Constants;

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;
private final JwtTokenUtil jwtTokenUtil;

@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
userService.registerUser(registerRequest.getUsername(), registerRequest.getPassword());
return ResponseEntity.ok(Constants.REGISTRATION_SUCCESS);
}

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
User user = userService.authenticateUser(loginRequest.getUsername(), loginRequest.getPassword());
String token = jwtTokenUtil.generateToken(user.getUsername());
return ResponseEntity.ok(String.format(Constants.TOKEN_RESPONSE, token));
}
}

AuthController sınıfı, kullanıcının kayıt ve giriş işlemleri sırasında kullanılmak üzere tasarlanmıştır.

/register (POST): Kullanıcıdan gelen kayıt isteğini alır ve UserService aracılığıyla yeni bir kullanıcı oluşturur. İşlem başarıyla tamamlandığında bir başarı mesajı döndürür.

/login (POST): Kullanıcının giriş kimlik bilgilerini doğrular. Kimlik doğrulama başarılı olursa, JwtTokenUtil kullanılarak bir JWT token üretilir ve kullanıcıya iletilir.

Bu controller, uygulamanın kimlik doğrulama süreçlerini yönetmek için oluşturulmuş temel bir controller'dır.

ProductController

package com.timeissecuritytime.controller;

import com.timeissecuritytime.entity.Product;
import com.timeissecuritytime.entity.Sale;
import com.timeissecuritytime.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {

private final ProductService productService;

@GetMapping("/all")
public ResponseEntity<List<Product>> getAllProducts() {
return ResponseEntity.ok(productService.findAllProducts());
}

@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
return ResponseEntity.ok(productService.findProductById(id));
}

@PostMapping("/add")
public ResponseEntity<Product> addProduct(@RequestBody Product product) {
return ResponseEntity.ok(productService.saveProduct(product));
}

@GetMapping("/sales/{productId}")
public ResponseEntity<List<Sale>> getSalesByProductId(@PathVariable Long productId) {
return ResponseEntity.ok(productService.findSalesByProductId(productId));
}
}

ProductController sınıfı, ürün ve satış işlemlerini yönetmek için oluşturulmuştur.Ürün ve satış verilerine erişim sağlamak üzere tasarlanmıştır.

Teknik olarak methodlarımız:

/all (GET): Tüm ürünleri listeler.

/{id} (GET): Belirtilen ID değerine sahip ürünü döndürür.

/add (POST): Yeni bir ürünü ekler ve kaydeder.

/sales/{productId} (GET): Belirtilen productId değerine ait satışları listeler.

Şimdi, projemizin konfigürasyon (configuration) dosyalarının tanımlanmasına geçiyoruz.

JwtAuthenticationFilter

package com.timeissecuritytime.configuration;

import java.io.IOException;
import java.util.Optional;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.timeissecuritytime.service.UserService;
import com.timeissecuritytime.util.JwtTokenUtil;
import com.timeissecuritytime.Constants;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenUtil jwtTokenUtil;
private final UserService userService;

public JwtAuthenticationFilter(JwtTokenUtil jwtTokenUtil, UserService userService) {
this.jwtTokenUtil = jwtTokenUtil;
this.userService = userService;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
Optional<String> tokenOptional = extractToken(request);
if (tokenOptional.isPresent()) {
String token = tokenOptional.get();
if (jwtTokenUtil.validateToken(token)) {
setAuthentication(token);
} else {
handleInvalidToken(response);
return;
}
}
} catch (Exception e) {
handleFilterException(response, e);
return;
}
filterChain.doFilter(request, response);
}

private Optional<String> extractToken(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(Constants.AUTHORIZATION_HEADER))
.filter(header -> header.startsWith(Constants.BEARER_PREFIX))
.map(header -> header.substring(Constants.BEARER_PREFIX.length()));
}

private void setAuthentication(String token) {
String username = jwtTokenUtil.extractUsername(token);
UserDetails userDetails = userService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}

private void handleInvalidToken(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(Constants.INVALID_TOKEN_MESSAGE);
}

private void handleFilterException(HttpServletResponse response, Exception e) throws IOException {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write(Constants.UNEXPECTED_ERROR_MESSAGE);
}
}

JwtAuthenticationFilter sınıfı, Spring Security içerisinde tanımlanan özel bir filtredir ve JWT (JSON Web Token)’ların doğrulanması ve kimlik doğrulama işlemlerinden sorumludur.

Teknik olarak methodlarımız:

doFilterInternal Metodu:

Gelen isteklerdeki Authorization başlığından JWT token’ı çıkarır.

Token geçerliyse, JwtTokenUtil kullanarak kullanıcı adını çözümler ve kimlik doğrulama işlemini gerçekleştirir.

Token geçersizse veya bir hata oluşursa, uygun HTTP durum kodu (status code) ve hata mesajı ile yanıt döner.

extractToken Metodu:

Authorization başlığındaki Bearer önekini kaldırarak token’ı çıkarır.

setAuthentication Metodu:

Token’dan elde edilen kullanıcı adını kullanarak UserDetails bilgilerini yükler.

Kullanıcının kimlik doğrulama bilgilerini SecurityContext içine kaydeder ve böylece kullanıcının, isteğin tüm yaşam döngüsü boyunca doğrulanmış (authenticated) kalmasını sağlar.

handleInvalidToken ve handleFilterException Metotları:

Geçersiz token veya beklenmeyen hata durumlarında, uygun hata mesajı ve HTTP durum kodu ile yanıt dönmesine yardımcı olur.

Bu sınıf, her HTTP isteği başına bir kez çalışır ve JWT doğrulama sürecini merkezi olarak yönetmektedir.

JwtTokenUtil


package com.timeissecuritytime.configuration;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;

import java.util.Date;
import java.util.Optional;

import javax.crypto.SecretKey;

import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class JwtTokenUtil {

private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private static final long EXPIRATION_TIME = 86400000; // One-day

public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(calculateExpirationDate())
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.compact();
}

public boolean validateToken(String token) {
return parseClaims(token)
.map(this::isTokenValid)
.orElse(false);
}

public String extractUsername(String token) {
return parseClaims(token)
.map(Claims::getSubject)
.orElse(null);
}

private Date calculateExpirationDate() {
return new Date(System.currentTimeMillis() + EXPIRATION_TIME);
}

private Optional<Claims> parseClaims(String token) {
try {
return Optional.of(Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody());
} catch (JwtException e) {
return Optional.empty();
}
}

private boolean isTokenValid(Claims claims) {
Date expirationDate = claims.getExpiration();
return expirationDate != null && !expirationDate.before(new Date());
}
}

JwtTokenUtil sınıfı, JWT işlemlerini (oluşturma, doğrulama ve çözümleme) yönetmek için tasarlanmış bir yardımcı (utility) sınıftır.
Bu sınıfta yer alan metotların teknik işlevlerine dair ayrıntılı açıklamalarımız;

  • generateToken(String username) Metodu:
    setSubject(username): Kullanıcı adını token’ın subject (sub) alanına ekler.
    setIssuedAt(new Date()): Token’ın oluşturulma tarihini belirler.
    setExpiration(calculateExpirationDate()): Token’ın bitiş (expiration) tarihini hesaplayıp ekler.
    signWith(SECRET_KEY, SignatureAlgorithm.HS256): Token’ı HS256 algoritması ve SECRET_KEY ile imzalar.
    compact(): Oluşturulan JWT’yi string olarak döndürür.
    Kullanıcı adı ve geçerlilik süresi bilgilerini içeren imzalı bir JWT oluşturur.

  • validateToken(String token) Metodu:
    parseClaims(token): Token içindeki Claims nesnesini çözümler.
    isTokenValid(claims): Token’ın süresinin geçerli olup olmadığını kontrol eder.
    Token geçersizse Optional.empty() döner.
    Token’ı doğrular ve geçerliyse true, geçersizse false döndürür.

  • extractUsername(String token) Metodu:
    parseClaims(token): Token’daki Claims nesnesini çözümler.
    Claims::getSubject: Token’daki sub (kullanıcı adı) bilgisini çıkarır.

  • ExpirationDate Metodu:
    System.currentTimeMillis() + EXPIRATION_TIME: Mevcut zamana bir gün ekleyerek token’ın bitiş tarihini hesaplar.
    Expiration tarihini Date nesnesi olarak döndürür.

  • parseClaims(String token) Metodu:
    Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token): Token’ı çözümleyerek içindeki Claims nesnesini çıkarır.
    Token geçersizse JwtException fırlatır ve Optional.empty() döner.
    Token içindeki tüm Claims verilerini döndürür.

  • isTokenValid(Claims claims) Metodu:
    claims.getExpiration(): Token’ın bitiş tarihini alır.
    !expirationDate.before(new Date()): Token süresinin dolup dolmadığını kontrol eder.
    Token geçerliyse true, süresi dolmuşsa false döndürür.

SecurityConfig

package com.timeissecuritytime.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter)
throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/auth/**",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/v3/api-docs/swagger-config"
).permitAll()
.anyRequest().authenticated()
)

.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

return http.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

SecurityConfig sınıfı, uygulama için Spring Security yapılandırmasını gerçekleştirir.
SecurityConfig içerisinde, güvenlik kuralları , kimlik doğrulama mekanizmaları ve kriptolama ayarları belirlenir.
Her bir bileşenin teknik açıklamasına ulaşabilirsiniz.

1. filterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) Metodu

Bu metot, uygulamanın temel güvenlik yapılandırmasını tanımlar; hangi endpoint’lerin korunduğunu, hangilerinin herkese açık olduğunu ve güvenlik filtrelerinin nasıl uygulandığını belirler.

csrf(csrf -> csrf.disable()) -> CSRF (Cross-Site Request Forgery) korumasını devre dışı bırakır.

authorizeHttpRequests(auth -> auth…) -> HTTP endpoint’leri için erişim kurallarını yapılandırmak için kullanılır.

requestMatchers(…): Herkese açık, kimlik doğrulaması gerektirmeyen endpoint’leri belirtmek için kullanılır.

/api/auth/ → Kimlik doğrulama süreçlerinde, kullanılan end point'lere erişim için herkese açık hale getirilmiştir.( login, register).
/v3/api-docs/, /swagger-ui/ → Swagger API dokümantasyonu'da benzer şekilde açık bırakılmıştır.

anyRequest().authenticated(): İzin verilen endpoint'ler haricinde kalan, diğer api'ler için authenticated şartını koymak için kullanılmıştır.

addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
Uygulamaya özel olarak oluşturulan JwtAuthenticationFilter bileşenini, Spring Security’nin filtre zincirine dahil etmek için kullanılır.

Ek olarak bu filtre, uygulamaya gelen her HTTP isteğini yakalayarak Authorization başlığındaki JWT token’ı doğrular.
Token geçerliyse, kullanıcının kimlik bilgilerini SecurityContext içerisine yerleştirir ve isteğin doğrulanmış bir kullanıcı adına devam etmesini sağlar.
Bu işlem, Spring Security’nin varsayılan UsernamePasswordAuthenticationFilter’ından önce gerçekleşir, böylece JWT tabanlı kimlik doğrulama mekanizması öncelikli hâle gelir.

sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

Oturum (session) yönetimini yapılandırmak için kullanılır.Oturum oluşturma politikasını STATELESS olarak ayarlar; bu sayede sunucu üzerinde herhangi bir oturum oluşturulmaz veya tutulmaz.
Bu yaklaşım, JWT tabanlı kimlik doğrulama sistemleri gibi durumsuz (stateless) güvenlik mimarilerinde gerekmektedir. Methodumuz , Spring Security’nin gelen istekleri nasıl işleyeceğini tanımlayan, tam olarak yapılandırılmış bir SecurityFilterChain nesnesi oluşturarak döndürür.

2. passwordEncoder() Metodu

Methodumuz, Parolaların hashlenmesi ve doğrulanması için kullanılacak şifreleyiciyi tanımlar.

Parolaların güvenli biçimde saklanması için güçlü bir BCryptPasswordEncoder örneği döndürür.

Düz metin (plaintext) parolalar hiçbir zaman saklanmaz; bunun yerine hashlenmiş olarak kaydedilir.

SecurityConfig Sınıfındaki Temel Kavramların Özeti

  • Stateless Security: Uygulama, kimlik doğrulama için JWT token’ları kullanır; bu nedenle sunucu tarafında herhangi bir oturum durumu (session state) tutulmaz. Bu durum, SessionCreationPolicy.STATELESS konfigurasyonuyla sağlanır.

  • Filter Chain: JwtAuthenticationFilter gibi özel filtreler, Spring Security filtre zincirine entegre edilerek JWT doğrulama işlemlerini yürütür.

  • Endpoint Security: /api/auth/** ve /swagger-ui/** gibi genel endpoint’ler açıkça erişime izinlidir. Bunların dışındaki tüm endpoint’ler, kimlik doğrulama gerektirir.

  • Password Encryption: Parolalar, BCryptPasswordEncoder kullanılarak şifrelenir ve bu sayede veritabanında güvenli biçimde saklanır.

Güvenlik Akışı (Security Flow):

Bir istek uygulamaya ulaştığında SecurityFilterChain tarafından işlenir.

Eğer istek herkese açık bir endpoint ile eşleşiyorsa, kimlik doğrulama yapılmadan kabul edilir.

Diğer tüm endpoint’lerde, JWT doğrulaması yapılır ve yalnızca geçerli token’a sahip kullanıcıların erişimine izin verilir.

Aşağıda, sabitlerin tanımlandığı dosyalarımız, genel hata yönetimi (global error handling) için kullanılan sınıflarımız ve domain nesneleri yer almaktadır.

Domain objelerimiz;

package com.timeissecuritytime.domain;
import lombok.Data;
@Data
public class LoginRequest {
private String username;
private String password;
}
package com.timeissecuritytime.domain;
import lombok.Data;
@Data
public class RegisterRequest {
private String username;
private String password;
}

GlobalExceptionHandler;

package com.timeissecuritytime.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}

Constants

package com.timeissecuritytime;

public class Constants {
public static final String REGISTRATION_SUCCESS = "User successfully registered!";
public static final String TOKEN_RESPONSE = "Token: %s";
public static final String PRODUCT_NOT_FOUND = "Product not found: ";
public static final String USER_NOT_FOUND = "User not found: ";
public static final String INVALID_PASSWORD = "Invalid password!";
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String BEARER_PREFIX = "Bearer ";
public static final String INVALID_TOKEN_MESSAGE = "{\"error\": \"Token has expired or is invalid. Please log in again.\"}";
public static final String UNEXPECTED_ERROR_MESSAGE = "{\"error\": \"An unexpected error occurred.\"}";
}

Şimdi, uygulamamızı test etmek için Postman kullanacağız.

Öncelikle, register (kayıt) işlemiyle yeni bir kullanıcı oluşturacağız.
Ardından, login (giriş) işlemini kullanarak bir JWT token alacağız.
Sonrasında, bu token’ı kullanmadan ürün listesini görüntülemeyi deneyeceğiz ve alacağımız yetkilendirme hatasını gözlemleyeceğiz.
Son olarak, geçerli bir JWT token sağlayarak servislerimize başarılı şekilde erişip ürünleri listeleyebileceğimizi göstereceğiz.

1 - Kullanıcının Kaydı

1*QNYYhri5aYRzcItoIE5__Q.png

2 - Giriş işlemi ve token'ın alınması

1*xuVrPyvMPGMvXe63wtDGDg.png

3 - Token Olmadan Ürünleri Listelemeyi Deneme ve Hata Mesajını Gözlemleme

1*lKpwarkFcfOv5JUh-t_ABQ.png

4 - Geçerli Bir Token ile Servislere Erişim Sağlama

1*f8KnS6r9K3N5m_M2lG8iLQ.png

Token servisimize geçirildikten sonra ürün listesini alabiliyoruz

1*Ezp4U9NfUJ4IJ_2eLmP_-Q.png

Bu makalede, Spring Security ile JWT tabanlı kimlik doğrulama ve yetkilendirme süreçlerini ayrıntılı bir şekilde inceledik. Ayrıca, uygulamamızı katmanlı mimari yapısına göre kurgulayarak güvenlik odaklı bir altyapı oluşturmanın önemini vurguladık.

Uygulamanın her bileşeni arasındaki etkileşimleri açıklarken, bu süreçleri daha anlaşılır kılmak için örnek kodlar kullandık. Bu yapı, yetkisiz erişimleri önleyerek güvenli ve sürdürülebilir bir sistem geliştirmenize yardımcı olacaktır.

----

Makalemizi okuduğunuz için teşekkür eder, faydalı bulduysanız paylaşmanızı rica ederim.

Bu makaleyi beğendiniz mi?

Arkadaşlarınızla paylaşın