diff --git a/spring-security-modules/spring-security-core-2/pom.xml b/spring-security-modules/spring-security-core-2/pom.xml
index ace629eef1..94940f2613 100644
--- a/spring-security-modules/spring-security-core-2/pom.xml
+++ b/spring-security-modules/spring-security-core-2/pom.xml
@@ -17,6 +17,7 @@
com.baeldung.authresolver.AuthResolverApplication
+ 0.12.5
@@ -61,6 +62,21 @@
org.springframework.security
spring-security-core
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jjwt.version}
+
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/JwtResponse.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/JwtResponse.java
new file mode 100644
index 0000000000..371491d49e
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/JwtResponse.java
@@ -0,0 +1,54 @@
+package com.baeldung.jwtsignkey;
+
+public class JwtResponse {
+ private String token;
+ private String type = "Bearer";
+
+ private String username;
+
+
+ public JwtResponse(String accessToken, String username) {
+ this.token = accessToken;
+ this.username = username;
+ }
+
+ public String getAccessToken() {
+ return token;
+ }
+
+ public void setAccessToken(String accessToken) {
+ this.token = accessToken;
+ }
+
+ public String getTokenType() {
+ return type;
+ }
+
+ public void setTokenType(String tokenType) {
+ this.type = tokenType;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/SpringJwtApplication.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/SpringJwtApplication.java
new file mode 100644
index 0000000000..72917323c2
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/SpringJwtApplication.java
@@ -0,0 +1,4 @@
+package com.baeldung.jwtsignkey;
+
+public class SpringJwtApplication {
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/controller/JwtAuthController.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/controller/JwtAuthController.java
new file mode 100644
index 0000000000..57b49ec9b8
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/controller/JwtAuthController.java
@@ -0,0 +1,70 @@
+package com.baeldung.jwtsignkey.controller;
+
+import com.baeldung.jwtsignkey.jwtconfig.JwtUtils;
+import com.baeldung.jwtsignkey.model.User;
+import com.baeldung.jwtsignkey.repository.UserRepository;
+import com.baeldung.jwtsignkey.userservice.UserDetailsImpl;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.UnsupportedEncodingException;
+
+@CrossOrigin(origins = "*", maxAge = 3600)
+@RestController
+public class JwtAuthController {
+
+ @Autowired
+ AuthenticationManager authenticationManager;
+
+ @Autowired
+ PasswordEncoder encoder;
+
+ @Autowired
+ JwtUtils jwtUtils;
+
+ @Autowired
+ UserRepository userRepository;
+
+ @PostMapping("/signup")
+ public ResponseEntity> registerUser(@RequestBody User signUpRequest, HttpServletRequest request) throws UnsupportedEncodingException {
+
+
+ // Create new user's account
+ User user = new User();
+ user.setUsername(signUpRequest.getUsername());
+ user.setPassword(encoder.encode(signUpRequest.getPassword()));
+
+ userRepository.save(user);
+
+ return ResponseEntity.ok(user);
+ }
+
+ @PostMapping("/signin")
+ public ResponseEntity> authenticateUser( @RequestBody LoginRequest loginRequest) {
+
+ Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
+
+ SecurityContextHolder.getContext()
+ .setAuthentication(authentication);
+ UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
+
+ String jwt = jwtUtils.generateJwtToken(authentication);
+
+
+
+ return ResponseEntity.ok(
+ new JwtResponse(jwt, userDetails.getUsername()));
+
+ }
+
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/AuthEntryPointJwt.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/AuthEntryPointJwt.java
new file mode 100644
index 0000000000..1fe3b6aa63
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/AuthEntryPointJwt.java
@@ -0,0 +1,39 @@
+package com.baeldung.jwtsignkey.jwtconfig;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class AuthEntryPointJwt implements AuthenticationEntryPoint {
+
+ private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
+
+ @Override
+ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
+ logger.error("Unauthorized error: {}", authException.getMessage());
+
+ response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+
+ final Map body = new HashMap<>();
+ body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
+ body.put("error", "Unauthorized");
+ body.put("message", authException.getMessage());
+ body.put("path", request.getServletPath());
+
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(response.getOutputStream(), body);
+ }
+}
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/AuthTokenFilter.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/AuthTokenFilter.java
new file mode 100644
index 0000000000..64dc102cff
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/AuthTokenFilter.java
@@ -0,0 +1,63 @@
+package com.baeldung.jwtsignkey.jwtconfig;
+
+import com.baeldung.jwtsignkey.userservice.UserDetailsServiceImpl;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+public class AuthTokenFilter extends OncePerRequestFilter {
+ @Autowired
+ private JwtUtils jwtUtils;
+
+ @Autowired
+ private UserDetailsServiceImpl userDetailsService;
+
+ private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ try {
+ String jwt = parseJwt(request);
+ if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
+ String username = jwtUtils.getUserNameFromJwtToken(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 e) {
+ logger.error("Cannot set user authentication: {}", e);
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ private String parseJwt(HttpServletRequest request) {
+ String headerAuth = request.getHeader("Authorization");
+
+ if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
+ return headerAuth.substring(7, headerAuth.length());
+ }
+
+ return null;
+ }
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/JwtUtils.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/JwtUtils.java
new file mode 100644
index 0000000000..a5c3ac9556
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/jwtconfig/JwtUtils.java
@@ -0,0 +1,69 @@
+package com.baeldung.jwtsignkey.jwtconfig;
+
+import com.baeldung.jwtsignkey.userservice.UserDetailsImpl;
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.security.Keys;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import java.util.Date;
+
+@Component
+public class JwtUtils {
+ private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
+
+ @Value("${baeldung.app.jwtSecret}")
+ private String jwtSecret;
+
+ @Value("${baeldung.app.jwtExpirationMs}")
+ private int jwtExpirationMs;
+
+ public String generateJwtToken(Authentication authentication) {
+
+ UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
+
+ return Jwts.builder()
+ .setSubject((userPrincipal.getUsername()))
+ .setIssuedAt(new Date())
+ .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
+ .signWith(getSigningKey())
+ .compact();
+
+ }
+
+ private Key getSigningKey() {
+ byte[] keyBytes = this.jwtSecret.getBytes(StandardCharsets.UTF_8);
+ return Keys.hmacShaKeyFor(keyBytes);
+ }
+
+ public String getUserNameFromJwtToken(String token) {
+ //return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
+ return Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody().getSubject();
+
+ }
+
+ public boolean validateJwtToken(String authToken) {
+ try {
+ Jwts.parser().setSigningKey(getSigningKey()).build().parseClaimsJws(authToken);
+ return true;
+ } catch (SignatureException e) {
+ logger.error("Invalid JWT signature: {}", e.getMessage());
+ } catch (MalformedJwtException e) {
+ logger.error("Invalid JWT token: {}", e.getMessage());
+ } catch (ExpiredJwtException e) {
+ logger.error("JWT token is expired: {}", e.getMessage());
+ } catch (UnsupportedJwtException e) {
+ logger.error("JWT token is unsupported: {}", e.getMessage());
+ } catch (IllegalArgumentException e) {
+ logger.error("JWT claims string is empty: {}", e.getMessage());
+ }
+
+ return false;
+ }
+
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/model/User.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/model/User.java
new file mode 100644
index 0000000000..c7acda047a
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/model/User.java
@@ -0,0 +1,45 @@
+package com.baeldung.jwtsignkey.model;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+@Entity
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String username;
+
+ private String password;
+
+ public User() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/repository/UserRepository.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/repository/UserRepository.java
new file mode 100644
index 0000000000..8d2706f756
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/repository/UserRepository.java
@@ -0,0 +1,12 @@
+package com.baeldung.jwtsignkey.repository;
+
+import com.baeldung.jwtsignkey.model.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface UserRepository extends JpaRepository {
+
+ Optional findByUsername(String username);
+
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/securityconfig/SecurityConfiguration.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/securityconfig/SecurityConfiguration.java
new file mode 100644
index 0000000000..be980966c8
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/securityconfig/SecurityConfiguration.java
@@ -0,0 +1,80 @@
+package com.baeldung.jwtsignkey.securityconfig;
+
+import com.baeldung.jwtsignkey.jwtconfig.AuthEntryPointJwt;
+import com.baeldung.jwtsignkey.jwtconfig.AuthTokenFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+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.configurers.AbstractHttpConfigurer;
+import org.springframework.security.core.userdetails.UserDetailsService;
+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;
+
+import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity
+public class SecurityConfiguration {
+
+ @Autowired
+ UserDetailsService userDetailsService;
+ @Autowired
+ private AuthEntryPointJwt unauthorizedHandler;
+
+ private static final String[] WHITE_LIST_URL = { "/signin", "/signup"
+ };
+
+ @Bean
+ public AuthTokenFilter authenticationJwtTokenFilter() {
+ return new AuthTokenFilter();
+ }
+
+
+
+ @Bean
+ public DaoAuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+
+ authProvider.setUserDetailsService(userDetailsService);
+ authProvider.setPasswordEncoder(passwordEncoder());
+
+ return authProvider;
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
+ return authConfig.getAuthenticationManager();
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http.csrf(AbstractHttpConfigurer::disable)
+ .cors(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(req -> req.requestMatchers(WHITE_LIST_URL)
+ .permitAll()
+ .anyRequest()
+ .authenticated())
+ .exceptionHandling( ex -> ex.authenticationEntryPoint(unauthorizedHandler))
+ .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
+ .authenticationProvider(authenticationProvider())
+ .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+
+}
+
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/userservice/UserDetailsImpl.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/userservice/UserDetailsImpl.java
new file mode 100644
index 0000000000..7c145d50c8
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/userservice/UserDetailsImpl.java
@@ -0,0 +1,92 @@
+package com.baeldung.jwtsignkey.userservice;
+
+import com.baeldung.jwtsignkey.model.User;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Objects;
+
+public class UserDetailsImpl implements UserDetails {
+ private static final long serialVersionUID = 1L;
+
+ private Long id;
+
+ private String username;
+
+ @JsonIgnore
+ private String password;
+
+
+ public UserDetailsImpl(Long id, String username, String password) {
+ this.id = id;
+ this.username = username;
+ this.password = password;
+
+ }
+
+ public static UserDetailsImpl build(User user) {
+
+
+ return new UserDetailsImpl(user.getId(), user.getUsername(), user.getPassword());
+ }
+
+
+ public Long getId() {
+ return id;
+ }
+
+
+
+ public static long getSerialversionuid() {
+ return serialVersionUID;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return null;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ UserDetailsImpl user = (UserDetailsImpl) o;
+ return Objects.equals(id, user.id);
+ }
+}
diff --git a/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/userservice/UserDetailsServiceImpl.java b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/userservice/UserDetailsServiceImpl.java
new file mode 100644
index 0000000000..dcaa4ec716
--- /dev/null
+++ b/spring-security-modules/spring-security-core-2/src/main/java/com/baeldung/jwtsignkey/userservice/UserDetailsServiceImpl.java
@@ -0,0 +1,28 @@
+package com.baeldung.jwtsignkey.userservice;
+
+import com.baeldung.jwtsignkey.model.User;
+import com.baeldung.jwtsignkey.repository.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+;
+
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+ @Autowired
+ UserRepository userRepository;
+
+ @Override
+ @Transactional
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ User user = userRepository.findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
+
+ return UserDetailsImpl.build(user);
+ }
+}
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-core-2/src/main/resources/application.properties b/spring-security-modules/spring-security-core-2/src/main/resources/application.properties
index 9d154c9cc0..d6d6a6f507 100644
--- a/spring-security-modules/spring-security-core-2/src/main/resources/application.properties
+++ b/spring-security-modules/spring-security-core-2/src/main/resources/application.properties
@@ -1 +1,3 @@
-spring.thymeleaf.prefix=classpath:/templates/
\ No newline at end of file
+spring.thymeleaf.prefix=classpath:/templates/
+baeldung.app.jwtSecret= 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
+baeldung.app.jwtExpirationMs= 8640000
\ No newline at end of file