From 225cf6cbabf04cf7db3c661a63d9235ddc115f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 15:14:16 -0300 Subject: [PATCH 1/8] =?UTF-8?q?feat(exception):=20cria=20a=20exce=C3=A7?= =?UTF-8?q?=C3=A3o=20InvalidTokenException=20para=20tratamento=20de=20toke?= =?UTF-8?q?ns=20inv=C3=A1lidos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/linktreeclone/exception/InvalidTokenException.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java diff --git a/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java new file mode 100644 index 0000000..b64c646 --- /dev/null +++ b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java @@ -0,0 +1,7 @@ +package br.com.linktreeclone.exception; + +public class InvalidTokenException extends RuntimeException { + public InvalidTokenException(String message) { + super(message); + } +} From fd8d7e7e108c39d13e6dbd4809281d72fdfb592c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 15:14:21 -0300 Subject: [PATCH 2/8] =?UTF-8?q?refactor(exception):=20ajusta=20a=20formata?= =?UTF-8?q?=C3=A7=C3=A3o=20da=20classe=20InvalidTokenException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../linktreeclone/exception/InvalidTokenException.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java index b64c646..60c8921 100644 --- a/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java +++ b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/InvalidTokenException.java @@ -1,7 +1,8 @@ package br.com.linktreeclone.exception; -public class InvalidTokenException extends RuntimeException { - public InvalidTokenException(String message) { - super(message); - } +public class InvalidTokenException extends RuntimeException +{ + public InvalidTokenException(String message) { + super(message); + } } From d172bc70a30d151e19c9443bd3a8ed47448e5d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 15:43:14 -0300 Subject: [PATCH 3/8] =?UTF-8?q?feat(exception):=20adiciona=20tratamento=20?= =?UTF-8?q?para=20exce=C3=A7=C3=A3o=20InvalidTokenException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/RestExceptionHandler.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java index 12c69e1..8782ca9 100644 --- a/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java +++ b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java @@ -38,4 +38,16 @@ public ResponseEntity handleUnauthorizedException(UnauthorizedExc return new ResponseEntity<>(errorResponse, HttpStatus.FORBIDDEN); } + + @ExceptionHandler(InvalidTokenException.class) + public ResponseEntity handleInvalidTokenException(InvalidTokenException ex, WebRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.FORBIDDEN.value(), + "Forbidden", + ex.getMessage(), + request.getDescription(false).replace("uri=", "") + ); + return new ResponseEntity<>(errorResponse, HttpStatus.FORBIDDEN); + } } From 88f2b7e25135e3a70efbff616a45c5815258a4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 15:43:19 -0300 Subject: [PATCH 4/8] =?UTF-8?q?feat(exception):=20substitui=20RuntimeExcep?= =?UTF-8?q?tion=20por=20InvalidTokenException=20no=20m=C3=A9todo=20validat?= =?UTF-8?q?eToken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/br/com/linktreeclone/security/TokenService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java b/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java index c4193f2..0a59aec 100644 --- a/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java +++ b/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java @@ -1,6 +1,7 @@ package br.com.linktreeclone.security; import br.com.linktreeclone.entity.User; +import br.com.linktreeclone.exception.InvalidTokenException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; @@ -53,7 +54,7 @@ public String validateToken(String token) .getSubject(); } catch (Exception e) { - throw new RuntimeException("Token JWT expirado ou inválido"); + throw new InvalidTokenException("Token JWT expirado ou inválido"); } } } \ No newline at end of file From 24be1bde924f1a371ac579f21303413fbbc15954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 15:43:25 -0300 Subject: [PATCH 5/8] fix(api): atualiza a URL base do cliente Axios para o novo endpoint da API --- linktreeclone-frontend/src/api/axiosConfig.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/linktreeclone-frontend/src/api/axiosConfig.js b/linktreeclone-frontend/src/api/axiosConfig.js index 9e707df..2a967aa 100644 --- a/linktreeclone-frontend/src/api/axiosConfig.js +++ b/linktreeclone-frontend/src/api/axiosConfig.js @@ -1,7 +1,7 @@ import axios from 'axios'; const apiClient = axios.create({ - baseURL: 'https://linktree-clone-api-238899108893.southamerica-east1.run.app', + baseURL: 'https://linktree-clone-api-faw777jkuq-rj.a.run.app', }); @@ -15,4 +15,18 @@ apiClient.interceptors.request.use((config) => { return Promise.reject(error); }); +apiClient.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + if (error.response && (error.response.status === 401 || error.response.status === 403)) { + localStorage.removeItem('authToken'); + window.location.href = '/login'; + console.log('Sessão expirada. Redirecionando para o login.'); + } + return Promise.reject(error); + } +); + export default apiClient; \ No newline at end of file From e1f56045e6f5a03f88a8189a27139737ff6daaff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 17:47:20 -0300 Subject: [PATCH 6/8] =?UTF-8?q?feat(exception):=20adiciona=20tratamento=20?= =?UTF-8?q?para=20exce=C3=A7=C3=A3o=20InvalidTokenException=20no=20RestExc?= =?UTF-8?q?eptionHandler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/RestExceptionHandler.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java index 8782ca9..ae66aab 100644 --- a/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java +++ b/linktreeclone-backend/src/main/java/br/com/linktreeclone/exception/RestExceptionHandler.java @@ -13,6 +13,18 @@ public class RestExceptionHandler { public record ErrorResponse(LocalDateTime timestamp, int status, String error, String message, String path) {} + @ExceptionHandler(InvalidTokenException.class) + public ResponseEntity handleInvalidTokenException(InvalidTokenException ex, WebRequest request) { + ErrorResponse errorResponse = new ErrorResponse( + LocalDateTime.now(), + HttpStatus.FORBIDDEN.value(), + "Forbidden", + ex.getMessage(), + request.getDescription(false).replace("uri=", "") + ); + return new ResponseEntity<>(errorResponse, HttpStatus.FORBIDDEN); + } + @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) { ErrorResponse errorResponse = new ErrorResponse( @@ -38,16 +50,4 @@ public ResponseEntity handleUnauthorizedException(UnauthorizedExc return new ResponseEntity<>(errorResponse, HttpStatus.FORBIDDEN); } - - @ExceptionHandler(InvalidTokenException.class) - public ResponseEntity handleInvalidTokenException(InvalidTokenException ex, WebRequest request) { - ErrorResponse errorResponse = new ErrorResponse( - LocalDateTime.now(), - HttpStatus.FORBIDDEN.value(), - "Forbidden", - ex.getMessage(), - request.getDescription(false).replace("uri=", "") - ); - return new ResponseEntity<>(errorResponse, HttpStatus.FORBIDDEN); - } } From c690cd7ab4a3b0170744b40c4d9ab3eeda2a5b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 17:47:27 -0300 Subject: [PATCH 7/8] =?UTF-8?q?feat(security):=20adiciona=20tratamento=20d?= =?UTF-8?q?e=20exce=C3=A7=C3=A3o=20e=20autentica=C3=A7=C3=A3o=20no=20filtr?= =?UTF-8?q?o=20de=20seguran=C3=A7a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/SecurityFilter.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/SecurityFilter.java b/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/SecurityFilter.java index 8800880..cb9cf41 100644 --- a/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/SecurityFilter.java +++ b/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/SecurityFilter.java @@ -1,16 +1,20 @@ package br.com.linktreeclone.security; import br.com.linktreeclone.repository.UserRepository; +import br.com.linktreeclone.exception.InvalidTokenException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; 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.UsernameNotFoundException; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.HandlerExceptionResolver; import java.io.IOException; import java.util.UUID; @@ -24,24 +28,37 @@ public class SecurityFilter extends OncePerRequestFilter @Autowired private UserRepository userRepository; + @Autowired + @Qualifier("handlerExceptionResolver") + private HandlerExceptionResolver resolver; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = this.recoverToken(request); - if (token != null) { - String subject = tokenService.validateToken(token); - UUID userId = UUID.fromString(subject); + try + { + if (token != null) + { + String subject = tokenService.validateToken(token); + UUID userId = UUID.fromString(subject); + + UserDetails user = userRepository.findById(userId) + .orElseThrow(() -> new UsernameNotFoundException("Usuário não encontrado com o ID: " + userId)); - UserDetails user = userRepository.findById(userId) - .orElseThrow(() -> new RuntimeException("User Not Found")); + var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + filterChain.doFilter(request, response); - var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); } - filterChain.doFilter(request, response); + catch (Exception e) + { + resolver.resolveException(request, response, null, e); + } } private String recoverToken(HttpServletRequest request) From 814731c5609421ae60a63dcba6c1867e75a344ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3?= Date: Sun, 21 Sep 2025 17:47:34 -0300 Subject: [PATCH 8/8] =?UTF-8?q?feat(exception):=20aprimora=20o=20tratament?= =?UTF-8?q?o=20de=20exce=C3=A7=C3=B5es=20no=20m=C3=A9todo=20validateToken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../linktreeclone/security/TokenService.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java b/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java index 0a59aec..5119105 100644 --- a/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java +++ b/linktreeclone-backend/src/main/java/br/com/linktreeclone/security/TokenService.java @@ -8,6 +8,12 @@ import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.SignatureException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; @@ -24,6 +30,8 @@ public class TokenService private SecretKey key; + private static final Logger logger = LoggerFactory.getLogger(TokenService.class); + @PostConstruct public void init() { this.key = Keys.hmacShaKeyFor(jwtSecretString.getBytes(StandardCharsets.UTF_8)); @@ -53,8 +61,25 @@ public String validateToken(String token) .getBody() .getSubject(); } - catch (Exception e) { - throw new InvalidTokenException("Token JWT expirado ou inválido"); + catch (ExpiredJwtException ex) { + logger.error("Token JWT expirou: {}", ex.getMessage()); + throw new InvalidTokenException("Sua sessão expirou. Por favor, faça login novamente."); + } + catch (SignatureException ex) { + logger.error("Assinatura do JWT é inválida: {}", ex.getMessage()); + throw new InvalidTokenException("Assinatura do token é inválida."); + } + catch (MalformedJwtException ex) { + logger.error("Token JWT malformado: {}", ex.getMessage()); + throw new InvalidTokenException("Token malformado."); + } + catch (UnsupportedJwtException ex) { + logger.error("Token JWT não é suportado: {}", ex.getMessage()); + throw new InvalidTokenException("Este tipo de token não é suportado."); + } + catch (IllegalArgumentException ex) { + logger.error("O conteúdo do JWT está vazio: {}", ex.getMessage()); + throw new InvalidTokenException("Token inválido ou vazio."); } } } \ No newline at end of file