MS Lesson22: Spring Security Project

Step1: docker-compose.yaml fayli

services:
postgres:
image: postgres:17
container_name: security2
environment:
POSTGRES_DB: security2
POSTGRES_USER: security2
POSTGRES_PASSWORD: security2
ports:
- "5555:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- demo-network

networks:
demo-network:
driver: bridge

volumes:

postgres_data: 


application.yaml fayli

server:
port: 9999

spring:
profiles:
include:
- db


application-db.yaml fayli

spring:
datasource:
url: jdbc:postgresql://${DB_HOSTNAME:localhost}:${DB_PORT:5555}/security2
username: security2
password: security2
driver-class-name: org.postgresql.Driver

jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true



Step2: Entity-leri yaratmaq

User entity

package az.etibarli.step3.entity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.LinkedHashSet;
import java.util.Set;
@Getter
@Setter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
@ToString.Exclude
private Set<Role> roles = new LinkedHashSet<>();
}


Role entity

package az.etibarli.step3.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.util.LinkedHashSet;
import java.util.Set;
@Getter
@Setter
@ToString
@NoArgsConstructor
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "role_permissions",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
@ToString.Exclude
private Set<Permission> permissions = new LinkedHashSet<>();
}


Permission entity

package az.etibarli.step3.entity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "permissions")
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
public Long getId() {
return id;
}
public String getName() {
return name;
}
}


Step3: UserRepository yaratmaq

package az.etibarli.step3.repository;

import az.etibarli.step3.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByUsername(String username);

}



Step4: ProjectSecurityConfig yaratmaq

package az.etibarli.step3.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class ProjectSecurityConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

}


ve UserController yaratmaq

package az.etibarli.step3.controller;

import az.etibarli.step3.entity.User;
import az.etibarli.step3.repository.UserRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

public UserController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}

@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestBody User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
return ResponseEntity.ok("User is created");
}

}



Step5: SecurityFilterChain yaratmaq. Bu ona gore lazimdir ki bizim yaratdigimiz /register endpointini public etmek. Cunki bu shekilde accessimiz yoxdur

package az.etibarli.step3.config;

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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(request -> request
.requestMatchers("/register").permitAll()
.anyRequest().authenticated());
return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

}


*** CSRF nedir? Cross-Site Request Forgery.

CSRF esasen cookie/session ile ishleyen tetbiqlerde vacibdir. Biz REST API ve JWT token esasli auth qurdugumuz ucun bu dersde CSRF-i disable edirik. 



Step6. CustomUserDetailsService yaratmaq. 

Spring Security login zamani istifadecini DB-den username ile tapa bilsin.

Spring Security ozu bizim UserRepository-ni tanimir. Ona gore ona demeliyik: "Username gelse, get DB-den hemin user-i tap".


package az.etibarli.step3.security;
import az.etibarli.step3.repository.UserRepository;
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.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username).orElseThrow(
() -> new UsernameNotFoundException("User details not found for the user: " + username)
);
}
}


UserDetailsService Spring Security-nin istifadecinin tapmaq ucun istifade etdiyi interface-dir. Biz bu interface-i implement edib deyirik ki, istifadeci DB-den username ile tapilmalidir. 


Step7. User entity-ni UserDetails etmek. 

CustomUserDetailsService UserDetails qaytarmalidir, lakin bizim UserRepository User qaytarir. Ona gore User class-imiz Spring Security-nin gozlediyi interface-i implement etmelidir. 


package az.etibarli.step3.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

@Getter
@Setter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "users")
public class User implements UserDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String username;

@Column(nullable = false)
private String password;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
@ToString.Exclude
private Set<Role> roles = new LinkedHashSet<>();

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Stream.concat(
roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())),
roles.stream()
.flatMap(role -> role.getPermissions().stream())
.map(permission -> new SimpleGrantedAuthority(permission.getName()))
)
.distinct()
.toList();
}

@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
User user = (User) o;
return getId() != null && Objects.equals(getId(), user.getId());
}

@Override
public final int hashCode() {
Long id = getId();
return id != null ? id.hashCode() : System.identityHashCode(this);
}

}


* Spring Security bizim Role ve Permission class-larimizi ozu tanimir. O, yalniz GrantedAuthority anlayishini bilir. Biz de role ve permission-lari SimpleGrantedAuthority formatina ceviririk. 


Step8. CustomAuthenticationProvider yaratmaq.

