From a68232d5a898c61caa2e8938078e4b5582e6df12 Mon Sep 17 00:00:00 2001 From: Lauro Silveira Date: Sun, 6 Oct 2024 21:07:54 +0200 Subject: [PATCH 01/10] Added RefreshToken and AuthenticationEntryPoint - Implemented RefreshToken endpoint - Implemented AuthenticationEntryPoint to redirect JWT exceptions - Adjusted tests --- .../AuthenticationController.java | 18 ++- .../AuthenticationControllerAdvice.java | 11 +- .../controller/dto/token/RefreshTokenVO.java | 6 + .../domain/token/RefreshToken.java | 31 ++++++ .../repository/RefreshTokenRepository.java | 7 ++ .../repository/UserRepository.java | 2 +- .../security/JwtAuthenticationEntryPoint.java | 29 +++++ .../security/SecurityConfigurations.java | 8 +- .../security/SecurityFilter.java | 77 ++++++------- .../security/TokenService.java | 104 ++++++++++-------- .../security/dto/TokenJwtDto.java | 2 +- .../service/token/RefreshTokenService.java | 37 +++++++ .../AuthenticationControllerTest.java | 7 +- .../security/TokenServiceTest.java | 27 +++-- 14 files changed, 260 insertions(+), 106 deletions(-) create mode 100644 src/main/java/com/alura/aluraflixapi/controller/dto/token/RefreshTokenVO.java create mode 100644 src/main/java/com/alura/aluraflixapi/domain/token/RefreshToken.java create mode 100644 src/main/java/com/alura/aluraflixapi/infraestructure/repository/RefreshTokenRepository.java create mode 100644 src/main/java/com/alura/aluraflixapi/infraestructure/security/JwtAuthenticationEntryPoint.java create mode 100644 src/main/java/com/alura/aluraflixapi/infraestructure/service/token/RefreshTokenService.java diff --git a/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationController.java b/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationController.java index ead70b9..de71f14 100644 --- a/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationController.java +++ b/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationController.java @@ -1,9 +1,11 @@ package com.alura.aluraflixapi.controller.authentication; +import com.alura.aluraflixapi.controller.dto.token.RefreshTokenVO; import com.alura.aluraflixapi.domain.user.User; import com.alura.aluraflixapi.domain.user.dto.AuthenticationDto; import com.alura.aluraflixapi.infraestructure.security.TokenService; import com.alura.aluraflixapi.infraestructure.security.dto.TokenJwtDto; +import com.alura.aluraflixapi.infraestructure.service.token.RefreshTokenService; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -21,10 +23,12 @@ public class AuthenticationController { private final AuthenticationManager authenticationManager; private final TokenService tokenService; + private final RefreshTokenService refreshTokenService; - public AuthenticationController(AuthenticationManager authenticationManager, TokenService tokenService) { + public AuthenticationController(AuthenticationManager authenticationManager, TokenService tokenService, RefreshTokenService refreshTokenService) { this.authenticationManager = authenticationManager; this.tokenService = tokenService; + this.refreshTokenService = refreshTokenService; } @PostMapping @@ -35,9 +39,17 @@ public ResponseEntity login(@RequestBody @Valid AuthenticationDto d dto.password()); //authenticationManager compare the password of the request with the password from database. final var authentication = this.authenticationManager.authenticate(authenticationToken); - final var tokenJWT = tokenService.generateTokenJWT((User) authentication.getPrincipal()); + final var tokenJwt = tokenService.generateTokenJwt((User) authentication.getPrincipal()); + final var refreshToken = tokenService.generateRefreshToken(dto.username()); log.info("Token Generated with Success!"); - return ResponseEntity.ok().body(new TokenJwtDto(tokenJWT)); + return ResponseEntity.ok().body(new TokenJwtDto(tokenJwt, refreshToken)); + } + + @PostMapping("/refresh") + public ResponseEntity refreshAccessToken(@RequestBody RefreshTokenVO refreshToken) { + log.info("Request to renew refresh accessToken"); + final var tokenJwtDto = refreshTokenService.refreshAccessToken(refreshToken.refreshToken()); + return ResponseEntity.ok(tokenJwtDto); } } diff --git a/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationControllerAdvice.java b/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationControllerAdvice.java index 60dc873..fb748f6 100644 --- a/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationControllerAdvice.java +++ b/src/main/java/com/alura/aluraflixapi/controller/authentication/AuthenticationControllerAdvice.java @@ -4,19 +4,26 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -@RestControllerAdvice(assignableTypes = AuthenticationController.class) +@RestControllerAdvice public class AuthenticationControllerAdvice { @ExceptionHandler(UsernameNotFoundException.class) public ResponseEntity handlerAuthenticationException(UsernameNotFoundException ex) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorMessageVO(ex.getMessage(), HttpStatus.NOT_FOUND)); } + @ExceptionHandler(BadCredentialsException.class) public ResponseEntity handleBadCredentialsException(BadCredentialsException ex) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorMessageVO(ex.getMessage(), HttpStatus.BAD_REQUEST)); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorMessageVO("Invalid username or password", HttpStatus.BAD_REQUEST)); + } + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleInvalidTokenException(AuthenticationException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorMessageVO("Invalid token, please verify expiration", HttpStatus.BAD_REQUEST)); } } diff --git a/src/main/java/com/alura/aluraflixapi/controller/dto/token/RefreshTokenVO.java b/src/main/java/com/alura/aluraflixapi/controller/dto/token/RefreshTokenVO.java new file mode 100644 index 0000000..abe93bd --- /dev/null +++ b/src/main/java/com/alura/aluraflixapi/controller/dto/token/RefreshTokenVO.java @@ -0,0 +1,6 @@ +package com.alura.aluraflixapi.controller.dto.token; + +import jakarta.validation.constraints.NotBlank; + +public record RefreshTokenVO(@NotBlank String refreshToken) { +} diff --git a/src/main/java/com/alura/aluraflixapi/domain/token/RefreshToken.java b/src/main/java/com/alura/aluraflixapi/domain/token/RefreshToken.java new file mode 100644 index 0000000..ddf2cdb --- /dev/null +++ b/src/main/java/com/alura/aluraflixapi/domain/token/RefreshToken.java @@ -0,0 +1,31 @@ +package com.alura.aluraflixapi.domain.token; + +import com.alura.aluraflixapi.domain.user.User; +import com.auth0.jwt.JWT; +import lombok.*; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.Instant; + +@Document +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Builder +public class RefreshToken { + @Id + private String id; + private String token; + private String userName; + private Instant expiryDate; + + public static RefreshToken buildRefreshTokenEntity(User user, String accessToken) { + return RefreshToken.builder() + .userName(user.getUsername()) + .token(accessToken) + .expiryDate(JWT.decode(accessToken).getExpiresAtAsInstant()) + .build(); + } +} diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/repository/RefreshTokenRepository.java b/src/main/java/com/alura/aluraflixapi/infraestructure/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..b1f60bd --- /dev/null +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/repository/RefreshTokenRepository.java @@ -0,0 +1,7 @@ +package com.alura.aluraflixapi.infraestructure.repository; + +import com.alura.aluraflixapi.domain.token.RefreshToken; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface RefreshTokenRepository extends MongoRepository { +} diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/repository/UserRepository.java b/src/main/java/com/alura/aluraflixapi/infraestructure/repository/UserRepository.java index 0082d5f..bb16824 100644 --- a/src/main/java/com/alura/aluraflixapi/infraestructure/repository/UserRepository.java +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/repository/UserRepository.java @@ -6,5 +6,5 @@ public interface UserRepository extends MongoRepository { - UserDetails findByUsername(String username); + User findByUsername(String username); } diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/security/JwtAuthenticationEntryPoint.java b/src/main/java/com/alura/aluraflixapi/infraestructure/security/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..d0a6509 --- /dev/null +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/security/JwtAuthenticationEntryPoint.java @@ -0,0 +1,29 @@ +package com.alura.aluraflixapi.infraestructure.security; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerExceptionResolver; + +/** + * This class handles unsuccessful JWT exceptions + */ +@Slf4j +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Qualifier("handlerExceptionResolver") + private final HandlerExceptionResolver resolver; + + public JwtAuthenticationEntryPoint(HandlerExceptionResolver handlerExceptionResolver) { + this.resolver = handlerExceptionResolver; + } + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { + resolver.resolveException(request, response, null, authException); + } +} diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityConfigurations.java b/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityConfigurations.java index 52275ef..83cf4b7 100644 --- a/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityConfigurations.java +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityConfigurations.java @@ -30,6 +30,7 @@ public class SecurityConfigurations { private final SecurityFilter securityFilter; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -39,13 +40,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement(managementConfigurer -> managementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(httpRequest -> httpRequest - .requestMatchers(HttpMethod.POST, "/login").permitAll() + .requestMatchers(HttpMethod.POST, "/login/**").permitAll() .requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll() //any other request has to be authenticated .anyRequest().authenticated() ) - //tell to spring to user our filter SecurityFilter.class instead their + //tell to spring to use our filter SecurityFilter.class instead their .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class) + //tell to spring to use this filter to handle any exception about JWT exception + .exceptionHandling(exceptionHandler -> + exceptionHandler.authenticationEntryPoint(jwtAuthenticationEntryPoint)) .build(); } diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityFilter.java b/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityFilter.java index 1450a75..268cfae 100644 --- a/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityFilter.java +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/security/SecurityFilter.java @@ -5,15 +5,16 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Objects; -import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + /** * Spring validate each request received only once */ @@ -21,46 +22,46 @@ @Component public class SecurityFilter extends OncePerRequestFilter { - private static final String PREFIX_LOGGING = "[SecurityFilter]"; - public static final String AUTHORIZATION = "Authorization"; - private final TokenService tokenService; - private final UserRepository userRepository; + private static final String PREFIX_LOGGING = "[SecurityFilter]"; + public static final String AUTHORIZATION = "Authorization"; + private final TokenService tokenService; + private final UserRepository userRepository; - public SecurityFilter(final TokenService tokenService, final UserRepository userRepository) { - this.tokenService = tokenService; - this.userRepository = userRepository; - } + public SecurityFilter(final TokenService tokenService, final UserRepository userRepository) { + this.tokenService = tokenService; + this.userRepository = userRepository; + } - //doFilterInternal is called for each request received - //All request to endpoint/login don´t have token in theirs headers - @Override - protected void doFilterInternal(final HttpServletRequest request, - final HttpServletResponse response, - final FilterChain filterChain) throws ServletException, IOException { + //doFilterInternal is called for each request received + //All request to endpoint/login don´t have accessToken in theirs headers + @Override + protected void doFilterInternal(final HttpServletRequest request, + final HttpServletResponse response, + final FilterChain filterChain) throws ServletException, IOException { - log.info("{} request received intercepted by Internal Filter", PREFIX_LOGGING); - final var tokenJWT = this.getTokenJWT(request); + log.info("{} request received intercepted by Internal Filter", PREFIX_LOGGING); + final var tokenJWT = this.getTokenJWT(request); - if (Objects.nonNull(tokenJWT)) { - //Retrieve user from Token JWT - final var subject = this.tokenService.getSubject(tokenJWT); - var user = this.userRepository.findByUsername(subject); + if (Objects.nonNull(tokenJWT)) { + //Retrieve user from Token JWT + final var username = this.tokenService.verifyTokenJWT(tokenJWT); + final var user = this.userRepository.findByUsername(username); - //after retrieve the user we need to tell to Spring framework to authenticate him in the context - //this is done by calling UsernamePasswordAuthenticationToken and SecurityContextHolder methods - log.info("{} Authenticating user: {} ", PREFIX_LOGGING, user.getUsername()); - var authentication = new UsernamePasswordAuthenticationToken(user, null, - user.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - log.info("{} User authenticated: {}", PREFIX_LOGGING, authentication.getPrincipal()); + //after retrieve the user we need to tell to Spring framework to authenticate him in the context + //this is done by calling UsernamePasswordAuthenticationToken and SecurityContextHolder methods + log.info("{} Authenticating user: {} ", PREFIX_LOGGING, user.getUsername()); + var authentication = new UsernamePasswordAuthenticationToken(user, null, + user.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + log.info("{} User authenticated: {}", PREFIX_LOGGING, authentication.getPrincipal()); + } + //continue the flow + filterChain.doFilter(request, response); } - //continue the flow - filterChain.doFilter(request, response); - } - private String getTokenJWT(final HttpServletRequest request) { - return Optional.ofNullable(request.getHeader(AUTHORIZATION)) - .map(token -> token.replace("Bearer", "").trim()) - .orElse(null); - } + private String getTokenJWT(final HttpServletRequest request) { + return Optional.ofNullable(request.getHeader(AUTHORIZATION)) + .map(token -> token.replace("Bearer", "").trim()) + .orElse(null); + } } diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java b/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java index b43cd43..72b81ac 100644 --- a/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java @@ -6,63 +6,81 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTCreationException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; - -import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.JWTVerificationException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + @Slf4j @Service public class TokenService { - @Value("${api.security.api-issuer}") - private String apiIssuer; + @Value("${api.security.api-issuer}") + private String apiIssuer; - @Value("${api.security.token-jwt-secret}") - public String secret; + @Value("${api.security.token-jwt-secret}") + public String secret; - public String generateTokenJWT(User user) { - try { - log.info("Generating Token JWT ..."); - return JWT.create() - .withIssuer(apiIssuer) - //owner - .withSubject(user.getUsername()) - .withClaim("id", user.getUsername()) - .withClaim("role", user.getRoles() - .stream() - .map(Roles::getRole) - .map(Enum::name) - .toList()) - // duration of JWT - .withExpiresAt(getExpireDate()) - .sign(Algorithm.HMAC256(secret)); + public String generateTokenJwt(User user) { + try { + log.info("Generating Token JWT ..."); + return JWT.create() + .withIssuer(apiIssuer) + //owner + .withSubject(user.getUsername()) + .withClaim("id", user.getUsername()) + .withClaim("role", user.getRoles() + .stream() + .map(Roles::getRole) + .map(Enum::name) + .toList()) + // duration of JWT + .withExpiresAt(getExpireDate()) + .sign(Algorithm.HMAC256(secret)); - } catch (NullPointerException exception) { - throw new JWTCreationException("Error to create JWT token", exception.getCause()); + } catch (NullPointerException exception) { + throw new JWTCreationException("Error to create JWT accessToken", exception.getCause()); + } } - } - public String getSubject(String tokenJWT) { - try { - var algorithm = Algorithm.HMAC256(secret); - return JWT.require(algorithm) - .withIssuer(apiIssuer) - .build() - .verify(tokenJWT) - .getSubject(); - } catch (JWTDecodeException ex) { - throw new JWTDecodeException("Error verifying JWT Token", ex.getCause()); + public String verifyTokenJWT(String tokenJWT) { + log.info("Verifying JWT accessToken ..."); + try { + return JWT.require(Algorithm.HMAC256(secret)) + .withIssuer(apiIssuer) + .build() + .verify(tokenJWT) + .getSubject(); + } catch (JWTVerificationException ex) { + throw new JWTVerificationException("Error verifying JWT Token", ex.getCause()); + } } - } - //Create expire date of token, in this case is the current hour plus 10 minutes - private Instant getExpireDate() { - return LocalDateTime.now().plusMinutes(10).toInstant(ZoneOffset.UTC); + public void isRefreshTokenExpired(String tokenJWT) { + if (JWT.decode(tokenJWT).getExpiresAt().toInstant().compareTo(Instant.now()) < 0) { + throw new RuntimeException("Refresh token expired, please login again"); + } + } - } + //Create expire date of accessToken, in this case is the current hour plus 10 minutes + private Instant getExpireDate() { + return Instant.now().plus(1, ChronoUnit.MINUTES); + + } + + public String generateRefreshToken(String subject) { + try { + log.info("Generating Refresh Token JWT ..."); + return JWT.create() + .withIssuer(apiIssuer) + .withExpiresAt(Instant.now().plus(5, ChronoUnit.MINUTES)) + .withSubject(subject) + .sign(Algorithm.HMAC256(secret)); + } catch (RuntimeException exception) { + throw new JWTCreationException("Error to create JWT refresh accessToken", exception.getCause()); + } + } } diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/security/dto/TokenJwtDto.java b/src/main/java/com/alura/aluraflixapi/infraestructure/security/dto/TokenJwtDto.java index 68fb050..dacde0a 100644 --- a/src/main/java/com/alura/aluraflixapi/infraestructure/security/dto/TokenJwtDto.java +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/security/dto/TokenJwtDto.java @@ -1,5 +1,5 @@ package com.alura.aluraflixapi.infraestructure.security.dto; -public record TokenJwtDto(String token) { +public record TokenJwtDto(String accessToken, String refreshToken) { } diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/service/token/RefreshTokenService.java b/src/main/java/com/alura/aluraflixapi/infraestructure/service/token/RefreshTokenService.java new file mode 100644 index 0000000..5b83991 --- /dev/null +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/service/token/RefreshTokenService.java @@ -0,0 +1,37 @@ +package com.alura.aluraflixapi.infraestructure.service.token; + +import com.alura.aluraflixapi.domain.token.RefreshToken; +import com.alura.aluraflixapi.infraestructure.repository.RefreshTokenRepository; +import com.alura.aluraflixapi.infraestructure.repository.UserRepository; +import com.alura.aluraflixapi.infraestructure.security.TokenService; +import com.alura.aluraflixapi.infraestructure.security.dto.TokenJwtDto; +import com.auth0.jwt.JWT; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class RefreshTokenService { + + private final TokenService tokenService; + private final RefreshTokenRepository refreshTokenRepository; + private final UserRepository userRepository; + + public RefreshTokenService(RefreshTokenRepository refreshTokenRepository, TokenService tokenService, UserRepository userRepository) { + this.refreshTokenRepository = refreshTokenRepository; + this.tokenService = tokenService; + this.userRepository = userRepository; + } + + public TokenJwtDto refreshAccessToken(final String refreshToken) { + log.info("[RefreshTokenService] - Generating new accessToken"); + //valid if refresh token is expired + tokenService.isRefreshTokenExpired(refreshToken); + final var user = userRepository.findByUsername(JWT.decode(refreshToken).getSubject()); + final var accessToken = tokenService.generateTokenJwt(user); + final var refreshTokenEntity = RefreshToken.buildRefreshTokenEntity(user, accessToken); + refreshTokenRepository.save(refreshTokenEntity); + return new TokenJwtDto(accessToken, refreshToken); + } + +} diff --git a/src/test/java/com/alura/aluraflixapi/controller/AuthenticationControllerTest.java b/src/test/java/com/alura/aluraflixapi/controller/AuthenticationControllerTest.java index c5db0a3..0b4bb3f 100644 --- a/src/test/java/com/alura/aluraflixapi/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/alura/aluraflixapi/controller/AuthenticationControllerTest.java @@ -6,6 +6,7 @@ import com.alura.aluraflixapi.domain.user.roles.Roles; import com.alura.aluraflixapi.domain.user.roles.RolesEnum; import com.alura.aluraflixapi.infraestructure.security.TokenService; +import com.alura.aluraflixapi.infraestructure.service.token.RefreshTokenService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; @@ -33,6 +34,8 @@ class AuthenticationControllerTest { @MockBean private TokenService tokenService; + @MockBean + private RefreshTokenService refreshTokenService; @Test void login_test() { @@ -42,7 +45,7 @@ void login_test() { Set.of(Roles.builder().id("1").role(RolesEnum.ROLE_ADMIN).build())); final var testingAuthenticationToken = new TestingAuthenticationToken(userAdministrator, userAdministrator); - when(this.tokenService.generateTokenJWT(Mockito.any())).thenReturn(tokenJwtFake); + when(this.tokenService.generateTokenJwt(Mockito.any())).thenReturn(tokenJwtFake); when(this.manager.authenticate(Mockito.any())).thenReturn(testingAuthenticationToken); //When @@ -51,6 +54,6 @@ void login_test() { //Then assertThat(userAuthenticated).isNotNull(); assertThat(userAuthenticated.getBody()).isNotNull(); - assertThat(userAuthenticated.getBody().token()).isEqualTo(tokenJwtFake); + assertThat(userAuthenticated.getBody().accessToken()).isEqualTo(tokenJwtFake); } } \ No newline at end of file diff --git a/src/test/java/com/alura/aluraflixapi/infraestructure/security/TokenServiceTest.java b/src/test/java/com/alura/aluraflixapi/infraestructure/security/TokenServiceTest.java index 47174a8..8d7c298 100644 --- a/src/test/java/com/alura/aluraflixapi/infraestructure/security/TokenServiceTest.java +++ b/src/test/java/com/alura/aluraflixapi/infraestructure/security/TokenServiceTest.java @@ -4,13 +4,12 @@ import com.alura.aluraflixapi.domain.user.roles.Roles; import com.alura.aluraflixapi.domain.user.roles.RolesEnum; import com.auth0.jwt.exceptions.JWTCreationException; -import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.JWTVerificationException; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.mock.mockito.SpyBean; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.util.Set; @@ -26,7 +25,7 @@ class TokenServiceTest { @Test @DisplayName("Should return a TokenJWT") - void generateTokenJWT_test() { + void generateTokenJwt_test() { //Given final var user = User.builder().id(UUID.randomUUID().toString()) .username("admin@aluraflix.com") @@ -37,7 +36,7 @@ void generateTokenJWT_test() { .build())) .build(); //When - final var tokenJWT = this.tokenService.generateTokenJWT(user); + final var tokenJWT = this.tokenService.generateTokenJwt(user); //Then assertThat(tokenJWT) .isNotBlank() @@ -46,17 +45,17 @@ void generateTokenJWT_test() { @Test @DisplayName("Should return a JWTCreationException when is generating TokenJWT and some field is null") - void generateTokenJWT_KO_test() { + void generateTokenJwt_KO_test() { //Then Assertions.assertThatExceptionOfType(JWTCreationException.class) - .isThrownBy(() -> this.tokenService.generateTokenJWT(null)) - .withMessageContaining("Error to create JWT token"); + .isThrownBy(() -> this.tokenService.generateTokenJwt(null)) + .withMessageContaining("Error to create JWT accessToken"); } @Test @DisplayName("Should retrieve User Principal from TokenJWT passed") - void getSubject_test() { + void verifyTokenJWT_test() { //Given final var user = User.builder().id(UUID.randomUUID().toString()) .username("admin@aluraflix.com") @@ -66,22 +65,22 @@ void getSubject_test() { .role(RolesEnum.ROLE_ADMIN) .build())) .build(); - final var tokenJWT = this.tokenService.generateTokenJWT(user); + final var tokenJWT = this.tokenService.generateTokenJwt(user); //When - final var userPrincipal = this.tokenService.getSubject(tokenJWT); + final var userPrincipal = this.tokenService.verifyTokenJWT(tokenJWT); //Then + assertThat(userPrincipal) - .isNotBlank() .isNotNull() .isEqualTo("admin@aluraflix.com"); } @Test @DisplayName("Should return a JWTDecodeException when is verifying TokenJWT and is null") - void getSubject_KO_test() { + void verifyTokenJWT_KO_test() { //Then - Assertions.assertThatExceptionOfType(JWTDecodeException.class) - .isThrownBy(() -> this.tokenService.getSubject(null)) + Assertions.assertThatExceptionOfType(JWTVerificationException.class) + .isThrownBy(() -> this.tokenService.verifyTokenJWT(null)) .withMessageContaining("Error verifying JWT Token"); } } \ No newline at end of file From 5fefb42627b061ffd2356a4f3b9a0f8626d3a307 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 01:27:43 +0000 Subject: [PATCH 02/10] Bump mapstruct.version from 1.5.5.Final to 1.6.2 Bumps `mapstruct.version` from 1.5.5.Final to 1.6.2. Updates `org.mapstruct:mapstruct` from 1.5.5.Final to 1.6.2 - [Release notes](https://github.com/mapstruct/mapstruct/releases) - [Commits](https://github.com/mapstruct/mapstruct/compare/1.5.5.Final...1.6.2) Updates `org.mapstruct:mapstruct-processor` from 1.5.5.Final to 1.6.2 - [Release notes](https://github.com/mapstruct/mapstruct/releases) - [Commits](https://github.com/mapstruct/mapstruct/compare/1.5.5.Final...1.6.2) --- updated-dependencies: - dependency-name: org.mapstruct:mapstruct dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.mapstruct:mapstruct-processor dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 65d0c8a..5ed74f5 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 21 - 1.5.5.Final + 1.6.2 3.13.0 2.5.0 4.4.0 From cd8cfc387e1c73bf8c65cae18abbf118ae9257f2 Mon Sep 17 00:00:00 2001 From: Lauro Correia Silveira Date: Sun, 24 Nov 2024 22:10:47 +0100 Subject: [PATCH 03/10] Removed URL connection with sensitivity data --- src/test/resources/application-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 517acf5..4b622cf 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -8,4 +8,4 @@ spring: mongodb: database: alura-flix-test # always commit the uri like this: ${DATABASE_TEST} - uri: ${DATABASE_TEST:mongodb+srv://laurosilveira:283TlfZiOW4nNOnN@alura-flix-test.qixe2kd.mongodb.net/?retryWrites=true&w=majority&appName=alura-flix-test} \ No newline at end of file + uri: ${DATABASE_TEST} From cd105973de1f0832a76ce7afc5c371c65cfcd194 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:26:53 +0100 Subject: [PATCH 04/10] Bump org.springframework.boot:spring-boot-starter-parent (#128) Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.3.4 to 3.4.0. - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v3.3.4...v3.4.0) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-starter-parent dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lauro Correia Silveira --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5ed74f5..9fdbd43 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.4 + 3.4.0 From d98f727b7ba6ff06a22847439cd32fb5a94f471a Mon Sep 17 00:00:00 2001 From: Lauro Correia Silveira Date: Sun, 24 Nov 2024 22:46:19 +0100 Subject: [PATCH 05/10] Added endpoint refresh token (#129) * Added RefreshToken and AuthenticationEntryPoint - Implemented RefreshToken endpoint - Implemented AuthenticationEntryPoint to redirect JWT exceptions - Adjusted tests * Added endpoint refresh token --- .../exception/JwtRefreshTokenExpiredException.java | 7 +++++++ .../infraestructure/security/TokenService.java | 1 + .../infraestructure/repository/VideoRepositoryTest.java | 1 - src/test/resources/application-test.yml | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/alura/aluraflixapi/infraestructure/exception/JwtRefreshTokenExpiredException.java diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/exception/JwtRefreshTokenExpiredException.java b/src/main/java/com/alura/aluraflixapi/infraestructure/exception/JwtRefreshTokenExpiredException.java new file mode 100644 index 0000000..f297d88 --- /dev/null +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/exception/JwtRefreshTokenExpiredException.java @@ -0,0 +1,7 @@ +package com.alura.aluraflixapi.infraestructure.exception; + +public class JwtRefreshTokenExpiredException extends RuntimeException { + public JwtRefreshTokenExpiredException(String message) { + super(message); + } +} diff --git a/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java b/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java index 72b81ac..6c30a32 100644 --- a/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java +++ b/src/main/java/com/alura/aluraflixapi/infraestructure/security/TokenService.java @@ -3,6 +3,7 @@ import com.alura.aluraflixapi.domain.user.User; import com.alura.aluraflixapi.domain.user.roles.Roles; +import com.alura.aluraflixapi.infraestructure.exception.JwtRefreshTokenExpiredException; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTCreationException; diff --git a/src/test/java/com/alura/aluraflixapi/infraestructure/repository/VideoRepositoryTest.java b/src/test/java/com/alura/aluraflixapi/infraestructure/repository/VideoRepositoryTest.java index d1c7f17..523e9e3 100644 --- a/src/test/java/com/alura/aluraflixapi/infraestructure/repository/VideoRepositoryTest.java +++ b/src/test/java/com/alura/aluraflixapi/infraestructure/repository/VideoRepositoryTest.java @@ -26,7 +26,6 @@ class VideoRepositoryTest extends ParseJson { @AfterEach void after() { - //TODO: search another way to delete all data without call this methods. videoRepository.deleteAll(); categoryRepository.deleteAll(); } diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 4b622cf..6a6c4a6 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -8,4 +8,4 @@ spring: mongodb: database: alura-flix-test # always commit the uri like this: ${DATABASE_TEST} - uri: ${DATABASE_TEST} + uri: ${DATABASE_TEST} \ No newline at end of file From 04ad04b4a9d4027b18ca7daebc6798ed8380af61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:51:35 +0100 Subject: [PATCH 06/10] Bump mapstruct.version from 1.6.2 to 1.6.3 (#127) Bumps `mapstruct.version` from 1.6.2 to 1.6.3. Updates `org.mapstruct:mapstruct` from 1.6.2 to 1.6.3 - [Release notes](https://github.com/mapstruct/mapstruct/releases) - [Commits](https://github.com/mapstruct/mapstruct/compare/1.6.2...1.6.3) Updates `org.mapstruct:mapstruct-processor` from 1.6.2 to 1.6.3 - [Release notes](https://github.com/mapstruct/mapstruct/releases) - [Commits](https://github.com/mapstruct/mapstruct/compare/1.6.2...1.6.3) --- updated-dependencies: - dependency-name: org.mapstruct:mapstruct dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.mapstruct:mapstruct-processor dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lauro Correia Silveira --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fdbd43..394cb04 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 21 - 1.6.2 + 1.6.3 3.13.0 2.5.0 4.4.0 From a9f579fb09bcdde76176c66d67b90037645f7a22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:56:38 +0100 Subject: [PATCH 07/10] Bump org.springdoc:springdoc-openapi-starter-webmvc-ui (#122) Bumps [org.springdoc:springdoc-openapi-starter-webmvc-ui](https://github.com/springdoc/springdoc-openapi) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/springdoc/springdoc-openapi/releases) - [Changelog](https://github.com/springdoc/springdoc-openapi/blob/main/CHANGELOG.md) - [Commits](https://github.com/springdoc/springdoc-openapi/compare/v2.5.0...v2.6.0) --- updated-dependencies: - dependency-name: org.springdoc:springdoc-openapi-starter-webmvc-ui dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lauro Correia Silveira --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 394cb04..c21a7a9 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 21 1.6.3 3.13.0 - 2.5.0 + 2.6.0 4.4.0 0.8.12 2023.0.3 From 7d6cbeb846281c9b5e0ee43b6a3fb27f1edd6916 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:32:35 +0000 Subject: [PATCH 08/10] Bump org.springdoc:springdoc-openapi-starter-webmvc-ui Bumps [org.springdoc:springdoc-openapi-starter-webmvc-ui](https://github.com/springdoc/springdoc-openapi) from 2.6.0 to 2.7.0. - [Release notes](https://github.com/springdoc/springdoc-openapi/releases) - [Changelog](https://github.com/springdoc/springdoc-openapi/blob/main/CHANGELOG.md) - [Commits](https://github.com/springdoc/springdoc-openapi/compare/v2.6.0...v2.7.0) --- updated-dependencies: - dependency-name: org.springdoc:springdoc-openapi-starter-webmvc-ui dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c21a7a9..7ccf6db 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 21 1.6.3 3.13.0 - 2.6.0 + 2.7.0 4.4.0 0.8.12 2023.0.3 From 8454d3aeca202d49cdb0ad03fa330b03d41c4978 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 01:10:11 +0000 Subject: [PATCH 09/10] Bump org.springframework.cloud:spring-cloud-dependencies Bumps [org.springframework.cloud:spring-cloud-dependencies](https://github.com/spring-cloud/spring-cloud-release) from 2023.0.3 to 2023.0.4. - [Release notes](https://github.com/spring-cloud/spring-cloud-release/releases) - [Commits](https://github.com/spring-cloud/spring-cloud-release/compare/v2023.0.3...v2023.0.4) --- updated-dependencies: - dependency-name: org.springframework.cloud:spring-cloud-dependencies dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c21a7a9..1c61dfa 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 2.6.0 4.4.0 0.8.12 - 2023.0.3 + 2023.0.4 From fd0dc9f724629f0d58fdd8d757ecc54ea54e3aa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 01:41:09 +0000 Subject: [PATCH 10/10] Bump org.springframework.cloud:spring-cloud-dependencies Bumps [org.springframework.cloud:spring-cloud-dependencies](https://github.com/spring-cloud/spring-cloud-release) from 2023.0.4 to 2024.0.0. - [Release notes](https://github.com/spring-cloud/spring-cloud-release/releases) - [Commits](https://github.com/spring-cloud/spring-cloud-release/compare/v2023.0.4...v2024.0.0) --- updated-dependencies: - dependency-name: org.springframework.cloud:spring-cloud-dependencies dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4265a92..e0b00ac 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 2.7.0 4.4.0 0.8.12 - 2023.0.4 + 2024.0.0