From 3160a19fa5cf977a7f054ca397690bb79ff89415 Mon Sep 17 00:00:00 2001 From: Dmitrii Cravcenco Date: Thu, 12 Oct 2023 12:52:39 +0300 Subject: [PATCH] Add refresh token generation --- .../config/ExceptionHandlers.java | 19 ++++++++++++ .../config/JwtAuthenticationFilter.java | 29 +++++++++++-------- .../expensetrackerfaf/config/JwtService.java | 11 +++++++ .../config/SecurityConfiguration.java | 2 ++ .../config/TokenExpiredException.java | 7 +++++ .../auth/AuthenticationController.java | 12 ++++---- .../controller/auth/ErrorResponse.java | 16 ++++++++++ .../service/AuthenticationService.java | 9 +++--- 8 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/faf223/expensetrackerfaf/config/ExceptionHandlers.java create mode 100644 src/main/java/com/faf223/expensetrackerfaf/config/TokenExpiredException.java create mode 100644 src/main/java/com/faf223/expensetrackerfaf/controller/auth/ErrorResponse.java diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/ExceptionHandlers.java b/src/main/java/com/faf223/expensetrackerfaf/config/ExceptionHandlers.java new file mode 100644 index 0000000..9349218 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/config/ExceptionHandlers.java @@ -0,0 +1,19 @@ +package com.faf223.expensetrackerfaf.config; + +import com.faf223.expensetrackerfaf.controller.auth.ErrorResponse; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ControllerAdvice +public class ExceptionHandlers { + + @ExceptionHandler(TokenExpiredException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ResponseBody + public ErrorResponse handleTokenExpiredException(TokenExpiredException ex) { + return new ErrorResponse("Unauthorized", ex.getMessage()); + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java b/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java index 1f91bc6..4529641 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java +++ b/src/main/java/com/faf223/expensetrackerfaf/config/JwtAuthenticationFilter.java @@ -1,5 +1,6 @@ package com.faf223.expensetrackerfaf.config; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -15,7 +16,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; - @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -35,23 +35,28 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } final String authHeader = request.getHeader("Authorization"); final String jwt; - final String userEmail; + String userEmail; if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } + jwt = authHeader.substring(7); - userEmail = jwtService.extractUsername(jwt); - if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); - if (jwtService.isTokenValid(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities()); - authToken.setDetails(new WebAuthenticationDetailsSource() - .buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authToken); + + try { + userEmail = jwtService.extractUsername(jwt); + if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } } + } catch (ExpiredJwtException e) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } filterChain.doFilter(request, response); } -} \ No newline at end of file +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java b/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java index a9904f9..9cf6066 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/config/JwtService.java @@ -38,6 +38,10 @@ public class JwtService { return generateToken(new HashMap<>(), userDetails); } + public String generateRefreshToken(UserDetails userDetails) { + return generateRefreshToken(new HashMap<>(), userDetails); + } + public String generateToken( Map extraClaims, UserDetails userDetails @@ -45,6 +49,13 @@ public class JwtService { return buildToken(extraClaims, userDetails, jwtExpiration); } + public String generateRefreshToken( + Map extraClaims, + UserDetails userDetails + ) { + return buildToken(extraClaims, userDetails, refreshExpiration); + } + private String buildToken(Map extraClaims, UserDetails userDetails, long expiration) { return Jwts .builder() diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java b/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java index 430f3ff..0c594fd 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java +++ b/src/main/java/com/faf223/expensetrackerfaf/config/SecurityConfiguration.java @@ -19,6 +19,7 @@ public class SecurityConfiguration { private final JwtAuthenticationFilter jwtAuthFilter; private final AuthenticationProvider authenticationProvider; +// private final Http401UnauthorizedEntryPoint entryPoint; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -29,6 +30,7 @@ public class SecurityConfiguration { .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) +// .exceptionHandling((e) -> e.authenticationEntryPoint(entryPoint)) .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // will be executed before UsernamePasswordAuthenticationFilter diff --git a/src/main/java/com/faf223/expensetrackerfaf/config/TokenExpiredException.java b/src/main/java/com/faf223/expensetrackerfaf/config/TokenExpiredException.java new file mode 100644 index 0000000..62e6db2 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/config/TokenExpiredException.java @@ -0,0 +1,7 @@ +package com.faf223.expensetrackerfaf.config; + +public class TokenExpiredException extends RuntimeException { + public TokenExpiredException(String message) { + super(message); + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java index 43c25df..b53fc63 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/AuthenticationController.java @@ -1,19 +1,17 @@ package com.faf223.expensetrackerfaf.controller.auth; import com.faf223.expensetrackerfaf.service.AuthenticationService; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("api/v1/auth") +@RequiredArgsConstructor public class AuthenticationController { private final AuthenticationService service; - public AuthenticationController(AuthenticationService service) { - this.service = service; - } - @PostMapping("/register") public ResponseEntity register(@RequestBody RegisterRequest request) { return ResponseEntity.ok(service.register(request)); @@ -24,8 +22,8 @@ public class AuthenticationController { return ResponseEntity.ok(service.authenticate(request)); } - @PostMapping("/refresh") - public ResponseEntity refreshAccessToken(@RequestBody TokenRefreshRequest refreshRequest) { - return ResponseEntity.ok(service.refreshAccessToken(refreshRequest)); + @PostMapping("/refreshtoken") + public ResponseEntity refreshAccessToken(@RequestBody TokenRefreshRequest request) { + return ResponseEntity.ok(service.refreshAccessToken(request)); } } diff --git a/src/main/java/com/faf223/expensetrackerfaf/controller/auth/ErrorResponse.java b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/ErrorResponse.java new file mode 100644 index 0000000..54db638 --- /dev/null +++ b/src/main/java/com/faf223/expensetrackerfaf/controller/auth/ErrorResponse.java @@ -0,0 +1,16 @@ +package com.faf223.expensetrackerfaf.controller.auth; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ErrorResponse { + private String error; + private String message; + + public ErrorResponse(String error, String message) { + this.error = error; + this.message = message; + } +} diff --git a/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java index bbc0440..4b644f0 100644 --- a/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java +++ b/src/main/java/com/faf223/expensetrackerfaf/service/AuthenticationService.java @@ -18,7 +18,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.io.IOException; import java.util.Optional; @Service @@ -45,7 +44,7 @@ public class AuthenticationService { UserDetails userDetails = new PersonDetails(credential); String jwtToken = jwtService.generateToken(userDetails); - String refreshToken = jwtService.generateToken(userDetails); + String refreshToken = jwtService.generateRefreshToken(userDetails); return AuthenticationResponse.builder() .accessToken(jwtToken) @@ -60,7 +59,7 @@ public class AuthenticationService { UserDetails userDetails = new PersonDetails(credential); String jwtToken = jwtService.generateToken(userDetails); - String refreshToken = jwtService.generateToken(userDetails); + String refreshToken = jwtService.generateRefreshToken(userDetails); return AuthenticationResponse.builder() .accessToken(jwtToken) .refreshToken(refreshToken) @@ -77,9 +76,9 @@ public class AuthenticationService { String jwtToken = jwtService.generateToken(userDetails); return AuthenticationResponse.builder() .accessToken(jwtToken) - .refreshToken(refreshToken) // Return the same refresh token + .refreshToken(refreshToken) .build(); - }else { + } else { throw new RuntimeException("Invalid or expired refresh token"); } }