From a47e93cee56faa3d45a52e6c81eba10514da353e Mon Sep 17 00:00:00 2001 From: DarkMatter015 Date: Sat, 2 May 2026 23:45:25 -0300 Subject: [PATCH] feat: Created support and auth logic to admins authentication --- .../infra/security/JwtProperties.java | 1 + .../infra/security/config/WebSecurity.java | 50 ++++++++++++++++--- .../filter/JWTAuthenticationFilter.java | 40 ++++++++++----- src/main/resources/application-dev.properties | 6 +-- 4 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/JwtProperties.java b/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/JwtProperties.java index e02b336..85383f4 100644 --- a/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/JwtProperties.java +++ b/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/JwtProperties.java @@ -17,4 +17,5 @@ public class JwtProperties { public static final String HEADER_STRING = "Authorization"; // header que será passado ao server com o token + public static final String HEADER_ORIGIN = "X-App-Source"; // header que será passado a origem para validar autenticação (user or admin) } diff --git a/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/config/WebSecurity.java b/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/config/WebSecurity.java index f83df13..bee3def 100644 --- a/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/config/WebSecurity.java +++ b/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/config/WebSecurity.java @@ -7,6 +7,7 @@ import br.edu.utfpr.pb.ecommerce.server_ecommerce.infra.security.handler.CustomAuthenticationEntryPoint; import br.edu.utfpr.pb.ecommerce.server_ecommerce.infra.security.handler.CustomAuthenticationFailureHandler; import br.edu.utfpr.pb.ecommerce.server_ecommerce.service.AuthService; +import br.edu.utfpr.pb.ecommerce.server_ecommerce.service.TranslationService; import br.edu.utfpr.pb.ecommerce.server_ecommerce.service.impl.alertProduct.IAlertProduct.IAlertProductRequestService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; @@ -27,6 +28,7 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.servlet.LocaleResolver; import java.util.Arrays; import java.util.List; @@ -47,6 +49,9 @@ public class WebSecurity { private final CustomAccessDeniedHandler accessDeniedHandler; private final CustomAuthenticationFailureHandler failureHandler; + private final TranslationService translationService; + private final LocaleResolver localeResolver; + @Bean @SneakyThrows public SecurityFilterChain filterChain(HttpSecurity http) { @@ -86,13 +91,36 @@ public SecurityFilterChain filterChain(HttpSecurity http) { authorize // ROTAS PÚBLICAS (permitAll) - .requestMatchers(HttpMethod.POST, "/users", "/shipment/products", "/auth/forgot-password", "/auth/reset-password", "/alerts").permitAll() - .requestMatchers(HttpMethod.GET, "/products/**", "/categories/**", "/payments/**", "/cep/validate/", "/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/auth/validate-reset-token", "/api/health").permitAll() - .requestMatchers("/error/**").permitAll() + .requestMatchers(HttpMethod.POST, + "/users", + "/shipment/products", + "/auth/forgot-password", + "/auth/reset-password", + "/alerts") + .permitAll() + + .requestMatchers(HttpMethod.GET, + "/products/**", + "/categories/**", + "/payments/**", + "/cep/validate/", + "/v3/api-docs/**", + "/swagger-ui.html", + "/swagger-ui/**", + "/auth/validate-reset-token", + "/api/health") + .permitAll() + + .requestMatchers("/error/**") + .permitAll() // ROTAS DE ADMIN // Apenas ADMIN pode gerenciar (criar, editar, deletar) produtos, categorias e pagamentos - .requestMatchers("/products/**", "/categories/**", "/payments/**").hasAnyAuthority("ADMIN"); + .requestMatchers( + "/products/**", + "/categories/**", + "/payments/**") + .hasAnyAuthority("ADMIN"); // ROTAS AUTENTICADAS (USER ou ADMIN) // Qualquer usuário autenticado pode acessar as demais rotas @@ -102,7 +130,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) { ); http.authenticationManager(authenticationManager) //Filtro da Autenticação - sobrescreve o método padrão do Spring Security para Autenticação. - .addFilter(new JWTAuthenticationFilter(authenticationManager, authService, objectMapper, jwtProperties, failureHandler, alertProductRequestService)) + .addFilter(new JWTAuthenticationFilter( + authenticationManager, + authService, + objectMapper, + jwtProperties, + failureHandler, + alertProductRequestService, + translationService, + localeResolver + )) //Filtro da Autorização - - sobrescreve o método padrão do Spring Security para Autorização. .addFilter(new JWTAuthorizationFilter(authenticationManager, jwtProperties)) //Como será criada uma API REST e todas as requisições que necessitam de autenticação/autorização serão realizadas com o envio do token JWT do usuário, não será necessário fazer controle de sessão no *back-end*. @@ -126,7 +163,8 @@ public CorsConfigurationSource corsConfigurationSource() { configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); // Lista dos Headers autorizados, o Authorization será o header que iremos utilizar para transferir o Token - configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "Accept", "Origin")); + configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "Accept", "Origin", "X-App-Source")); + configuration.setExposedHeaders(List.of("Authorization")); configuration.setAllowCredentials(false); diff --git a/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/filter/JWTAuthenticationFilter.java b/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/filter/JWTAuthenticationFilter.java index ed52c7a..4f08956 100644 --- a/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/filter/JWTAuthenticationFilter.java +++ b/src/main/java/br/edu/utfpr/pb/ecommerce/server_ecommerce/infra/security/filter/JWTAuthenticationFilter.java @@ -7,6 +7,7 @@ import br.edu.utfpr.pb.ecommerce.server_ecommerce.infra.security.exception.JsonAuthenticationException; import br.edu.utfpr.pb.ecommerce.server_ecommerce.model.User; import br.edu.utfpr.pb.ecommerce.server_ecommerce.service.AuthService; +import br.edu.utfpr.pb.ecommerce.server_ecommerce.service.TranslationService; import br.edu.utfpr.pb.ecommerce.server_ecommerce.service.impl.alertProduct.IAlertProduct.IAlertProductRequestService; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; @@ -16,22 +17,25 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.servlet.LocaleResolver; import java.io.IOException; import java.time.Instant; -import java.util.ArrayList; import java.util.stream.Collectors; public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthService authService; private final ObjectMapper objectMapper; + private final TranslationService translationService; + private final LocaleResolver localeResolver; private final JwtProperties jwtProperties; private final IAlertProductRequestService alertProductRequestService; @@ -39,11 +43,16 @@ public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthService authService, ObjectMapper objectMapper, JwtProperties jwtProperties, - AuthenticationFailureHandler authenticationFailureHandler, IAlertProductRequestService alertProductRequestService) { + AuthenticationFailureHandler authenticationFailureHandler, + IAlertProductRequestService alertProductRequestService, + TranslationService translationService, + LocaleResolver localeResolver) { super(authenticationManager); this.authService = authService; this.objectMapper = objectMapper; + this.translationService = translationService; this.jwtProperties = jwtProperties; + this.localeResolver = localeResolver; this.alertProductRequestService = alertProductRequestService; this.setAuthenticationFailureHandler(authenticationFailureHandler); @@ -53,15 +62,24 @@ public JWTAuthenticationFilter(AuthenticationManager authenticationManager, @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { + String appSource = request.getHeader(JwtProperties.HEADER_ORIGIN); LoginRequestDTO credentials = objectMapper.readValue(request.getInputStream(), LoginRequestDTO.class); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + credentials.getEmail(), + credentials.getPassword()); - return super.getAuthenticationManager().authenticate( - new UsernamePasswordAuthenticationToken( - credentials.getEmail(), - credentials.getPassword(), - new ArrayList<>() - ) - ); + Authentication authResult = super.getAuthenticationManager().authenticate(authToken); + + if ("admin".equalsIgnoreCase(appSource)) { + boolean isAdmin = authResult.getAuthorities().stream() + .anyMatch(role -> role.getAuthority().equals("ADMIN")); + + if (!isAdmin) { + throw new BadCredentialsException(translationService.getMessageLocale("access.denied", localeResolver.resolveLocale(request))); + } + } + + return authResult; } catch (IOException e) { throw new JsonAuthenticationException("Invalid data format for login request", e); } @@ -77,12 +95,8 @@ protected void successfulAuthentication(HttpServletRequest request, alertProductRequestService.syncOrphanAlerts(user); - // o método create() da classe JWT é utilizado para criação de um novo token JWT String token = JWT.create() - // o objeto authResult possui os dados do usuário autenticado, nesse caso o método getId() retorna o id do usuário foi autenticado no método attemptAuthentication. - // mudei para getId() por ser um atributo imutável .withSubject(user.getId().toString()) - //a data de validade do token é a data atual mais o valor armazenado na constante EXPIRATION_TIME, nesse caso 1 dia .withExpiresAt( getExpirationDate() ) diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index b9c5a88..f14c613 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,7 +1,7 @@ # H2 DATABASE -spring.datasource.generate-unique-name=false -spring.h2.console.enabled=true -spring.h2.console.path=/h2-console +#spring.datasource.generate-unique-name=false +#spring.h2.console.enabled=true +#spring.h2.console.path=/h2-console # PostgresSQL DATABASE: spring.datasource.url=jdbc:postgresql://localhost:5432/riffhouse