Login zamani gelen username/password melumatlarini yoxlamaq.

Biz artiq bunlari etmishik:

a) UserRepository ile user-i DB-den tapa bilerik

b) CustomUserDetailsService ile Spring Security-ye user-i nece tapacagini deyirik

c) User artiq UserDetails oldu


package az.etibarli.step3.security;

import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {

private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;

@Override
public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (passwordEncoder.matches(password, userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
}
throw new BadCredentialsException("Invalid password!");
}

@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

}


AuthenticationProvider login prosesinde username ve password-u yoxlayan hissedir. Biz username ile user-i DB-den tapiriq, sonra gelen plain password-u DB-deki encoded password ile passwordEncoder.matches() vasitesile muqayise edirik. BCrypt password-u geri decode etmirik. Sadece matches() deyir ki, bu plain password hemin hash-a uygundur ya yox. 


Step9. Indi ise /login endpointi yaradaq


package az.etibarli.step3.controller;

import az.etibarli.step3.controller.dto.LoginRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class AuthController {

private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;

@PostMapping("/auth/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request) {
UserDetails user = userDetailsService.loadUserByUsername(request.username());

if (!passwordEncoder.matches(request.password(), user.getPassword())) {
throw new BadCredentialsException("Invalid username or password");
}

return ResponseEntity.ok("Login success");
}

}


package az.etibarli.step3.config;

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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ProjectSecurityConfig {

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(request -> request
.requestMatchers("/register", "/auth/login").permitAll()
.anyRequest().authenticated());
return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

}



Step10. Novbeti addim kimi login ugurlu olduqda JWT token yaratmaq

JWT bize ona gore lazimdir ki, login olunmush user-i her request-de yeniden teniya bilek, amma server terefde session saxlamaq mecburiyyetinde qalmayaq. Yeni server her user ucun ayrica session saxlamir. 

Token client-de olur, server ise her request-de token-i verify edir. 

JWT shifreleme deyil. JWT icindeki melumat oxuna biler. One gore token-in icine password, card number, secret data yazilmamalidir. 


a) dependency-ler elave etmek

implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'


b) application-security.yaml icinde JWT config saxlamaq

app:
jwt:
secret: "change-me-to-a-long-random-string-in-production"
expiration-ms: 86400000


c) 

package az.etibarli.step3.security;

