diff --git a/pom.xml b/pom.xml index 842cd68..9b86c58 100644 --- a/pom.xml +++ b/pom.xml @@ -1,14 +1,16 @@ - - + + 4.0.0 org.springframework.boot spring-boot-starter-parent 3.2.5 - + com.example @@ -18,6 +20,7 @@ 17 + 0.12.5 @@ -25,6 +28,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + org.springframework.boot spring-boot-starter-data-jpa @@ -34,6 +41,27 @@ postgresql runtime + + org.liquibase + liquibase-core + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + org.projectlombok lombok diff --git a/src/main/java/com/example/bankcards/security/JwtAuthenticationFilter.java b/src/main/java/com/example/bankcards/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..7db031e --- /dev/null +++ b/src/main/java/com/example/bankcards/security/JwtAuthenticationFilter.java @@ -0,0 +1,61 @@ +package com.example.bankcards.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +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; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + String token = authHeader.substring(7); + String username = jwtService.extractUsername(token); + + if ( + username != null && + SecurityContextHolder.getContext().getAuthentication() == null + ) { + UserDetails userDetails = userDetailsService.loadUserByUsername( + username + ); + if (jwtService.isTokenValid(token, userDetails)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + authToken.setDetails( + new WebAuthenticationDetailsSource().buildDetails(request) + ); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/example/bankcards/security/JwtService.java b/src/main/java/com/example/bankcards/security/JwtService.java new file mode 100644 index 0000000..c818327 --- /dev/null +++ b/src/main/java/com/example/bankcards/security/JwtService.java @@ -0,0 +1,65 @@ +package com.example.bankcards.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +@Service +public class JwtService { + + @Value("${app.jwt.secret}") + private String secret; + + @Value("${app.jwt.expiration}") + private long expiration; + + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + return Jwts.builder() + .claims(claims) + .subject(userDetails.getUsername()) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSignKey()) + .compact(); + } + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public boolean isTokenValid(String token, UserDetails userDetails) { + String username = extractUsername(token); + return ( + username.equals(userDetails.getUsername()) && !isTokenExpired(token) + ); + } + + private boolean isTokenExpired(String token) { + return extractClaim(token, Claims::getExpiration).before(new Date()); + } + + private T extractClaim( + String token, + Function claimsResolver + ) { + Claims claims = Jwts.parser() + .verifyWith(getSignKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + return claimsResolver.apply(claims); + } + + private SecretKey getSignKey() { + return Keys.hmacShaKeyFor(secret.getBytes()); + } +} diff --git a/src/main/java/com/example/bankcards/security/UserDetailsServiceImpl.java b/src/main/java/com/example/bankcards/security/UserDetailsServiceImpl.java new file mode 100644 index 0000000..6944f37 --- /dev/null +++ b/src/main/java/com/example/bankcards/security/UserDetailsServiceImpl.java @@ -0,0 +1,33 @@ +package com.example.bankcards.security; + +import com.example.bankcards.entity.User; +import com.example.bankcards.repository.UserRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +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; + +@Service +@RequiredArgsConstructor +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException { + User user = userRepository + .findByUsername(username) + .orElseThrow(() -> + new UsernameNotFoundException("User not found: " + username) + ); + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + List.of(new SimpleGrantedAuthority(user.getRole().name())) + ); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 13c9ed3..bf78070 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,3 +12,8 @@ spring: server: port: 8080 + +app: + jwt: + secret: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 + expiration: 86400000