Spring Security ile Kimlik Doğrulama ve Yetkilendirme
Bu bölümde, e-ticaret uygulaması için Spring Security kullanarak kimlik doğrulama ve yetkilendirme yapılandırmasını detaylı olarak ele alacağız. Spring Security, Java uygulamaları için kapsamlı bir güvenlik çerçevesidir ve kimlik doğrulama, yetkilendirme ve diğer güvenlik özelliklerini sağlar.
Spring Security Nedir?
Spring Security, Spring Framework’ün bir parçası olarak, Java uygulamaları için kapsamlı bir güvenlik çözümü sunar. Spring Security, aşağıdaki özellikleri sağlar:
- Kimlik Doğrulama (Authentication): Kullanıcının kimliğini doğrulama.
- Yetkilendirme (Authorization): Kullanıcının belirli kaynaklara erişim yetkisini kontrol etme.
- Oturum Yönetimi (Session Management): Kullanıcı oturumlarını yönetme.
- CSRF Koruması (Cross-Site Request Forgery Protection): CSRF saldırılarına karşı koruma.
- CORS Yapılandırması (Cross-Origin Resource Sharing Configuration): Farklı kaynaklardan gelen istekleri yönetme.
- Şifreleme (Encryption): Şifreleri güvenli bir şekilde saklama.
Maven Bağımlılıkları
Spring Security’yi kullanmak için, aşağıdaki Maven bağımlılıklarını pom.xml
dosyasına eklememiz gerekir:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
Güvenlik Yapılandırması
Spring Security’yi yapılandırmak için, bir yapılandırma sınıfı oluşturmamız gerekir. Bu sınıf, WebSecurityConfigurerAdapter
sınıfını genişletir ve güvenlik yapılandırmasını tanımlar.
package com.example.ecommerce.config;
import com.example.ecommerce.security.JwtAuthenticationEntryPoint;
import com.example.ecommerce.security.JwtAuthenticationFilter;
import com.example.ecommerce.security.JwtTokenProvider;
import com.example.ecommerce.service.CustomUserDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomUserDetailsService customUserDetailsService;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtTokenProvider jwtTokenProvider;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter(jwtTokenProvider, customUserDetailsService);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/products/**", "/api/categories/**").permitAll()
.antMatchers("/api/reviews/product/**").permitAll()
.antMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Bu yapılandırma sınıfı, aşağıdaki özellikleri sağlar:
- CORS ve CSRF: CORS’u etkinleştirir ve CSRF korumasını devre dışı bırakır (JWT token kullanıldığı için).
- Oturum Yönetimi: Oturum oluşturmayı devre dışı bırakır (JWT token kullanıldığı için).
- URL Erişim Kuralları: Hangi URL’lerin kimlik doğrulaması gerektirdiğini belirtir.
- JWT Filtresi: JWT token’ları doğrulamak için bir filtre ekler.
Kullanıcı Detayları Servisi
Spring Security, kullanıcı kimlik doğrulaması için bir UserDetailsService
arayüzü sağlar. Bu arayüzü uygulayan bir sınıf oluşturmamız gerekir.
package com.example.ecommerce.service;
import com.example.ecommerce.model.User;
import com.example.ecommerce.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.isActive(),
true,
true,
true,
getAuthorities(user)
);
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
return authorities;
}
}
Bu sınıf, UserDetailsService
arayüzünü uygular ve loadUserByUsername
metodunu override eder. Bu metod, kullanıcı adına göre kullanıcıyı veritabanından alır ve Spring Security’nin UserDetails
nesnesine dönüştürür.
JWT Token Sağlayıcısı
JWT (JSON Web Token), kimlik doğrulama ve bilgi alışverişi için kompakt ve bağımsız bir yöntemdir. JWT token’ları, kullanıcı kimliğini ve yetkilerini içerir ve dijital olarak imzalanır.
package com.example.ecommerce.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
@Slf4j
public class JwtTokenProvider {
@Value("${app.jwt.secret}")
private String jwtSecret;
@Value("${app.jwt.expiration}")
private int jwtExpirationInMs;
public String generateToken(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(key)
.compact();
}
public String getUsernameFromJWT(String token) {
SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String authToken) {
try {
SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken);
return true;
} catch (MalformedJwtException ex) {
log.error("Invalid JWT token");
} catch (ExpiredJwtException ex) {
log.error("Expired JWT token");
} catch (UnsupportedJwtException ex) {
log.error("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
log.error("JWT claims string is empty");
}
return false;
}
}
Bu sınıf, JWT token’larını oluşturmak, doğrulamak ve token’dan kullanıcı adını çıkarmak için metodlar sağlar.
JWT Kimlik Doğrulama Filtresi
JWT token’larını doğrulamak için bir filtre oluşturmamız gerekir. Bu filtre, her HTTP isteğini yakalar ve JWT token’ını doğrular.
package com.example.ecommerce.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
String username = jwtTokenProvider.getUsernameFromJWT(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
Bu filtre, her HTTP isteğini yakalar, “Authorization” başlığından JWT token’ını alır, token’ı doğrular ve kullanıcı kimliğini güvenlik bağlamına ayarlar.
JWT Kimlik Doğrulama Giriş Noktası
Kimlik doğrulama hatası durumunda, uygun bir hata yanıtı döndürmek için bir giriş noktası oluşturmamız gerekir.
package com.example.ecommerce.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
log.error("Unauthorized error: {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Bu sınıf, kimlik doğrulama hatası durumunda 401 Unauthorized yanıtı döndürür.
Uygulama Özellikleri
JWT token’ları için gerekli özellikleri application.properties
dosyasına eklememiz gerekir:
# JWT Properties
app.jwt.secret=YourJwtSecretKey
app.jwt.expiration=86400000
Bu özellikler, JWT token’larının imzalanması için kullanılan gizli anahtarı ve token’ların geçerlilik süresini (milisaniye cinsinden) belirtir.
Kimlik Doğrulama ve Yetkilendirme İşlemleri
Kullanıcı Kaydı
Kullanıcı kaydı için, AuthController
sınıfında bir endpoint tanımlarız:
@PostMapping("/register")
public ResponseEntity<UserDto> register(@Valid @RequestBody UserDto userDto) {
UserDto createdUser = userService.createUser(userDto);
return ResponseEntity.ok(createdUser);
}
Bu endpoint, kullanıcı bilgilerini alır, şifreyi şifreler ve kullanıcıyı veritabanına kaydeder.
Kullanıcı Girişi
Kullanıcı girişi için, AuthController
sınıfında bir endpoint tanımlarız:
@PostMapping("/login")
public ResponseEntity<AuthResponseDto> login(@Valid @RequestBody AuthRequestDto authRequestDto) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authRequestDto.getUsername(),
authRequestDto.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateToken(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
UserDto userDto = userService.getUserByUsername(userDetails.getUsername());
AuthResponseDto authResponseDto = new AuthResponseDto(jwt, userDto);
return ResponseEntity.ok(authResponseDto);
}
Bu endpoint, kullanıcı adı ve şifreyi doğrular, bir JWT token oluşturur ve kullanıcı bilgileriyle birlikte döndürür.
Yetkilendirme
Yetkilendirme için, @PreAuthorize
anotasyonunu kullanırız. Bu anotasyon, Spring Security’nin SpEL (Spring Expression Language) ifadelerini kullanarak, endpoint’lere erişim yetkilerini belirler.
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<UserDto>> getAllUsers() {
List<UserDto> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
Bu endpoint, sadece “ADMIN” rolüne sahip kullanıcılar tarafından erişilebilir.
@GetMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or @userSecurity.isCurrentUser(#id)")
public ResponseEntity<UserDto> getUserById(@PathVariable Long id) {
UserDto user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
Bu endpoint, “ADMIN” rolüne sahip kullanıcılar veya kendi bilgilerini görüntüleyen kullanıcılar tarafından erişilebilir.
Güvenlik Kontrolleri
Güvenlik kontrolleri için, özel sınıflar oluşturabiliriz. Bu sınıflar, @PreAuthorize
anotasyonunda kullanılabilir.
@Component
@RequiredArgsConstructor
public class UserSecurity {
private final UserRepository userRepository;
public boolean isCurrentUser(Long userId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return false;
}
Object principal = authentication.getPrincipal();
if (!(principal instanceof UserDetails)) {
return false;
}
String username = ((UserDetails) principal).getUsername();
User user = userRepository.findByUsername(username).orElse(null);
return user != null && user.getId().equals(userId);
}
public boolean isCurrentUsername(String username) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return false;
}
Object principal = authentication.getPrincipal();
if (!(principal instanceof UserDetails)) {
return false;
}
return ((UserDetails) principal).getUsername().equals(username);
}
}
Bu sınıf, kullanıcının kendi bilgilerini görüntüleyip görüntüleyemeyeceğini kontrol eder.
Şifre Şifreleme
Şifreleri güvenli bir şekilde saklamak için, BCryptPasswordEncoder
sınıfını kullanırız.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Bu bean, şifreleri şifrelemek ve doğrulamak için kullanılır.
@Transactional
public UserDto createUser(UserDto userDto) {
// Kullanıcı adı ve e-posta kontrolü
if (userRepository.existsByUsername(userDto.getUsername())) {
throw new IllegalArgumentException("Username is already taken");
}
if (userRepository.existsByEmail(userDto.getEmail())) {
throw new IllegalArgumentException("Email is already in use");
}
// Kullanıcı oluşturma
User user = convertToEntity(userDto);
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
user.setActive(true);
User savedUser = userRepository.save(user);
return convertToDto(savedUser);
}
Bu metod, kullanıcı şifresini şifreler ve kullanıcıyı veritabanına kaydeder.
Güvenlik Testleri
Güvenlik yapılandırmasını test etmek için, unit testler ve integration testler yazabiliriz.
Unit Test Örneği
@ExtendWith(MockitoExtension.class)
public class JwtTokenProviderTest {
@Mock
private Authentication authentication;
@InjectMocks
private JwtTokenProvider jwtTokenProvider;
@BeforeEach
void setUp() {
ReflectionTestUtils.setField(jwtTokenProvider, "jwtSecret", "testSecret");
ReflectionTestUtils.setField(jwtTokenProvider, "jwtExpirationInMs", 3600000);
}
@Test
void generateToken_ValidAuthentication_ReturnsToken() {
// Arrange
UserDetails userDetails = new User("testuser", "password", Collections.emptyList());
when(authentication.getPrincipal()).thenReturn(userDetails);
// Act
String token = jwtTokenProvider.generateToken(authentication);
// Assert
assertNotNull(token);
assertTrue(token.length() > 0);
}
@Test
void getUsernameFromJWT_ValidToken_ReturnsUsername() {
// Arrange
UserDetails userDetails = new User("testuser", "password", Collections.emptyList());
when(authentication.getPrincipal()).thenReturn(userDetails);
String token = jwtTokenProvider.generateToken(authentication);
// Act
String username = jwtTokenProvider.getUsernameFromJWT(token);
// Assert
assertEquals("testuser", username);
}
@Test
void validateToken_ValidToken_ReturnsTrue() {
// Arrange
UserDetails userDetails = new User("testuser", "password", Collections.emptyList());
when(authentication.getPrincipal()).thenReturn(userDetails);
String token = jwtTokenProvider.generateToken(authentication);
// Act
boolean isValid = jwtTokenProvider.validateToken(token);
// Assert
assertTrue(isValid);
}
}
Bu unit test, JwtTokenProvider
sınıfının generateToken
, getUsernameFromJWT
ve validateToken
metodlarını test eder.
Integration Test Örneği
@SpringBootTest
@AutoConfigureMockMvc
public class AuthControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void register_ValidUserDto_ReturnsCreatedUser() throws Exception {
// Arrange
UserDto userDto = UserDto.builder()
.username("testuser")
.email("test@example.com")
.password("password")
.firstName("Test")
.lastName("User")
.role("USER")
.build();
// Act & Assert
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(userDto)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.username").value(userDto.getUsername()))
.andExpect(jsonPath("$.email").value(userDto.getEmail()));
}
@Test
void login_ValidCredentials_ReturnsToken() throws Exception {
// Arrange
User user = new User();
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setPassword(passwordEncoder.encode("password"));
user.setFirstName("Test");
user.setLastName("User");
user.setRole("USER");
user.setActive(true);
userRepository.save(user);
AuthRequestDto authRequestDto = new AuthRequestDto("testuser", "password");
// Act & Assert
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(authRequestDto)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.token").exists())
.andExpect(jsonPath("$.user.username").value(user.getUsername()))
.andExpect(jsonPath("$.user.email").value(user.getEmail()));
}
}
Bu integration test, AuthController
sınıfının register
ve login
endpoint’lerini test eder.
Hata Yönetimi ve Exception Handling
Hata yönetimi ve exception handling, uygulamanın güvenliği için önemlidir. Spring Boot, hata yönetimi için @ControllerAdvice
ve @ExceptionHandler
anotasyonlarını sağlar.
package com.example.ecommerce.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex) {
log.error("Resource not found exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
log.error("Illegal argument exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ErrorResponse> handleBadCredentialsException(BadCredentialsException ex) {
log.error("Bad credentials exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), "Invalid username or password");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
}
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException ex) {
log.error("Access denied exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.FORBIDDEN.value(), "Access denied");
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
log.error("Validation exception: {}", ex.getMessage());
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
log.error("Global exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
Bu sınıf, çeşitli istisnaları yakalar ve uygun HTTP yanıtlarına dönüştürür.
ErrorResponse
package com.example.ecommerce.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {
private int status;
private String message;
private LocalDateTime timestamp;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
this.timestamp = LocalDateTime.now();
}
}
Bu sınıf, hata yanıtlarının yapısını tanımlar.
ResourceNotFoundException
package com.example.ecommerce.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Bu sınıf, kaynak bulunamadığında fırlatılan bir istisnadır.
Güvenlik En İyi Uygulamaları
Spring Security ile güvenli bir uygulama geliştirmek için aşağıdaki en iyi uygulamaları izleyebilirsiniz:
- Güçlü Şifre Politikaları: Kullanıcıların güçlü şifreler oluşturmasını sağlayın.
- Şifre Şifreleme: Şifreleri her zaman şifrelenmiş olarak saklayın.
- HTTPS Kullanımı: Tüm iletişimi HTTPS üzerinden gerçekleştirin.
- CSRF Koruması: CSRF saldırılarına karşı koruma sağlayın.
- XSS Koruması: XSS saldırılarına karşı koruma sağlayın.
- Yetkilendirme Kontrolleri: Tüm endpoint’ler için uygun yetkilendirme kontrolleri uygulayın.
- Hata Mesajları: Hassas bilgileri açığa çıkaran hata mesajları göstermeyin.
- Oturum Yönetimi: Güvenli oturum yönetimi uygulayın.
- Rate Limiting: Brute force saldırılarını önlemek için rate limiting uygulayın.
- Güncel Bağımlılıklar: Güvenlik açıklarını önlemek için bağımlılıkları güncel tutun.
Sonuç
Bu bölümde, e-ticaret uygulaması için Spring Security kullanarak kimlik doğrulama ve yetkilendirme yapılandırmasını detaylı olarak ele aldık. Spring Security, Java uygulamaları için kapsamlı bir güvenlik çerçevesidir ve kimlik doğrulama, yetkilendirme ve diğer güvenlik özelliklerini sağlar.
Bir sonraki bölümde, frontend geliştirme konusunu ele alacağız.