import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public final class JwtSigningSupport {

private JwtSigningSupport() {
}

public static SecretKey hmacSha256KeyFromSecret(String secret) {
return Keys.hmacShaKeyFor(sha256(secret));
}

private static byte[] sha256(String value) {
try {
return MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}

}


Config faylindaki secret adli String-i JWT ucun lazim olan imza acarina cevirir. Bu klas secret-i goturur, duzgun formata salir ve JWT tokeni imzalamaq/yoxlamaq ucun hazir acar verir. JWT yaradilarken ve yoxlanilarken eyni acar lazimdir. Bu klas hemin acari hazirlayir. 


* JWT imza nedir?

JWT 3 hisseden ibaret olur: header.payload.signature

hader - tokenin tipi ve alqoritm. 


{
"alg": "HS256",
"typ": "JWT"
}


payload - username, authorities, expiration ve s.


{
"sub": "parvin",
"authorities": ["ROLE_USER", "read", "write"],
"iat": 1710000000,
"exp": 1710086400
}


signature - senin bu tokene qoydugun imza

Imza olmadan token sadece base64 textdir. Her kes payload-i deyishib yeni token yaza biler.

Server imza ile deyir: "Bu token menim secret acarimla yaradilib". 
Sonra request gelende yeniden yoxlayir: "Bu imza helede duzgundurmu".






























Bes o zaman bu klasin JwtSigningSupport rolu nedir?









Spring Boot-da JWT və BCrypt — Sadə Dildə İzah


Problem nədir?

HTTP yaddaşsızdır. Sən bir səhifədən digərinə keçəndə server səni unudur.

Köhnə üsul: Session — server hər istifadəçinin məlumatını özündə saxlayırdı. Milyonlarla istifadəçi olsa server yavaşlayır.

Yeni üsul: JWT — server heç nə saxlamır. İstifadəçinin məlumatları tokenin içindədir.


Hissə 1 — Token Yaranması (Login)

Addım 1 — İstifadəçi credentials daxil edir

username → parvin
password → parvin

Addım 2 — Server bazada username-ə görə istifadəçini tapır

Addım 3 — Bazadakı BCrypt hash-i götürür

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy

Addım 4 — Hash-dən iki şey extract edir

$10$                    → 2¹⁰ = 1024 dəfə iterasiya olunacaq
N9qo8uLOickgx2ZMRZoMye → salt (22 simvol)

Addım 5 — Şifrə yoxlanır

İstifadəçinin daxil etdiyi password həmin salt ilə qarışdırılır və 1024 dəfə iterasiya olunur. Nəticədə 31 simvolluq hash alınır.

"parvin" + salt → 1024 dövr → 31 simvolluq hash

Addım 6 — 3 hissə birləşdirilir → 60 simvol

$2a$10$ (7) + salt (22) + hash (31) = 60 simvol

passwordEncoder.matches() ilə müqayisə edilir. Bu metod XOR əsasında işləyir — həmişə sona qədər gedir, heç bir simvolda dayanmır. Buna görə Timing Attack mümkün deyil.

✅ Eynidir   → şifrə düzdür, davam et
❌ Fərqlidir → şifrə yanlışdır, 401 qaytar

⚠️ String.equals() işlədilmir — o birinci fərqli simvolda dayanır, haker vaxtı ölçüb şifrə haqqında məlumat əldə edə bilər.

Addım 7 — JWT üçün Header və Payload hazırlanır

Header:

{"alg": "HS256", "typ": "JWT"}  →  Base64URL ilə kodlanır

Payload:

{"id": 42, "username": "parvin", "role": "admin", "exp": 1735000000}  →  Base64URL ilə kodlanır

⚠️ Base64 şifrələmə deyil! Hər kəs geri çevirib oxuya bilər. Buraya şifrə, kart nömrəsi yazma!

Addım 8 — HMAC açarı (SigningKey) yaradılır

Secret key (String) SHA-256 ilə dəqiq 32 bayta çevrilir və HMAC açarı yaradılır.

"mySecretKey" → SHA-256 → 32 bayt → SigningKey

Niyə SHA-256? HMAC-SHA256 açarı mütləq 32 bayt olmalıdır. String isə istənilən uzunluqda ola bilər. SHA-256 nə versən həmişə 32 bayt qaytarır.

Addım 9 — İmza yaradılır

Header və Payload nöqtə ilə birləşdirilir, sonra SigningKey ilə HMAC-a verilir:

Header_Base64 + "." + Payload_Base64  →  birləşdirilir
HMAC(birləşdirilmiş mətn, SigningKey) →  Signature

Addım 10 — Final token formalaşır

Header_Base64 . Payload_Base64 . Signature

Addım 11 — İstifadəçiyə access token və refresh token verilir


Hissə 2 — Token Yoxlanması (Hər Sorğu)

Addım 1 — İstifadəçi access token ilə sorğu göndərir

Addım 2 — HMAC açarı (SigningKey) yaradılır

Secret key → SHA-256 → 32 bayt → SigningKey

Addım 3 — Gələn tokenin Header və Payload-ı götürülür, signature yenidən hesablanır

HMAC(Header_Base64 + "." + Payload_Base64, SigningKey) = HesablanmışSignature

Addım 4 — Müqayisə edilir

MessageDigest.isEqual() ilə müqayisə edilir. Bu metod da XOR əsasında işləyir — sabit vaxt, Timing Attack yoxdur.

✅ Eynidir   → token etibarlıdır, access verilir
❌ Fərqlidir → token saxta, 401 qaytar

Hissə 3 — Secret Key Niyə Bu Qədər Vacibdir?

Əgər secret key açılsa — hər kəs öz tokenini özü yarada bilər:

1. Mövcud tokeni götür, Payload-ı decode et (Base64-dür, asandır)
   {"role": "user"}

2. Payload-ı dəyişdir
   {"role": "admin"}

3. Dəyişdirilmiş Payload-ı Base64 et

4. Secret key ilə SigningKey yarat

5. HMAC(Header_Base64 + "." + YeniPayload_Base64, SigningKey) → YeniSignature

6. Yeni token:
   Header_Base64 . YeniPayload_Base64 . YeniSignature

7. Server yoxlayır → signature düzdür → admin kimi daxil olur ✅

Server fərqi görmür — çünki token texniki olaraq tamamilə düzgündür.

Secret key necə qorunmalıdır?

❌ Yanlış → jwt.secret = mySecretKey123   (koda yazılır, GitHub-a yüklənir)
✅ Düzgün → jwt.secret = ${JWT_SECRET}    (mühit dəyişənindən oxunur)

Production-da isə Vault kimi xüsusi secret management alətləri işlədilir.


Hissə 4 — Timing Attack Nədir?

String.equals() müqayisəni birinci fərqli simvolda dayandırır:

"ABC" vs "XBC"  →  çox tez bitir   (1-ci simvolda fərq)
"ABC" vs "ABX"  →  bir az gec bitir (3-cü simvolda fərq)

Haker minlərlə cəhd edib vaxtı ölçsə — hash haqqında ipucu əldə edər.

Buna görə həm BCrypt, həm JWT sabit vaxt alan müqayisə işlədir:

BCrypt  →  passwordEncoder.matches()  →  XOR, sabit vaxt
JWT     →  MessageDigest.isEqual()    →  XOR, sabit vaxt

Həmişə sona qədər gedir — fərq harada olursa olsun, vaxt dəyişmir.


Xülasə

Mərhələ Nə baş verir
Qeydiyyat Şifrə BCrypt ilə hashlanır (salt + 1024 iterasiya), bazaya yazılır
Login BCrypt.matches() ilə şifrə yoxlanır, JWT token yaradılır
Token yaranması Header + Payload → Base64, Secret → SigningKey, HMAC → Signature
Hər sorğu SigningKey ilə signature yenidən hesablanır, MessageDigest.isEqual() ilə müqayisə edilir
Təhlükəsizlik Secret key açılsa sistem tam açılır — mühit dəyişənində saxla





Step11. Indi ise JwtAuthenticationFilter yaradiriq ve ProjectSecurityConfig-i stateless session ve filter ile yenileyirik. 


package az.etibarli.step3.filter;

import az.etibarli.step3.config.JwtProperties;
import az.etibarli.step3.security.JwtSigningSupport;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
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.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.crypto.SecretKey;
import java.io.IOException;
import java.util.Date;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private static final String BEARER_PREFIX = "Bearer ";

private final UserDetailsService userDetailsService;
private final SecretKey signingKey;

public JwtAuthenticationFilter(JwtProperties jwtProperties, UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
this.signingKey = JwtSigningSupport.hmacSha256KeyFromSecret(jwtProperties.getSecret());
}

@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith(BEARER_PREFIX)) {
filterChain.doFilter(request, response);
return;
}

String token = header.substring(BEARER_PREFIX.length());
if (SecurityContextHolder.getContext().getAuthentication() != null) {
filterChain.doFilter(request, response);
return;
}

try {
Claims claims = Jwts.parser()
.verifyWith(signingKey)
.build()
.parseSignedClaims(token)
.getPayload();

if (claims.getExpiration().before(new Date())) {
filterChain.doFilter(request, response);
return;
}

String username = claims.getSubject();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (JwtException | IllegalArgumentException ignored) {
// invalid or malformed JWT — continue without authentication
}

filterChain.doFilter(request, response);
}
}


package az.etibarli.step3.config;

import az.etibarli.step3.filter.JwtAuthenticationFilter;
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.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class ProjectSecurityConfig {

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(request -> request
.requestMatchers("/register", "/auth/login").permitAll()
.anyRequest().authenticated());
return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

}




Step12. TestController yaradib test etmek

package az.etibarli.step3.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class TestController {

@GetMapping("/roles-user-manager-admin-read")
private ResponseEntity<String> one() {
return ResponseEntity.ok("roles user/manager/admin + read");
}

@GetMapping("/roles-manager-admin-read")
private ResponseEntity<String> two() {
return ResponseEntity.ok("roles manager/admin + read");
}

@GetMapping("/roles-manager-admin-write")
private ResponseEntity<String> three() {
return ResponseEntity.ok("roles manager/admin + write");
}

@GetMapping("/roles-admin-read")
private ResponseEntity<String> four() {
return ResponseEntity.ok("role admin + read");
}

@GetMapping("/roles-admin-write")
private ResponseEntity<String> five() {
return ResponseEntity.ok("role admin + write");
}

@GetMapping("/roles-admin-delete")
private ResponseEntity<String> six() {
return ResponseEntity.ok("role admin + delete");
}

}










Комментарии

Популярные сообщения из этого блога

Interview questions

Lesson1: JDK, JVM, JRE

Lesson_2: Operations in Java