From 0f60b34ebe5adff1d6c7c6cbe73de5c3565d549d Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Thu, 11 Sep 2025 21:19:13 -0500 Subject: [PATCH 01/14] configuration --- .../java/com/security/config/CookieAuthenticationFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/security/config/CookieAuthenticationFilter.java b/src/main/java/com/security/config/CookieAuthenticationFilter.java index 3e1472a..6dff8cf 100644 --- a/src/main/java/com/security/config/CookieAuthenticationFilter.java +++ b/src/main/java/com/security/config/CookieAuthenticationFilter.java @@ -22,7 +22,8 @@ protected void doFilterInternal(HttpServletRequest request, FilterChain filterChain) throws ServletException, IOException { String token = extractTokenFromCookies(request); - + log.debug("Token from cookies: {}", token); + log.debug("Authorization header: {}", request.getHeader("Authorization")); if (token != null) { HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(request) { @Override From 19b9b1bdad0481ccf53da84f5953aeef92764aef Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Thu, 18 Sep 2025 08:53:00 -0500 Subject: [PATCH 02/14] feat: add authenticated with email and password and cookies --- Dockerfile.dev | 66 +++++------ pom.xml | 22 ++-- .../com/security/DTOs/RegisterRequestDto.java | 32 +++++ .../java/com/security/Entity/UserEntity.java | 5 + .../security/Repository/UserRepository.java | 6 + .../com/security/annotations/AdminAccess.java | 29 +++++ .../annotations/AdminOrTrainerAccess.java | 26 ++++ .../annotations/AdminOrUserAccess.java | 26 ++++ .../annotations/AuthenticatedAccess.java | 26 ++++ .../security/annotations/TrainerAccess.java | 27 +++++ .../annotations/TrainerClientAccess.java | 29 +++++ .../annotations/TrainerOwnResourceAccess.java | 29 +++++ .../com/security/annotations/UserAccess.java | 28 +++++ .../annotations/UserOwnResourceAccess.java | 29 +++++ .../com/security/config/SecurityConfig.java | 2 +- .../security/controllers/AuthController.java | 26 +++- .../com/security/services/AuthService.java | 2 + .../services/Impl/AuthServiceImpl.java | 111 ++++++++---------- 18 files changed, 414 insertions(+), 107 deletions(-) create mode 100644 src/main/java/com/security/DTOs/RegisterRequestDto.java create mode 100644 src/main/java/com/security/annotations/AdminAccess.java create mode 100644 src/main/java/com/security/annotations/AdminOrTrainerAccess.java create mode 100644 src/main/java/com/security/annotations/AdminOrUserAccess.java create mode 100644 src/main/java/com/security/annotations/AuthenticatedAccess.java create mode 100644 src/main/java/com/security/annotations/TrainerAccess.java create mode 100644 src/main/java/com/security/annotations/TrainerClientAccess.java create mode 100644 src/main/java/com/security/annotations/TrainerOwnResourceAccess.java create mode 100644 src/main/java/com/security/annotations/UserAccess.java create mode 100644 src/main/java/com/security/annotations/UserOwnResourceAccess.java diff --git a/Dockerfile.dev b/Dockerfile.dev index 265ecf5..d2eebdb 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,13 +1,13 @@ -# FROM openjdk:21-jdk-slim -# WORKDIR /app -# COPY pom.xml . -# COPY mvnw . -# COPY .mvn .mvn -# RUN chmod +x mvnw -# RUN ./mvnw dependency:go-offline -# COPY src ./src -# EXPOSE 9091 -# CMD ["./mvnw", "spring-boot:run"] +FROM openjdk:21-jdk-slim +WORKDIR /app +COPY pom.xml . +COPY mvnw . +COPY .mvn .mvn +RUN chmod +x mvnw +RUN ./mvnw dependency:go-offline +COPY src ./src +EXPOSE 9091 +CMD ["./mvnw", "spring-boot:run"] # FROM openjdk:21-jdk-slim @@ -35,36 +35,36 @@ -FROM openjdk:21-jdk-slim +# FROM openjdk:21-jdk-slim -RUN apt-get update && \ - apt-get install -y maven && \ - rm -rf /var/lib/apt/lists/* && \ - mvn --version +# RUN apt-get update && \ +# apt-get install -y maven && \ +# rm -rf /var/lib/apt/lists/* && \ +# mvn --version -WORKDIR /app +# WORKDIR /app -COPY security-common/pom.xml /app/security-common/pom.xml -COPY msvc-security/pom.xml /app/msvc-security/pom.xml +# COPY security-common/pom.xml /app/security-common/pom.xml +# COPY msvc-security/pom.xml /app/msvc-security/pom.xml -WORKDIR /app/security-common -RUN mvn dependency:resolve || true +# WORKDIR /app/security-common +# RUN mvn dependency:resolve || true -COPY security-common /app/security-common -RUN mvn clean install -DskipTests -q +# COPY security-common /app/security-common +# RUN mvn clean install -DskipTests -q -WORKDIR /app/msvc-security -COPY msvc-security/mvnw . -COPY msvc-security/.mvn .mvn -RUN chmod +x mvnw +# WORKDIR /app/msvc-security +# COPY msvc-security/mvnw . +# COPY msvc-security/.mvn .mvn +# RUN chmod +x mvnw -RUN ./mvnw dependency:go-offline -q || true +# RUN ./mvnw dependency:go-offline -q || true -COPY msvc-security/src ./src +# COPY msvc-security/src ./src -EXPOSE 9091 -ENV SPRING_PROFILES_ACTIVE=dev +# EXPOSE 9091 +# ENV SPRING_PROFILES_ACTIVE=dev -CMD ["./mvnw", "spring-boot:run", \ - "-Dspring-boot.run.fork=false", \ - "-Dspring-boot.run.jvmArguments=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"] \ No newline at end of file +# CMD ["./mvnw", "spring-boot:run", \ +# "-Dspring-boot.run.fork=false", \ +# "-Dspring-boot.run.jvmArguments=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7b43704..7c8def6 100644 --- a/pom.xml +++ b/pom.xml @@ -27,12 +27,12 @@ UTF-8 - - - jitpack.io - https://jitpack.io - - + + + + + + @@ -143,11 +143,11 @@ - - com.github.FitDesk - security-common - 1.0.0 - + + + + + diff --git a/src/main/java/com/security/DTOs/RegisterRequestDto.java b/src/main/java/com/security/DTOs/RegisterRequestDto.java new file mode 100644 index 0000000..07ab96f --- /dev/null +++ b/src/main/java/com/security/DTOs/RegisterRequestDto.java @@ -0,0 +1,32 @@ +package com.security.DTOs; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +public record RegisterRequestDto( + @NotBlank(message = "El primer nombre es obligatorio") + @Size(max = 50, message = "El primer nombre es demasiado largo") + String firstName, + + @NotBlank(message = "El apellido es obligatorio") + @Size(max = 50, message = "El apellido es demasiado largo") + String lastName, + + @NotBlank(message = "El email es obligatorio") + @Email(message = "El formato del email no es válido") + String email, + + @NotBlank(message = "El DNI es obligatorio") + @Pattern(regexp = "\\d{6,9}", message = "El DNI debe contener sólo dígitos (6-9 digitos)") + String dni, + + @NotBlank(message = "La contraseña es obligatoria") + @Size(min = 8, message = "La contraseña debe tener al menos 8 caracteres") + String password, + @NotBlank(message = "El telefono es obligatorio") + @Size(min = 9, max = 9, message = "El telefono debe tener 9 digitos") + String phone +) { +} diff --git a/src/main/java/com/security/Entity/UserEntity.java b/src/main/java/com/security/Entity/UserEntity.java index 00cfec0..4553e97 100644 --- a/src/main/java/com/security/Entity/UserEntity.java +++ b/src/main/java/com/security/Entity/UserEntity.java @@ -1,5 +1,6 @@ package com.security.Entity; +import com.security.config.Audit; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -30,7 +31,11 @@ public class UserEntity implements UserDetails { private String password; private String firstName; private String lastName; + private String dni; + private String phone; + @Embedded + private Audit audit; @Builder.Default private Boolean enabled = true; diff --git a/src/main/java/com/security/Repository/UserRepository.java b/src/main/java/com/security/Repository/UserRepository.java index e01c336..38b3ae9 100644 --- a/src/main/java/com/security/Repository/UserRepository.java +++ b/src/main/java/com/security/Repository/UserRepository.java @@ -14,4 +14,10 @@ public interface UserRepository extends JpaRepository { boolean existsByUsername(String username); boolean existsByEmail(String email); + + boolean existsByDni(String dni); + + boolean existsByPhone(String phone); + + Optional findByDni(String dni); } diff --git a/src/main/java/com/security/annotations/AdminAccess.java b/src/main/java/com/security/annotations/AdminAccess.java new file mode 100644 index 0000000..6234122 --- /dev/null +++ b/src/main/java/com/security/annotations/AdminAccess.java @@ -0,0 +1,29 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; + + +/** + * Restringe el acceso únicamente a usuarios con rol ADMIN. + * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/admin/users")
+ * @AdminAccess
+ * public List getAllUsers() {
+ *     // Solo administradores pueden acceder a la lista completa de usuarios
+ *     return userService.findAll();
+ * }
+ * }
+ * 
+ */ + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@PreAuthorize("hasRole('ADMIN')") +public @interface AdminAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/AdminOrTrainerAccess.java b/src/main/java/com/security/annotations/AdminOrTrainerAccess.java new file mode 100644 index 0000000..6747eef --- /dev/null +++ b/src/main/java/com/security/annotations/AdminOrTrainerAccess.java @@ -0,0 +1,26 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; +/** + * Restringe el acceso a usuarios con rol ADMIN o TRAINER. + * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/classes")
+ * @AdminOrTrainerAccess
+ * public List getAllClasses() {
+ *     // Solo administradores y entrenadores pueden ver todas las clases
+ *     return classService.findAll();
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('ADMIN') or hasRole('TRAINER')") +@Documented +public @interface AdminOrTrainerAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/AdminOrUserAccess.java b/src/main/java/com/security/annotations/AdminOrUserAccess.java new file mode 100644 index 0000000..8c55156 --- /dev/null +++ b/src/main/java/com/security/annotations/AdminOrUserAccess.java @@ -0,0 +1,26 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; +/** + * Restringe el acceso a usuarios con rol ADMIN o USER. + * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/memberships/plans")
+ * @AdminOrUserAccess
+ * public List getAvailablePlans() {
+ *     // Solo administradores y usuarios regulares pueden ver los planes de membresía
+ *     return membershipService.findAllPlans();
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('ADMIN') or hasRole('USER')") +@Documented +public @interface AdminOrUserAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/AuthenticatedAccess.java b/src/main/java/com/security/annotations/AuthenticatedAccess.java new file mode 100644 index 0000000..19f8412 --- /dev/null +++ b/src/main/java/com/security/annotations/AuthenticatedAccess.java @@ -0,0 +1,26 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; +/** + * Restringe el acceso a cualquier usuario autenticado, independientemente de su rol. + * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/account/settings")
+ * @AuthenticatedAccess
+ * public AccountSettingsDTO getAccountSettings() {
+ *     // Cualquier usuario autenticado puede acceder a sus ajustes de cuenta
+ *     return accountService.getSettingsForCurrentUser();
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("isAuthenticated()") +@Documented +public @interface AuthenticatedAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/TrainerAccess.java b/src/main/java/com/security/annotations/TrainerAccess.java new file mode 100644 index 0000000..52332c2 --- /dev/null +++ b/src/main/java/com/security/annotations/TrainerAccess.java @@ -0,0 +1,27 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; + +/** + * Restringe el acceso únicamente a usuarios con rol TRAINER. + * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/trainers/dashboard")
+ * @TrainerAccess
+ * public ResponseEntity getTrainerDashboard() {
+ *     // Solo accesible por entrenadores
+ *     return ResponseEntity.ok(dashboardService.getTrainerDashboard());
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('TRAINER')") +@Documented +public @interface TrainerAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/TrainerClientAccess.java b/src/main/java/com/security/annotations/TrainerClientAccess.java new file mode 100644 index 0000000..397ba99 --- /dev/null +++ b/src/main/java/com/security/annotations/TrainerClientAccess.java @@ -0,0 +1,29 @@ +package com.security.annotations; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; + +/** + * Permite a un entrenador acceder solo a recursos de usuarios que están asignados como sus clientes. + * + *

Requiere que exista un método {@code isTrainerOfUser} en un bean llamado {@code securityService} + * que verifique la relación entrenador-cliente.

+ * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/clients/{userId}/progress")
+ * @TrainerClientAccess
+ * public ProgressDTO viewClientProgress(@PathVariable Long userId) {
+ *     // Solo el entrenador asignado a este cliente puede ver su progreso
+ *     return progressService.getClientProgress(userId);
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('TRAINER') and @securityService.isTrainerOfUser(authentication.principal.id, #userId)") +@Documented +public @interface TrainerClientAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/TrainerOwnResourceAccess.java b/src/main/java/com/security/annotations/TrainerOwnResourceAccess.java new file mode 100644 index 0000000..9bc70bc --- /dev/null +++ b/src/main/java/com/security/annotations/TrainerOwnResourceAccess.java @@ -0,0 +1,29 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; +/** + * Permite a un entrenador acceder únicamente a sus propios recursos basándose en su ID. + * + *

El ID del entrenador debe estar disponible como un parámetro llamado {@code userId} + * en el método controlador.

+ * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/trainers/{userId}/profile")
+ * @TrainerOwnResourceAccess
+ * public TrainerProfileDTO getTrainerProfile(@PathVariable Long userId) {
+ *     // Un entrenador solo puede ver o editar su propio perfil
+ *     return trainerService.findProfileById(userId);
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('TRAINER') AND #userId == authentication.principal.id") +@Documented +public @interface TrainerOwnResourceAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/UserAccess.java b/src/main/java/com/security/annotations/UserAccess.java new file mode 100644 index 0000000..b32f992 --- /dev/null +++ b/src/main/java/com/security/annotations/UserAccess.java @@ -0,0 +1,28 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; + + +/** + * Restringe el acceso únicamente a usuarios con rol USER. + * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/classes/available")
+ * @UserAccess
+ * public List getAvailableClasses() {
+ *     // Solo clientes regulares pueden ver las clases disponibles
+ *     return classService.findAvailable();
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('USER')") +@Documented +public @interface UserAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/annotations/UserOwnResourceAccess.java b/src/main/java/com/security/annotations/UserOwnResourceAccess.java new file mode 100644 index 0000000..01c97a9 --- /dev/null +++ b/src/main/java/com/security/annotations/UserOwnResourceAccess.java @@ -0,0 +1,29 @@ +package com.security.annotations; + +import org.springframework.security.access.prepost.PreAuthorize; + +import java.lang.annotation.*; +/** + * Permite a un usuario acceder únicamente a sus propios recursos basándose en su ID. + * + *

El ID del usuario debe estar disponible como un parámetro llamado {@code userId} + * en el método controlador.

+ * + *

Ejemplo de uso:

+ *
+ * {@code
+ * @GetMapping("/users/{userId}/profile")
+ * @UserOwnResourceAccess
+ * public UserProfileDTO getUserProfile(@PathVariable Long userId) {
+ *     // Un usuario solo puede ver su propio perfil
+ *     return userService.findProfileById(userId);
+ * }
+ * }
+ * 
+ */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize(" hasRole('USER') AND #userId == authentication.principal.id") +@Documented +public @interface UserOwnResourceAccess { +} \ No newline at end of file diff --git a/src/main/java/com/security/config/SecurityConfig.java b/src/main/java/com/security/config/SecurityConfig.java index 4b8f4f1..cd291d3 100644 --- a/src/main/java/com/security/config/SecurityConfig.java +++ b/src/main/java/com/security/config/SecurityConfig.java @@ -40,7 +40,6 @@ import java.time.Duration; import java.util.UUID; -import com.fitdesk.security.annotations.*; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @@ -80,6 +79,7 @@ public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CookieA "/auth/status", "/auth/login", "/auth/refresh", + "/auth/register", "/saludo", "/" ).permitAll() diff --git a/src/main/java/com/security/controllers/AuthController.java b/src/main/java/com/security/controllers/AuthController.java index e72236b..1c7f314 100644 --- a/src/main/java/com/security/controllers/AuthController.java +++ b/src/main/java/com/security/controllers/AuthController.java @@ -1,8 +1,9 @@ package com.security.controllers; -import com.fitdesk.security.annotations.*; import com.security.DTOs.LoginRequestDTO; import com.security.DTOs.LoginResponseDTO; +import com.security.DTOs.RegisterRequestDto; +import com.security.annotations.AuthenticatedAccess; import com.security.services.AuthService; import com.security.services.CookieService; import com.security.services.LoginResponseService; @@ -17,9 +18,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ResponseStatusException; import java.util.Map; + @RestController @RequestMapping("/auth") @RequiredArgsConstructor @@ -107,6 +110,27 @@ public ResponseEntity> logout( } } + + @PostMapping("/register") + public ResponseEntity> register(@Valid @RequestBody RegisterRequestDto registerRequestDto, HttpServletResponse response) { + try { + LoginResponseDTO authResponse = authService.registerUser(registerRequestDto); + cookieService.setSecureTokenCookies(response, authResponse); + Map safeResponse = loginResponseService.createSafeLoginResponse(authResponse); + log.info("Register successful for email: {}", registerRequestDto.email()); + return ResponseEntity.status(HttpStatus.CREATED).body(safeResponse); + } catch ( + ResponseStatusException ex) { + log.warn("Register failed: {}", ex.getMessage()); + throw ex; + } catch ( + Exception e) { + log.error("Register failed", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(loginResponseService.createErrorResponse("Error en el registro")); + } + } + @Operation(summary = "Verificar autenticación") @AuthenticatedAccess @GetMapping("/me") diff --git a/src/main/java/com/security/services/AuthService.java b/src/main/java/com/security/services/AuthService.java index cdf1e47..3375cb3 100644 --- a/src/main/java/com/security/services/AuthService.java +++ b/src/main/java/com/security/services/AuthService.java @@ -2,6 +2,7 @@ import com.security.DTOs.LoginRequestDTO; import com.security.DTOs.LoginResponseDTO; +import com.security.DTOs.RegisterRequestDto; import com.security.Entity.UserEntity; public interface AuthService { @@ -12,6 +13,7 @@ public interface AuthService { // boolean validateToken(String token); + LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto); LoginResponseDTO refreshToken(String refreshToken); diff --git a/src/main/java/com/security/services/Impl/AuthServiceImpl.java b/src/main/java/com/security/services/Impl/AuthServiceImpl.java index 262cfe4..d6018b0 100644 --- a/src/main/java/com/security/services/Impl/AuthServiceImpl.java +++ b/src/main/java/com/security/services/Impl/AuthServiceImpl.java @@ -2,18 +2,23 @@ import com.security.DTOs.LoginRequestDTO; import com.security.DTOs.LoginResponseDTO; +import com.security.DTOs.RegisterRequestDto; +import com.security.Entity.RoleEntity; import com.security.Entity.UserEntity; import com.security.Mappers.UserMapper; +import com.security.Repository.RoleRepository; import com.security.Repository.UserRepository; import com.security.services.AuthService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import com.security.services.TokenService; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -29,8 +34,9 @@ public class AuthServiceImpl implements AuthService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final RoleRepository roleRepository; private final UserMapper userMapper; - private final TokenService tokenService; + // private final TokenService tokenService; private final JwtEncoder jwtEncoder; private final JwtDecoder jwtDecoder; @@ -139,67 +145,50 @@ public void logout(String accessToken) { } } + @Override + public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { + if (userRepository.existsByEmail(registerRequestDto.email())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Error al inicial sesion"); + } + + if (userRepository.existsByUsername(registerRequestDto.firstName())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Error al registrarse verifica tus credenciales"); + + } + + if (userRepository.existsByDni(registerRequestDto.dni())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Error al registrarse verifica tus credenciales"); + } + RoleEntity userRole = roleRepository.findByName("USER").orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Error al encontrar el rol de Usuario")); + + + UserEntity user = UserEntity.builder() + .firstName(registerRequestDto.firstName()) + .lastName(registerRequestDto.lastName()) + .email(registerRequestDto.email()) + .username((registerRequestDto.firstName() + "." + registerRequestDto.lastName()).toLowerCase()) + .dni(registerRequestDto.dni()) + .phone(registerRequestDto.phone()) + .password(passwordEncoder.encode(registerRequestDto.password())) + .roles(Set.of(userRole)) + .enabled(true) + .build(); + + userRepository.save(user); + String accessToken = generateAccessToken(user); + String refreshToken = generateRefreshToken(user); + validRefreshTokens.add(refreshToken); + return LoginResponseDTO.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .tokenType("Bearer") + .expiresAt(Instant.now().plus(15, ChronoUnit.MINUTES)) + .scope("read write") + .user(userMapper.toDTO(user)) + .message("Registro exitoso") + .build(); + } -// @Override -// public boolean validateToken(String token) { -// return tokenService.isTokenValid(token); -// } - -// private UserEntity findUserByEmail(String email) { -// return userRepository.findByEmail(email) -// .orElseThrow(() -> { -// log.warn("User not found with email: {}", email); -// return new UsernameNotFoundException("Usuario no encontrado con el email: " + email); -// }); -// } - -// private void validatePassword(String rawPassword, String encodedPassword) { -// log.debug("Validating password - Raw length: {}, Encoded length: {}", -// rawPassword != null ? rawPassword.length() : 0, -// encodedPassword != null ? encodedPassword.length() : 0); -// -// if (encodedPassword == null || encodedPassword.trim().isEmpty()) { -// log.error("Encoded password is null or empty"); -// throw new BadCredentialsException("Contraseña incorrecta"); -// } -// -// if (rawPassword == null || rawPassword.trim().isEmpty()) { -// log.error("Raw password is null or empty"); -// throw new BadCredentialsException("Contraseña incorrecta"); -// } -// -// if (!passwordEncoder.matches(rawPassword, encodedPassword)) { -// log.warn("Password validation failed"); -// throw new BadCredentialsException("Contraseña incorrecta"); -// } -// -// log.debug("Password validation successful"); -// } - - -// private void validateUserStatus(UserEntity user) { -// if (!user.getEnabled()) { -// log.warn("User account is disabled: {}", user.getEmail()); -// throw new DisabledException("La cuenta de usuario está deshabilitada"); -// } -// -// if (!user.isAccountNonLocked()) { -// log.warn("User account is locked: {}", user.getEmail()); -// throw new DisabledException("La cuenta de usuario está bloqueada"); -// } -// -// if (!user.isAccountNonExpired()) { -// log.warn("User account is expired: {}", user.getEmail()); -// throw new DisabledException("La cuenta de usuario ha expirado"); -// } -// } - -// private String maskToken(String token) { -// if (token == null || token.length() < 10) { -// return "***"; -// } -// return token.substring(0, 10) + "..."; -// } private String generateRefreshToken(UserEntity user) { Instant now = Instant.now(); From b7bfe3f5879f1e90e738e6706422b2c06a953957 Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Thu, 18 Sep 2025 11:36:35 -0500 Subject: [PATCH 03/14] feat: add or remove roles for users --- Dockerfile.dev | 4 +- .../com/security/DTOs/AuthResponseDTO.java | 10 +++ .../security/DTOs/RoleChangeRequestDTO.java | 9 +++ .../com/security/DTOs/RolesResponseDTO.java | 10 +++ .../java/com/security/Entity/RoleEntity.java | 3 + .../com/security/config/SecurityConfig.java | 46 ++++++++++++ .../controllers/AdminUserController.java | 47 ++++++++++++ .../services/Impl/UserRoleServiceImpl.java | 73 +++++++++++++++++++ .../security/services/UserRoleService.java | 16 ++++ 9 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/security/DTOs/AuthResponseDTO.java create mode 100644 src/main/java/com/security/DTOs/RoleChangeRequestDTO.java create mode 100644 src/main/java/com/security/DTOs/RolesResponseDTO.java create mode 100644 src/main/java/com/security/controllers/AdminUserController.java create mode 100644 src/main/java/com/security/services/Impl/UserRoleServiceImpl.java create mode 100644 src/main/java/com/security/services/UserRoleService.java diff --git a/Dockerfile.dev b/Dockerfile.dev index d2eebdb..9b2c43f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -6,8 +6,8 @@ COPY .mvn .mvn RUN chmod +x mvnw RUN ./mvnw dependency:go-offline COPY src ./src -EXPOSE 9091 -CMD ["./mvnw", "spring-boot:run"] +EXPOSE 9091 5005 +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"] # FROM openjdk:21-jdk-slim diff --git a/src/main/java/com/security/DTOs/AuthResponseDTO.java b/src/main/java/com/security/DTOs/AuthResponseDTO.java new file mode 100644 index 0000000..7b5f683 --- /dev/null +++ b/src/main/java/com/security/DTOs/AuthResponseDTO.java @@ -0,0 +1,10 @@ +package com.security.DTOs; + +import java.time.Instant; + +public record AuthResponseDTO( + boolean success, + String message, + Instant timestamp +) { +} diff --git a/src/main/java/com/security/DTOs/RoleChangeRequestDTO.java b/src/main/java/com/security/DTOs/RoleChangeRequestDTO.java new file mode 100644 index 0000000..18e3c05 --- /dev/null +++ b/src/main/java/com/security/DTOs/RoleChangeRequestDTO.java @@ -0,0 +1,9 @@ +package com.security.DTOs; + +import jakarta.validation.constraints.NotBlank; + +public record RoleChangeRequestDTO( + @NotBlank(message = "El nombre del rol es requerido") + String role +) { +} diff --git a/src/main/java/com/security/DTOs/RolesResponseDTO.java b/src/main/java/com/security/DTOs/RolesResponseDTO.java new file mode 100644 index 0000000..644bf7c --- /dev/null +++ b/src/main/java/com/security/DTOs/RolesResponseDTO.java @@ -0,0 +1,10 @@ +package com.security.DTOs; + +import java.util.Set; +import java.util.UUID; + +public record RolesResponseDTO( + UUID userId, + Set roles +) { +} diff --git a/src/main/java/com/security/Entity/RoleEntity.java b/src/main/java/com/security/Entity/RoleEntity.java index a450a3a..c9ccb91 100644 --- a/src/main/java/com/security/Entity/RoleEntity.java +++ b/src/main/java/com/security/Entity/RoleEntity.java @@ -1,5 +1,6 @@ package com.security.Entity; +import com.security.config.Audit; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -21,4 +22,6 @@ public class RoleEntity { @Column(unique = true, nullable = false) private String name; private String description; + @Embedded + private Audit audit; } diff --git a/src/main/java/com/security/config/SecurityConfig.java b/src/main/java/com/security/config/SecurityConfig.java index cd291d3..94b306b 100644 --- a/src/main/java/com/security/config/SecurityConfig.java +++ b/src/main/java/com/security/config/SecurityConfig.java @@ -14,6 +14,8 @@ 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.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -31,6 +33,8 @@ import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; import java.security.KeyPair; @@ -38,6 +42,9 @@ import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; import java.util.UUID; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -93,6 +100,45 @@ public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CookieA .build(); } + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter scopeConverter = new JwtGrantedAuthoritiesConverter(); + scopeConverter.setAuthorityPrefix("SCOPE_"); + JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); + converter.setJwtGrantedAuthoritiesConverter(jwt -> { + Collection authorities = new ArrayList<>(); + + Collection scopeAuth = (Collection) scopeConverter.convert(jwt); + if (scopeAuth != null) + authorities.addAll(scopeAuth); + + Object claim = jwt.getClaims().get("authorities"); + if (claim != null) { + if (claim instanceof String) { + String[] parts = ((String) claim).trim().split("\\s+"); + for (String p : parts) { + if (!p.isBlank()) + authorities.add(new SimpleGrantedAuthority(p)); + } + } else if (claim instanceof Collection) { + ((Collection) claim).forEach(o -> { + if (o != null) + authorities.add(new SimpleGrantedAuthority(o.toString())); + }); + } else if (claim instanceof Map) { + ((Map) claim).values().forEach(v -> { + if (v != null) + authorities.add(new SimpleGrantedAuthority(v.toString())); + }); + } + } + + return authorities; + }); + + return converter; + } + @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient gatewayClient = RegisteredClient.withId(UUID.randomUUID().toString()) diff --git a/src/main/java/com/security/controllers/AdminUserController.java b/src/main/java/com/security/controllers/AdminUserController.java new file mode 100644 index 0000000..50fd1b5 --- /dev/null +++ b/src/main/java/com/security/controllers/AdminUserController.java @@ -0,0 +1,47 @@ +package com.security.controllers; + +import com.security.DTOs.AuthResponseDTO; +import com.security.DTOs.RoleChangeRequestDTO; +import com.security.DTOs.RolesResponseDTO; +import com.security.annotations.AdminAccess; +import com.security.services.UserRoleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@RestController +@RequestMapping("/admin/users") +@RequiredArgsConstructor +@Tag(name = "Roles", description = "Endpoints para manejo de roles") +public class AdminUserController { + + private final UserRoleService userRoleService; + + @Operation(summary = "Listar roles de usuario") + @GetMapping("/{id}/roles") + @AdminAccess + public ResponseEntity getRoles(@PathVariable UUID id) { + return ResponseEntity.ok(userRoleService.getUserRoles(id)); + } + + + @Operation(summary = "Agregar un rol a un usuario") + @PostMapping("/{id}/roles") + @AdminAccess + public ResponseEntity addRole(@PathVariable UUID id, @Valid @RequestBody RoleChangeRequestDTO request) { + return ResponseEntity.ok(userRoleService.addRoleToUser(id, request.role())); + } + + @Operation(summary = "Quitar un rol a un usuario") + @DeleteMapping("/{id}/roles") + @AdminAccess + public ResponseEntity deleteRole(@PathVariable UUID id, @RequestBody RoleChangeRequestDTO requestDTO) { + return ResponseEntity.ok(userRoleService.removeRoleFromUser(id, requestDTO.role())); + } +} diff --git a/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java b/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java new file mode 100644 index 0000000..23b1fdd --- /dev/null +++ b/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java @@ -0,0 +1,73 @@ +package com.security.services.Impl; + +import com.security.DTOs.AuthResponseDTO; +import com.security.DTOs.RolesResponseDTO; +import com.security.Entity.RoleEntity; +import com.security.Entity.UserEntity; +import com.security.Repository.RoleRepository; +import com.security.Repository.UserRepository; +import com.security.services.UserRoleService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.time.Instant; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +public class UserRoleServiceImpl implements UserRoleService { + + private final UserRepository userRepository; + private final RoleRepository roleRepository; + + + @Override + @Transactional(readOnly = true) + public RolesResponseDTO getUserRoles(UUID userId) { + UserEntity user = userRepository.findById(userId).orElseThrow(); + Set roles = user.getRoles().stream().map(RoleEntity::getName).collect(Collectors.toSet()); + return new RolesResponseDTO(userId, roles); + } + + @Override + @Transactional + public AuthResponseDTO addRoleToUser(UUID userId, String roleName) { + UserEntity user = userRepository.findById(userId).orElseThrow(); + + RoleEntity role = roleRepository.findByName(roleName).orElseThrow(); + if (user.getRoles() == null) { + user.setRoles(Set.of(role)); + } else if (user.getRoles().stream().noneMatch(r -> r.getName().equals(roleName))) { + HashSet mutable = new HashSet<>(user.getRoles()); + mutable.add(role); + user.setRoles(mutable); + } + userRepository.save(user); + + return new AuthResponseDTO(true, "Rol agregado", Instant.now()); + } + + @Override + @Transactional + public AuthResponseDTO removeRoleFromUser(UUID userId, String roleName) { + UserEntity user = userRepository.findById(userId).orElseThrow(); + if (user.getRoles() == null || user.getRoles().stream().noneMatch(r -> r.getName().equals(roleName))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "El usuario no tiene ese rol"); + + } + + var mutable = new HashSet<>(user.getRoles()); + mutable.removeIf(r -> r.getName().equals(roleName)); + user.setRoles(mutable); + userRepository.save(user); + return new AuthResponseDTO(true, "Rol removido", Instant.now()); + } +} diff --git a/src/main/java/com/security/services/UserRoleService.java b/src/main/java/com/security/services/UserRoleService.java new file mode 100644 index 0000000..4db678f --- /dev/null +++ b/src/main/java/com/security/services/UserRoleService.java @@ -0,0 +1,16 @@ +package com.security.services; + +import com.security.DTOs.AuthResponseDTO; +import com.security.DTOs.RolesResponseDTO; + +import java.util.Set; +import java.util.UUID; + +public interface UserRoleService { + RolesResponseDTO getUserRoles(UUID userId); + + AuthResponseDTO addRoleToUser(UUID userId, String roleName); + + AuthResponseDTO removeRoleFromUser(UUID userId, String roleName); + +} From 4bc86078a7ccd54c6a745fd128210e2bd52e647c Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Thu, 18 Sep 2025 15:23:15 -0500 Subject: [PATCH 04/14] feat: add new method for activate and desactivate for users --- .../java/com/security/Entity/UserEntity.java | 2 + src/main/java/com/security/config/Audit.java | 37 ++++++--- .../com/security/config/AuditListener.java | 78 +++++++++++++++++++ .../controllers/AdminUserController.java | 31 +++++++- .../services/Impl/UserAccountServiceImpl.java | 73 +++++++++++++++++ .../security/services/UserAccountService.java | 11 +++ 6 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/security/config/AuditListener.java create mode 100644 src/main/java/com/security/services/Impl/UserAccountServiceImpl.java create mode 100644 src/main/java/com/security/services/UserAccountService.java diff --git a/src/main/java/com/security/Entity/UserEntity.java b/src/main/java/com/security/Entity/UserEntity.java index 4553e97..2ab3df1 100644 --- a/src/main/java/com/security/Entity/UserEntity.java +++ b/src/main/java/com/security/Entity/UserEntity.java @@ -1,6 +1,7 @@ package com.security.Entity; import com.security.config.Audit; +import com.security.config.AuditListener; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -15,6 +16,7 @@ import java.util.UUID; import java.util.stream.Collectors; +@EntityListeners(AuditListener.class) @Entity @Table(name = "users") @Data diff --git a/src/main/java/com/security/config/Audit.java b/src/main/java/com/security/config/Audit.java index 85bf1a0..5f4099d 100644 --- a/src/main/java/com/security/config/Audit.java +++ b/src/main/java/com/security/config/Audit.java @@ -4,22 +4,35 @@ import jakarta.persistence.Embeddable; import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.time.Instant; import java.time.LocalDateTime; + @Embeddable +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder public class Audit { + + @Column(name = "created_by") + private String createdBy; + @Column(name = "created_at") - private LocalDateTime createdAt; - @Column(name = "update_at") - private LocalDateTime updateAt; - - @PrePersist - void prePersist() { - createdAt = LocalDateTime.now(); - } - @PreUpdate - void preUpdate() { - updateAt = LocalDateTime.now(); - } + private Instant createdAt; + + @Column(name = "updated_by") + private String updatedBy; + + @Column(name = "updated_at") + private Instant updatedAt; + + @Column(name = "status_reason") + private String statusReason; + } \ No newline at end of file diff --git a/src/main/java/com/security/config/AuditListener.java b/src/main/java/com/security/config/AuditListener.java new file mode 100644 index 0000000..0c7a7e4 --- /dev/null +++ b/src/main/java/com/security/config/AuditListener.java @@ -0,0 +1,78 @@ +package com.security.config; + +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.lang.reflect.Field; +import java.time.Instant; + +public class AuditListener { + + @PrePersist + public void onPrePersist(Object entity) { + applyAudit(entity, true); + } + + @PreUpdate + public void onPreUpdate(Object entity) { + applyAudit(entity, false); + } + + private void applyAudit(Object entity, boolean isCreate) { + try { + Field f = findAuditField(entity); + if (f == null) + return; + f.setAccessible(true); + + Audit audit = (Audit) f.get(entity); + if (audit == null) { + audit = Audit.builder().build(); + } + + String user = currentUsername(); + Instant now = Instant.now(); + + if (isCreate) { + if (audit.getCreatedAt() == null) + audit.setCreatedAt(now); + if (audit.getCreatedBy() == null) + audit.setCreatedBy(user); + } + // siempre actualizar updated* en create y update + audit.setUpdatedAt(now); + audit.setUpdatedBy(user); + + f.set(entity, audit); + } catch ( + Exception ignored) { + // no bloquear persist/update por auditoría + } + } + + private Field findAuditField(Object entity) { + Class cls = entity.getClass(); + while (cls != null && cls != Object.class) { + try { + return cls.getDeclaredField("audit"); + } catch ( + NoSuchFieldException e) { + cls = cls.getSuperclass(); + } + } + return null; + } + + private String currentUsername() { + try { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null && auth.getName() != null) + return auth.getName(); + } catch ( + Exception ignored) { + } + return "system"; + } +} diff --git a/src/main/java/com/security/controllers/AdminUserController.java b/src/main/java/com/security/controllers/AdminUserController.java index 50fd1b5..c7800f1 100644 --- a/src/main/java/com/security/controllers/AdminUserController.java +++ b/src/main/java/com/security/controllers/AdminUserController.java @@ -4,13 +4,15 @@ import com.security.DTOs.RoleChangeRequestDTO; import com.security.DTOs.RolesResponseDTO; import com.security.annotations.AdminAccess; +import com.security.services.LoginResponseService; +import com.security.services.UserAccountService; import com.security.services.UserRoleService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.util.UUID; @@ -18,10 +20,12 @@ @RestController @RequestMapping("/admin/users") @RequiredArgsConstructor -@Tag(name = "Roles", description = "Endpoints para manejo de roles") +@Tag(name = "Autorizacion", description = "Endpoints para manejo de roles") public class AdminUserController { private final UserRoleService userRoleService; + private final UserAccountService userAccountService; + private final LoginResponseService loginResponseService; @Operation(summary = "Listar roles de usuario") @GetMapping("/{id}/roles") @@ -44,4 +48,27 @@ public ResponseEntity addRole(@PathVariable UUID id, @Valid @Re public ResponseEntity deleteRole(@PathVariable UUID id, @RequestBody RoleChangeRequestDTO requestDTO) { return ResponseEntity.ok(userRoleService.removeRoleFromUser(id, requestDTO.role())); } + + + @Operation(summary = "Desactivar cuenta de usuario") + @PatchMapping("/{id}/deactivate") + @AdminAccess + public ResponseEntity deactivate(@PathVariable UUID id, + @RequestParam(required = false) String reason, + Authentication authentication) { + String admin = authentication != null ? authentication.getName() : "system"; + + return ResponseEntity.ok(userAccountService.deactivateUser(id, reason, admin)); + } + + @Operation(summary = "Activar cuenta de usuario") + @PatchMapping("/{id}/activate") + @AdminAccess + public ResponseEntity activate(@PathVariable UUID id, + Authentication authentication) { + String admin = authentication != null ? authentication.getName() : "system"; + + return ResponseEntity.ok(userAccountService.activateUser(id, admin)); + } + } diff --git a/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java b/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java new file mode 100644 index 0000000..a70796b --- /dev/null +++ b/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java @@ -0,0 +1,73 @@ +package com.security.services.Impl; + +import com.security.DTOs.AuthResponseDTO; +import com.security.Entity.UserEntity; +import com.security.Repository.UserRepository; +import com.security.config.Audit; +import com.security.services.UserAccountService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.time.Instant; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Transactional +public class UserAccountServiceImpl implements UserAccountService { + + private final UserRepository userRepository; + + + @Override + public AuthResponseDTO deactivateUser(UUID userId, String reason, String admin) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado")); + + if (!Boolean.TRUE.equals(user.getEnabled())) + return new AuthResponseDTO(false, "Usuario ya esta desactivado", Instant.now()); + + Audit a = user.getAudit(); + if (a == null) { + a = Audit.builder().build(); + } + a.setUpdatedBy(admin); + a.setUpdatedAt(Instant.now()); + a.setStatusReason(reason); + user.setAudit(a); + user.setEnabled(false); + + userRepository.save(user); + + + return new AuthResponseDTO(true, "Usuario desactivado", Instant.now()); + + } + + + @Override + public AuthResponseDTO activateUser(UUID userId, String admin) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Usuario no encontrado")); + + if (Boolean.TRUE.equals(user.getEnabled())) { + return new AuthResponseDTO(false, "Usuario ya esta activo", Instant.now()); + } + + Audit a = user.getAudit(); + if (a == null) { + a = Audit.builder().build(); + } + a.setUpdatedBy(admin); + a.setUpdatedAt(Instant.now()); + a.setStatusReason(null); + user.setAudit(a); + user.setEnabled(true); + + userRepository.save(user); + return new AuthResponseDTO(true, "Usuario activado", Instant.now()); + } +} diff --git a/src/main/java/com/security/services/UserAccountService.java b/src/main/java/com/security/services/UserAccountService.java new file mode 100644 index 0000000..be937b2 --- /dev/null +++ b/src/main/java/com/security/services/UserAccountService.java @@ -0,0 +1,11 @@ +package com.security.services; + +import com.security.DTOs.AuthResponseDTO; + +import java.util.UUID; + +public interface UserAccountService { + AuthResponseDTO deactivateUser(UUID userId, String reason, String admin); + + AuthResponseDTO activateUser(UUID userId, String admin); +} From b6d681c38e186022eed546c915979ada50897d3b Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Thu, 18 Sep 2025 16:13:25 -0500 Subject: [PATCH 05/14] feat: refactor for code about good practices --- .../controllers/AdminUserController.java | 2 - .../security/controllers/AuthController.java | 41 ++++--- .../com/security/services/AuthService.java | 3 +- .../com/security/services/CookieService.java | 97 +--------------- .../services/Impl/AuthServiceImpl.java | 10 +- .../services/Impl/CookieServiceImpl.java | 105 ++++++++++++++++++ .../{ => Impl}/CustomUserDetailsService.java | 2 +- .../services/Impl/TokenServiceImpl.java | 87 --------------- .../services/LoginResponseService.java | 58 ---------- .../com/security/services/TokenService.java | 16 --- 10 files changed, 141 insertions(+), 280 deletions(-) create mode 100644 src/main/java/com/security/services/Impl/CookieServiceImpl.java rename src/main/java/com/security/services/{ => Impl}/CustomUserDetailsService.java (96%) delete mode 100644 src/main/java/com/security/services/Impl/TokenServiceImpl.java delete mode 100644 src/main/java/com/security/services/LoginResponseService.java delete mode 100644 src/main/java/com/security/services/TokenService.java diff --git a/src/main/java/com/security/controllers/AdminUserController.java b/src/main/java/com/security/controllers/AdminUserController.java index c7800f1..64baa21 100644 --- a/src/main/java/com/security/controllers/AdminUserController.java +++ b/src/main/java/com/security/controllers/AdminUserController.java @@ -4,7 +4,6 @@ import com.security.DTOs.RoleChangeRequestDTO; import com.security.DTOs.RolesResponseDTO; import com.security.annotations.AdminAccess; -import com.security.services.LoginResponseService; import com.security.services.UserAccountService; import com.security.services.UserRoleService; import io.swagger.v3.oas.annotations.Operation; @@ -25,7 +24,6 @@ public class AdminUserController { private final UserRoleService userRoleService; private final UserAccountService userAccountService; - private final LoginResponseService loginResponseService; @Operation(summary = "Listar roles de usuario") @GetMapping("/{id}/roles") diff --git a/src/main/java/com/security/controllers/AuthController.java b/src/main/java/com/security/controllers/AuthController.java index 1c7f314..cb59d64 100644 --- a/src/main/java/com/security/controllers/AuthController.java +++ b/src/main/java/com/security/controllers/AuthController.java @@ -1,12 +1,12 @@ package com.security.controllers; +import com.security.DTOs.AuthResponseDTO; import com.security.DTOs.LoginRequestDTO; import com.security.DTOs.LoginResponseDTO; import com.security.DTOs.RegisterRequestDto; import com.security.annotations.AuthenticatedAccess; import com.security.services.AuthService; import com.security.services.CookieService; -import com.security.services.LoginResponseService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import java.time.Instant; import java.util.Map; @@ -32,11 +33,11 @@ public class AuthController { private final AuthService authService; private final CookieService cookieService; - private final LoginResponseService loginResponseService; +// private final LoginResponseService loginResponseService; @Operation(summary = "Iniciar sesión con email", description = "Autentica un usuario y establece cookies seguras") @PostMapping("/login") - public ResponseEntity> login( + public ResponseEntity login( @Valid @RequestBody LoginRequestDTO loginRequest, HttpServletResponse response) { @@ -46,22 +47,21 @@ public ResponseEntity> login( LoginResponseDTO authResponse = authService.authenticateUser(loginRequest); cookieService.setSecureTokenCookies(response, authResponse); - Map safeResponse = loginResponseService.createSafeLoginResponse(authResponse); log.info("Login successful for email: {}", loginRequest.getEmail()); - return ResponseEntity.ok(safeResponse); + return ResponseEntity.ok(new AuthResponseDTO(true, "Inicio de sesion correctamente", Instant.now())); } catch ( Exception e) { log.error("Login failed for email: {}", loginRequest.getEmail(), e); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(loginResponseService.createErrorResponse("Credenciales inválidas")); + .body(new AuthResponseDTO(false, "Login fallido", Instant.now())); } } @Operation(summary = "Refrescar token", description = "Renueva el access token usando el refresh token") @PostMapping("/refresh") - public ResponseEntity> refreshToken( + public ResponseEntity refreshToken( HttpServletRequest request, HttpServletResponse response) { @@ -70,26 +70,26 @@ public ResponseEntity> refreshToken( if (refreshToken == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(loginResponseService.createErrorResponse("No refresh token found")); + .body(new AuthResponseDTO(false, "No ha iniciado sesion , intente iniciar sesion", Instant.now())); } LoginResponseDTO newTokens = authService.refreshToken(refreshToken); cookieService.setSecureTokenCookies(response, newTokens); - return ResponseEntity.ok(loginResponseService.createSuccessResponse("Token renovado")); + return ResponseEntity.ok(new AuthResponseDTO(true, "Token Renovado", Instant.now())); } catch ( Exception e) { - log.error("Token refresh failed", e); + log.error("Error al renovar el token", e); cookieService.clearTokenCookies(response); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(loginResponseService.createErrorResponse("Token refresh failed")); + .body(new AuthResponseDTO(false, "No se pudo renovar el token", Instant.now())); } } @Operation(summary = "Cerrar sesión", description = "Limpia las cookies seguras") @PostMapping("/logout") - public ResponseEntity> logout( + public ResponseEntity logout( HttpServletRequest request, HttpServletResponse response) { @@ -100,34 +100,33 @@ public ResponseEntity> logout( } cookieService.clearTokenCookies(response); - return ResponseEntity.ok(loginResponseService.createSuccessResponse("Sesión cerrada exitosamente")); + return ResponseEntity.ok(new AuthResponseDTO(true, "Sesión cerrada exitosamente", Instant.now())); } catch ( Exception e) { log.error("Logout failed", e); cookieService.clearTokenCookies(response); - return ResponseEntity.ok(loginResponseService.createSuccessResponse("Sesión cerrada")); + return ResponseEntity.ok(new AuthResponseDTO(false, "Error al cerrar sesion", Instant.now())); } } @PostMapping("/register") - public ResponseEntity> register(@Valid @RequestBody RegisterRequestDto registerRequestDto, HttpServletResponse response) { + public ResponseEntity register(@Valid @RequestBody RegisterRequestDto registerRequestDto, HttpServletResponse response) { try { LoginResponseDTO authResponse = authService.registerUser(registerRequestDto); cookieService.setSecureTokenCookies(response, authResponse); - Map safeResponse = loginResponseService.createSafeLoginResponse(authResponse); - log.info("Register successful for email: {}", registerRequestDto.email()); - return ResponseEntity.status(HttpStatus.CREATED).body(safeResponse); + log.info("Registro correcto con el email: {}", registerRequestDto.email()); + return ResponseEntity.status(HttpStatus.CREATED).body(new AuthResponseDTO(true, "Usuario registrado correctamente", Instant.now())); } catch ( ResponseStatusException ex) { - log.warn("Register failed: {}", ex.getMessage()); + log.warn("Registro fallido: {}", ex.getMessage()); throw ex; } catch ( Exception e) { - log.error("Register failed", e); + log.error("Registro Fallido", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(loginResponseService.createErrorResponse("Error en el registro")); + .body(new AuthResponseDTO(false, "Error al registrar cuenta", Instant.now())); } } diff --git a/src/main/java/com/security/services/AuthService.java b/src/main/java/com/security/services/AuthService.java index 3375cb3..2ca0bbb 100644 --- a/src/main/java/com/security/services/AuthService.java +++ b/src/main/java/com/security/services/AuthService.java @@ -3,7 +3,7 @@ import com.security.DTOs.LoginRequestDTO; import com.security.DTOs.LoginResponseDTO; import com.security.DTOs.RegisterRequestDto; -import com.security.Entity.UserEntity; + public interface AuthService { @@ -11,7 +11,6 @@ public interface AuthService { void logout(String token); -// boolean validateToken(String token); LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto); diff --git a/src/main/java/com/security/services/CookieService.java b/src/main/java/com/security/services/CookieService.java index 2ddf616..dd3e002 100644 --- a/src/main/java/com/security/services/CookieService.java +++ b/src/main/java/com/security/services/CookieService.java @@ -1,101 +1,16 @@ package com.security.services; + import com.security.DTOs.LoginResponseDTO; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -public class CookieService { - - @Value("${app.cookie.domain:localhost}") - private String cookieDomain; - - @Value("${app.cookie.secure:false}") - private boolean cookieSecure; - - @Value("${app.cookie.access-token-max-age:900}") - private int accessTokenMaxAge; - - @Value("${app.cookie.refresh-token-max-age:604800}") - private int refreshTokenMaxAge; - - - public void setSecureTokenCookies(HttpServletResponse response, LoginResponseDTO authResponse) { - setSecureCookie(response, "access_token", authResponse.getAccessToken(), accessTokenMaxAge); - - setSecureCookie(response, "refresh_token", authResponse.getRefreshToken(), refreshTokenMaxAge); - - log.debug("Secure cookies set for user: {}", authResponse.getUser().getEmail()); - } - - - private void setSecureCookie(HttpServletResponse response, String name, String value, int maxAge) { - Cookie cookie = new Cookie(name, value); - cookie.setHttpOnly(true); - cookie.setSecure(cookieSecure); - cookie.setPath("/"); - cookie.setMaxAge(maxAge); - - if (!"localhost".equals(cookieDomain)) { - cookie.setDomain(cookieDomain); - } - - response.addCookie(cookie); - - String sameSiteValue = cookieSecure ? "None" : "Strict"; - String cookieHeader = String.format("%s=%s; Path=/; HttpOnly; Max-Age=%d; SameSite=%s%s%s", - name, - value, - maxAge, - sameSiteValue, - cookieSecure ? "; Secure" : "", - !"localhost".equals(cookieDomain) ? "; Domain=" + cookieDomain : "" - ); - - response.addHeader("Set-Cookie", cookieHeader); - } - - - public void clearTokenCookies(HttpServletResponse response) { - clearCookie(response, "access_token"); - clearCookie(response, "refresh_token"); - log.debug("Token cookies cleared"); - } - - private void clearCookie(HttpServletResponse response, String name) { - Cookie cookie = new Cookie(name, ""); - cookie.setMaxAge(0); - cookie.setPath("/"); - cookie.setHttpOnly(true); - response.addCookie(cookie); - - response.addHeader("Set-Cookie", String.format("%s=; Path=/; HttpOnly; Max-Age=0", name)); - } - - - public String extractAccessTokenFromCookies(HttpServletRequest request) { - return extractTokenFromCookies(request, "access_token"); - } +public interface CookieService { + void setSecureTokenCookies(HttpServletResponse response, LoginResponseDTO authResponse); - public String extractRefreshTokenFromCookies(HttpServletRequest request) { - return extractTokenFromCookies(request, "refresh_token"); - } + String extractAccessTokenFromCookies(HttpServletRequest request); + String extractRefreshTokenFromCookies(HttpServletRequest request); - private String extractTokenFromCookies(HttpServletRequest request, String tokenName) { - if (request.getCookies() != null) { - for (Cookie cookie : request.getCookies()) { - if (tokenName.equals(cookie.getName())) { - return cookie.getValue(); - } - } - } - return null; - } + void clearTokenCookies(HttpServletResponse response); } \ No newline at end of file diff --git a/src/main/java/com/security/services/Impl/AuthServiceImpl.java b/src/main/java/com/security/services/Impl/AuthServiceImpl.java index d6018b0..0be6af4 100644 --- a/src/main/java/com/security/services/Impl/AuthServiceImpl.java +++ b/src/main/java/com/security/services/Impl/AuthServiceImpl.java @@ -1,5 +1,6 @@ package com.security.services.Impl; +import com.security.DTOs.AuthResponseDTO; import com.security.DTOs.LoginRequestDTO; import com.security.DTOs.LoginResponseDTO; import com.security.DTOs.RegisterRequestDto; @@ -9,10 +10,14 @@ import com.security.Repository.RoleRepository; import com.security.Repository.UserRepository; import com.security.services.AuthService; +import com.security.services.CookieService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import com.security.services.TokenService; +//import com.security.services.TokenService; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.*; @@ -36,7 +41,6 @@ public class AuthServiceImpl implements AuthService { private final PasswordEncoder passwordEncoder; private final RoleRepository roleRepository; private final UserMapper userMapper; - // private final TokenService tokenService; private final JwtEncoder jwtEncoder; private final JwtDecoder jwtDecoder; @@ -145,6 +149,8 @@ public void logout(String accessToken) { } } + + @Override public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { if (userRepository.existsByEmail(registerRequestDto.email())) { diff --git a/src/main/java/com/security/services/Impl/CookieServiceImpl.java b/src/main/java/com/security/services/Impl/CookieServiceImpl.java new file mode 100644 index 0000000..21052e7 --- /dev/null +++ b/src/main/java/com/security/services/Impl/CookieServiceImpl.java @@ -0,0 +1,105 @@ +package com.security.services.Impl; + +import com.security.DTOs.LoginResponseDTO; +import com.security.services.CookieService; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class CookieServiceImpl implements CookieService { + @Value("${app.cookie.domain:localhost}") + private String cookieDomain; + + @Value("${app.cookie.secure:false}") + private boolean cookieSecure; + + @Value("${app.cookie.access-token-max-age:900}") + private int accessTokenMaxAge; + + @Value("${app.cookie.refresh-token-max-age:604800}") + private int refreshTokenMaxAge; + + @Override + public void setSecureTokenCookies(HttpServletResponse response, LoginResponseDTO authResponse) { + setSecureCookie(response, "access_token", authResponse.getAccessToken(), accessTokenMaxAge); + + setSecureCookie(response, "refresh_token", authResponse.getRefreshToken(), refreshTokenMaxAge); + + log.debug("Secure cookies set for user: {}", authResponse.getUser().getEmail()); + } + + @Override + public void clearTokenCookies(HttpServletResponse response) { + clearCookie(response, "access_token"); + clearCookie(response, "refresh_token"); + log.debug("Token cookies cleared"); + } + + + @Override + public String extractAccessTokenFromCookies(HttpServletRequest request) { + return extractTokenFromCookies(request, "access_token"); + } + + @Override + public String extractRefreshTokenFromCookies(HttpServletRequest request) { + return extractTokenFromCookies(request, "refresh_token"); + } + + + private String extractTokenFromCookies(HttpServletRequest request, String tokenName) { + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if (tokenName.equals(cookie.getName())) { + return cookie.getValue(); + } + } + } + return null; + } + + + private void setSecureCookie(HttpServletResponse response, String name, String value, int maxAge) { + Cookie cookie = new Cookie(name, value); + cookie.setHttpOnly(true); + cookie.setSecure(cookieSecure); + cookie.setPath("/"); + cookie.setMaxAge(maxAge); + + if (!"localhost".equals(cookieDomain)) { + cookie.setDomain(cookieDomain); + } + + response.addCookie(cookie); + + String sameSiteValue = cookieSecure ? "None" : "Strict"; + String cookieHeader = String.format("%s=%s; Path=/; HttpOnly; Max-Age=%d; SameSite=%s%s%s", + name, + value, + maxAge, + sameSiteValue, + cookieSecure ? "; Secure" : "", + !"localhost".equals(cookieDomain) ? "; Domain=" + cookieDomain : "" + ); + + response.addHeader("Set-Cookie", cookieHeader); + } + + + private void clearCookie(HttpServletResponse response, String name) { + Cookie cookie = new Cookie(name, ""); + cookie.setMaxAge(0); + cookie.setPath("/"); + cookie.setHttpOnly(true); + response.addCookie(cookie); + + response.addHeader("Set-Cookie", String.format("%s=; Path=/; HttpOnly; Max-Age=0", name)); + } + + +} diff --git a/src/main/java/com/security/services/CustomUserDetailsService.java b/src/main/java/com/security/services/Impl/CustomUserDetailsService.java similarity index 96% rename from src/main/java/com/security/services/CustomUserDetailsService.java rename to src/main/java/com/security/services/Impl/CustomUserDetailsService.java index d3ecdae..2c3cc84 100644 --- a/src/main/java/com/security/services/CustomUserDetailsService.java +++ b/src/main/java/com/security/services/Impl/CustomUserDetailsService.java @@ -1,4 +1,4 @@ -package com.security.services; +package com.security.services.Impl; import com.security.Repository.UserRepository; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/security/services/Impl/TokenServiceImpl.java b/src/main/java/com/security/services/Impl/TokenServiceImpl.java deleted file mode 100644 index c09f1ec..0000000 --- a/src/main/java/com/security/services/Impl/TokenServiceImpl.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.security.services.Impl; -import com.security.Entity.UserEntity; -import com.security.services.TokenService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.jwt.*; -import org.springframework.stereotype.Service; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.stream.Collectors; - -@Service -@RequiredArgsConstructor -@Slf4j -public class TokenServiceImpl implements TokenService { - - private final JwtEncoder jwtEncoder; - private final JwtDecoder jwtDecoder; - - @Override - public String generateTokenForUser(UserEntity user) { - try { - Instant now = Instant.now(); - - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuer("http://localhost:9091") - .issuedAt(now) - .expiresAt(now.plus(1, ChronoUnit.HOURS)) - .subject(user.getEmail()) - .claim("user_id", user.getId().toString()) - .claim("username", user.getUsername()) - .claim("email", user.getEmail()) - .claim("firstName", user.getFirstName()) - .claim("lastName", user.getLastName()) - .claim("authorities", user.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.toList())) - .claim("scope", "read write") - .build(); - - String token = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); - - log.info("Token generated successfully for user: {}", user.getEmail()); - return token; - - } catch (Exception e) { - log.error("Error generating token for user: {}", user.getEmail(), e); - throw new RuntimeException("Error al generar el token", e); - } - } - - @Override - public Instant getTokenExpiration(String token) { - try { - Jwt jwt = jwtDecoder.decode(token); - return jwt.getExpiresAt(); - } catch (JwtException e) { - log.error("Error decoding token to get expiration", e); - return Instant.now().plusSeconds(3600); - } - } - - @Override - public boolean isTokenValid(String token) { - try { - Jwt jwt = jwtDecoder.decode(token); - return jwt.getExpiresAt() != null && jwt.getExpiresAt().isAfter(Instant.now()); - } catch (JwtException e) { - log.error("Token validation failed", e); - return false; - } - } - - @Override - public void invalidateToken(String token) { - log.info("Token invalidated: {}", maskToken(token)); - } - - private String maskToken(String token) { - if (token == null || token.length() < 10) { - return "***"; - } - return token.substring(0, 10) + "..."; - } -} \ No newline at end of file diff --git a/src/main/java/com/security/services/LoginResponseService.java b/src/main/java/com/security/services/LoginResponseService.java deleted file mode 100644 index 13f8a14..0000000 --- a/src/main/java/com/security/services/LoginResponseService.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.security.services; - -import com.security.DTOs.LoginResponseDTO; -import org.springframework.stereotype.Service; - -import java.time.Instant; -import java.util.Map; - -@Service -public class LoginResponseService { - - - public Map createSafeLoginResponse(LoginResponseDTO authResponse) { - return Map.of( - "success", true, - "message", authResponse.getMessage(), - "user", Map.of( - "id", authResponse.getUser().getId(), - "username", authResponse.getUser().getUsername(), - "email", authResponse.getUser().getEmail(), - "firstName", authResponse.getUser().getFirstName(), - "lastName", authResponse.getUser().getLastName(), - "roles", authResponse.getUser().getRoles() - ), - "expiresAt", authResponse.getExpiresAt(), - "timestamp", Instant.now() - ); - } - - - private Map createUserResponse(LoginResponseDTO authResponse) { - return Map.of( - "id", authResponse.getUser().getId(), - "username", authResponse.getUser().getUsername(), - "email", authResponse.getUser().getEmail(), - "firstName", authResponse.getUser().getFirstName(), - "lastName", authResponse.getUser().getLastName(), - "roles", authResponse.getUser().getRoles() - ); - } - - - public Map createErrorResponse(String message) { - return Map.of( - "success", false, - "message", message, - "timestamp", Instant.now() - ); - } - - public Map createSuccessResponse(String message) { - return Map.of( - "success", true, - "message", message, - "timestamp", Instant.now() - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/security/services/TokenService.java b/src/main/java/com/security/services/TokenService.java deleted file mode 100644 index c78e4f3..0000000 --- a/src/main/java/com/security/services/TokenService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.security.services; - -import com.security.Entity.UserEntity; - -import java.time.Instant; - -public interface TokenService { - - String generateTokenForUser(UserEntity user); - - Instant getTokenExpiration(String token); - - boolean isTokenValid(String token); - - void invalidateToken(String token); -} \ No newline at end of file From a7ded039dcd3cf1f354531b6685f9faaef0bc8ce Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Thu, 18 Sep 2025 17:45:28 -0500 Subject: [PATCH 06/14] fix: update packages names --- .../com/security/config/DataInitializer.java | 8 +++---- .../security/config/{ => audit}/Audit.java | 5 +---- .../config/{ => audit}/AuditListener.java | 2 +- .../CookieAuthenticationFilter.java | 2 +- .../config/{ => auth}/SecurityConfig.java | 2 +- .../controllers/AdminUserController.java | 6 +++--- .../security/controllers/AuthController.java | 8 +++---- .../{DTOs => dtos}/AuthResponseDTO.java | 2 +- .../{DTOs => dtos}/LoginRequestDTO.java | 3 +-- .../{DTOs => dtos}/LoginResponseDTO.java | 2 +- .../{DTOs => dtos}/RegisterRequestDto.java | 2 +- .../{DTOs => dtos}/RoleChangeRequestDTO.java | 2 +- .../com/security/{DTOs => dtos}/RoleDTO.java | 2 +- .../{DTOs => dtos}/RolesResponseDTO.java | 2 +- .../com/security/{DTOs => dtos}/UserDTO.java | 2 +- .../{Entity => entity}/RoleEntity.java | 4 ++-- .../{Entity => entity}/UserEntity.java | 6 +++--- .../{Mappers => mappers}/RoleMapper.java | 6 +++--- .../{Mappers => mappers}/UserMapper.java | 6 +++--- .../RoleRepository.java | 4 ++-- .../UserRepository.java | 4 ++-- .../com/security/services/AuthService.java | 6 +++--- .../com/security/services/CookieService.java | 2 +- .../services/Impl/AuthServiceImpl.java | 21 +++++++------------ .../services/Impl/CookieServiceImpl.java | 2 +- .../Impl/CustomUserDetailsService.java | 2 +- .../services/Impl/UserAccountServiceImpl.java | 8 +++---- .../services/Impl/UserRoleServiceImpl.java | 12 +++++------ .../security/services/UserAccountService.java | 2 +- .../security/services/UserRoleService.java | 5 ++--- 30 files changed, 65 insertions(+), 75 deletions(-) rename src/main/java/com/security/config/{ => audit}/Audit.java (82%) rename src/main/java/com/security/config/{ => audit}/AuditListener.java (98%) rename src/main/java/com/security/config/{ => auth}/CookieAuthenticationFilter.java (98%) rename src/main/java/com/security/config/{ => auth}/SecurityConfig.java (99%) rename src/main/java/com/security/{DTOs => dtos}/AuthResponseDTO.java (83%) rename src/main/java/com/security/{DTOs => dtos}/LoginRequestDTO.java (85%) rename src/main/java/com/security/{DTOs => dtos}/LoginResponseDTO.java (92%) rename src/main/java/com/security/{DTOs => dtos}/RegisterRequestDto.java (97%) rename src/main/java/com/security/{DTOs => dtos}/RoleChangeRequestDTO.java (86%) rename src/main/java/com/security/{DTOs => dtos}/RoleDTO.java (88%) rename src/main/java/com/security/{DTOs => dtos}/RolesResponseDTO.java (83%) rename src/main/java/com/security/{DTOs => dtos}/UserDTO.java (92%) rename src/main/java/com/security/{Entity => entity}/RoleEntity.java (87%) rename src/main/java/com/security/{Entity => entity}/UserEntity.java (95%) rename src/main/java/com/security/{Mappers => mappers}/RoleMapper.java (69%) rename src/main/java/com/security/{Mappers => mappers}/UserMapper.java (84%) rename src/main/java/com/security/{Repository => repository}/RoleRepository.java (76%) rename src/main/java/com/security/{Repository => repository}/UserRepository.java (87%) diff --git a/src/main/java/com/security/config/DataInitializer.java b/src/main/java/com/security/config/DataInitializer.java index 957f34c..3b57fc6 100644 --- a/src/main/java/com/security/config/DataInitializer.java +++ b/src/main/java/com/security/config/DataInitializer.java @@ -1,9 +1,9 @@ package com.security.config; -import com.security.Entity.RoleEntity; -import com.security.Entity.UserEntity; -import com.security.Repository.RoleRepository; -import com.security.Repository.UserRepository; +import com.security.entity.RoleEntity; +import com.security.entity.UserEntity; +import com.security.repository.RoleRepository; +import com.security.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; diff --git a/src/main/java/com/security/config/Audit.java b/src/main/java/com/security/config/audit/Audit.java similarity index 82% rename from src/main/java/com/security/config/Audit.java rename to src/main/java/com/security/config/audit/Audit.java index 5f4099d..c368bf8 100644 --- a/src/main/java/com/security/config/Audit.java +++ b/src/main/java/com/security/config/audit/Audit.java @@ -1,16 +1,13 @@ -package com.security.config; +package com.security.config.audit; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.time.Instant; -import java.time.LocalDateTime; @Embeddable diff --git a/src/main/java/com/security/config/AuditListener.java b/src/main/java/com/security/config/audit/AuditListener.java similarity index 98% rename from src/main/java/com/security/config/AuditListener.java rename to src/main/java/com/security/config/audit/AuditListener.java index 0c7a7e4..034504a 100644 --- a/src/main/java/com/security/config/AuditListener.java +++ b/src/main/java/com/security/config/audit/AuditListener.java @@ -1,4 +1,4 @@ -package com.security.config; +package com.security.config.audit; import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; diff --git a/src/main/java/com/security/config/CookieAuthenticationFilter.java b/src/main/java/com/security/config/auth/CookieAuthenticationFilter.java similarity index 98% rename from src/main/java/com/security/config/CookieAuthenticationFilter.java rename to src/main/java/com/security/config/auth/CookieAuthenticationFilter.java index 3e1472a..7391229 100644 --- a/src/main/java/com/security/config/CookieAuthenticationFilter.java +++ b/src/main/java/com/security/config/auth/CookieAuthenticationFilter.java @@ -1,4 +1,4 @@ -package com.security.config; +package com.security.config.auth; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/src/main/java/com/security/config/SecurityConfig.java b/src/main/java/com/security/config/auth/SecurityConfig.java similarity index 99% rename from src/main/java/com/security/config/SecurityConfig.java rename to src/main/java/com/security/config/auth/SecurityConfig.java index 94b306b..ca7642a 100644 --- a/src/main/java/com/security/config/SecurityConfig.java +++ b/src/main/java/com/security/config/auth/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.security.config; +package com.security.config.auth; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; diff --git a/src/main/java/com/security/controllers/AdminUserController.java b/src/main/java/com/security/controllers/AdminUserController.java index 64baa21..e3b7ab8 100644 --- a/src/main/java/com/security/controllers/AdminUserController.java +++ b/src/main/java/com/security/controllers/AdminUserController.java @@ -1,8 +1,8 @@ package com.security.controllers; -import com.security.DTOs.AuthResponseDTO; -import com.security.DTOs.RoleChangeRequestDTO; -import com.security.DTOs.RolesResponseDTO; +import com.security.dtos.AuthResponseDTO; +import com.security.dtos.RoleChangeRequestDTO; +import com.security.dtos.RolesResponseDTO; import com.security.annotations.AdminAccess; import com.security.services.UserAccountService; import com.security.services.UserRoleService; diff --git a/src/main/java/com/security/controllers/AuthController.java b/src/main/java/com/security/controllers/AuthController.java index cb59d64..34c193e 100644 --- a/src/main/java/com/security/controllers/AuthController.java +++ b/src/main/java/com/security/controllers/AuthController.java @@ -1,9 +1,9 @@ package com.security.controllers; -import com.security.DTOs.AuthResponseDTO; -import com.security.DTOs.LoginRequestDTO; -import com.security.DTOs.LoginResponseDTO; -import com.security.DTOs.RegisterRequestDto; +import com.security.dtos.AuthResponseDTO; +import com.security.dtos.LoginRequestDTO; +import com.security.dtos.LoginResponseDTO; +import com.security.dtos.RegisterRequestDto; import com.security.annotations.AuthenticatedAccess; import com.security.services.AuthService; import com.security.services.CookieService; diff --git a/src/main/java/com/security/DTOs/AuthResponseDTO.java b/src/main/java/com/security/dtos/AuthResponseDTO.java similarity index 83% rename from src/main/java/com/security/DTOs/AuthResponseDTO.java rename to src/main/java/com/security/dtos/AuthResponseDTO.java index 7b5f683..3894962 100644 --- a/src/main/java/com/security/DTOs/AuthResponseDTO.java +++ b/src/main/java/com/security/dtos/AuthResponseDTO.java @@ -1,4 +1,4 @@ -package com.security.DTOs; +package com.security.dtos; import java.time.Instant; diff --git a/src/main/java/com/security/DTOs/LoginRequestDTO.java b/src/main/java/com/security/dtos/LoginRequestDTO.java similarity index 85% rename from src/main/java/com/security/DTOs/LoginRequestDTO.java rename to src/main/java/com/security/dtos/LoginRequestDTO.java index 4524c3b..5e90c0d 100644 --- a/src/main/java/com/security/DTOs/LoginRequestDTO.java +++ b/src/main/java/com/security/dtos/LoginRequestDTO.java @@ -1,8 +1,7 @@ -package com.security.DTOs; +package com.security.dtos; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; import lombok.*; @Data diff --git a/src/main/java/com/security/DTOs/LoginResponseDTO.java b/src/main/java/com/security/dtos/LoginResponseDTO.java similarity index 92% rename from src/main/java/com/security/DTOs/LoginResponseDTO.java rename to src/main/java/com/security/dtos/LoginResponseDTO.java index eb1b134..d751cca 100644 --- a/src/main/java/com/security/DTOs/LoginResponseDTO.java +++ b/src/main/java/com/security/dtos/LoginResponseDTO.java @@ -1,4 +1,4 @@ -package com.security.DTOs; +package com.security.dtos; import lombok.*; diff --git a/src/main/java/com/security/DTOs/RegisterRequestDto.java b/src/main/java/com/security/dtos/RegisterRequestDto.java similarity index 97% rename from src/main/java/com/security/DTOs/RegisterRequestDto.java rename to src/main/java/com/security/dtos/RegisterRequestDto.java index 07ab96f..dbda513 100644 --- a/src/main/java/com/security/DTOs/RegisterRequestDto.java +++ b/src/main/java/com/security/dtos/RegisterRequestDto.java @@ -1,4 +1,4 @@ -package com.security.DTOs; +package com.security.dtos; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/security/DTOs/RoleChangeRequestDTO.java b/src/main/java/com/security/dtos/RoleChangeRequestDTO.java similarity index 86% rename from src/main/java/com/security/DTOs/RoleChangeRequestDTO.java rename to src/main/java/com/security/dtos/RoleChangeRequestDTO.java index 18e3c05..464a55e 100644 --- a/src/main/java/com/security/DTOs/RoleChangeRequestDTO.java +++ b/src/main/java/com/security/dtos/RoleChangeRequestDTO.java @@ -1,4 +1,4 @@ -package com.security.DTOs; +package com.security.dtos; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/security/DTOs/RoleDTO.java b/src/main/java/com/security/dtos/RoleDTO.java similarity index 88% rename from src/main/java/com/security/DTOs/RoleDTO.java rename to src/main/java/com/security/dtos/RoleDTO.java index 55c834c..ba19d92 100644 --- a/src/main/java/com/security/DTOs/RoleDTO.java +++ b/src/main/java/com/security/dtos/RoleDTO.java @@ -1,4 +1,4 @@ -package com.security.DTOs; +package com.security.dtos; import lombok.*; diff --git a/src/main/java/com/security/DTOs/RolesResponseDTO.java b/src/main/java/com/security/dtos/RolesResponseDTO.java similarity index 83% rename from src/main/java/com/security/DTOs/RolesResponseDTO.java rename to src/main/java/com/security/dtos/RolesResponseDTO.java index 644bf7c..c6a60cd 100644 --- a/src/main/java/com/security/DTOs/RolesResponseDTO.java +++ b/src/main/java/com/security/dtos/RolesResponseDTO.java @@ -1,4 +1,4 @@ -package com.security.DTOs; +package com.security.dtos; import java.util.Set; import java.util.UUID; diff --git a/src/main/java/com/security/DTOs/UserDTO.java b/src/main/java/com/security/dtos/UserDTO.java similarity index 92% rename from src/main/java/com/security/DTOs/UserDTO.java rename to src/main/java/com/security/dtos/UserDTO.java index f9e0766..bfff417 100644 --- a/src/main/java/com/security/DTOs/UserDTO.java +++ b/src/main/java/com/security/dtos/UserDTO.java @@ -1,4 +1,4 @@ -package com.security.DTOs; +package com.security.dtos; import lombok.*; diff --git a/src/main/java/com/security/Entity/RoleEntity.java b/src/main/java/com/security/entity/RoleEntity.java similarity index 87% rename from src/main/java/com/security/Entity/RoleEntity.java rename to src/main/java/com/security/entity/RoleEntity.java index c9ccb91..c45d4d8 100644 --- a/src/main/java/com/security/Entity/RoleEntity.java +++ b/src/main/java/com/security/entity/RoleEntity.java @@ -1,6 +1,6 @@ -package com.security.Entity; +package com.security.entity; -import com.security.config.Audit; +import com.security.config.audit.Audit; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/security/Entity/UserEntity.java b/src/main/java/com/security/entity/UserEntity.java similarity index 95% rename from src/main/java/com/security/Entity/UserEntity.java rename to src/main/java/com/security/entity/UserEntity.java index 2ab3df1..305047d 100644 --- a/src/main/java/com/security/Entity/UserEntity.java +++ b/src/main/java/com/security/entity/UserEntity.java @@ -1,7 +1,7 @@ -package com.security.Entity; +package com.security.entity; -import com.security.config.Audit; -import com.security.config.AuditListener; +import com.security.config.audit.Audit; +import com.security.config.audit.AuditListener; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/security/Mappers/RoleMapper.java b/src/main/java/com/security/mappers/RoleMapper.java similarity index 69% rename from src/main/java/com/security/Mappers/RoleMapper.java rename to src/main/java/com/security/mappers/RoleMapper.java index 336dc7b..352232f 100644 --- a/src/main/java/com/security/Mappers/RoleMapper.java +++ b/src/main/java/com/security/mappers/RoleMapper.java @@ -1,7 +1,7 @@ -package com.security.Mappers; +package com.security.mappers; -import com.security.DTOs.RoleDTO; -import com.security.Entity.RoleEntity; +import com.security.dtos.RoleDTO; +import com.security.entity.RoleEntity; import com.security.config.MapStructConfig; import org.mapstruct.Mapper; diff --git a/src/main/java/com/security/Mappers/UserMapper.java b/src/main/java/com/security/mappers/UserMapper.java similarity index 84% rename from src/main/java/com/security/Mappers/UserMapper.java rename to src/main/java/com/security/mappers/UserMapper.java index 8edd87c..e5574ca 100644 --- a/src/main/java/com/security/Mappers/UserMapper.java +++ b/src/main/java/com/security/mappers/UserMapper.java @@ -1,7 +1,7 @@ -package com.security.Mappers; +package com.security.mappers; -import com.security.DTOs.UserDTO; -import com.security.Entity.UserEntity; +import com.security.dtos.UserDTO; +import com.security.entity.UserEntity; import com.security.config.MapStructConfig; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/src/main/java/com/security/Repository/RoleRepository.java b/src/main/java/com/security/repository/RoleRepository.java similarity index 76% rename from src/main/java/com/security/Repository/RoleRepository.java rename to src/main/java/com/security/repository/RoleRepository.java index a702e97..9b0e5c4 100644 --- a/src/main/java/com/security/Repository/RoleRepository.java +++ b/src/main/java/com/security/repository/RoleRepository.java @@ -1,6 +1,6 @@ -package com.security.Repository; +package com.security.repository; -import com.security.Entity.RoleEntity; +import com.security.entity.RoleEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/main/java/com/security/Repository/UserRepository.java b/src/main/java/com/security/repository/UserRepository.java similarity index 87% rename from src/main/java/com/security/Repository/UserRepository.java rename to src/main/java/com/security/repository/UserRepository.java index 38b3ae9..5a89f92 100644 --- a/src/main/java/com/security/Repository/UserRepository.java +++ b/src/main/java/com/security/repository/UserRepository.java @@ -1,6 +1,6 @@ -package com.security.Repository; +package com.security.repository; -import com.security.Entity.UserEntity; +import com.security.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/main/java/com/security/services/AuthService.java b/src/main/java/com/security/services/AuthService.java index 2ca0bbb..93960ec 100644 --- a/src/main/java/com/security/services/AuthService.java +++ b/src/main/java/com/security/services/AuthService.java @@ -1,8 +1,8 @@ package com.security.services; -import com.security.DTOs.LoginRequestDTO; -import com.security.DTOs.LoginResponseDTO; -import com.security.DTOs.RegisterRequestDto; +import com.security.dtos.LoginRequestDTO; +import com.security.dtos.LoginResponseDTO; +import com.security.dtos.RegisterRequestDto; public interface AuthService { diff --git a/src/main/java/com/security/services/CookieService.java b/src/main/java/com/security/services/CookieService.java index dd3e002..81c4117 100644 --- a/src/main/java/com/security/services/CookieService.java +++ b/src/main/java/com/security/services/CookieService.java @@ -1,7 +1,7 @@ package com.security.services; -import com.security.DTOs.LoginResponseDTO; +import com.security.dtos.LoginResponseDTO; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/security/services/Impl/AuthServiceImpl.java b/src/main/java/com/security/services/Impl/AuthServiceImpl.java index 0be6af4..1dbd2c0 100644 --- a/src/main/java/com/security/services/Impl/AuthServiceImpl.java +++ b/src/main/java/com/security/services/Impl/AuthServiceImpl.java @@ -1,23 +1,18 @@ package com.security.services.Impl; -import com.security.DTOs.AuthResponseDTO; -import com.security.DTOs.LoginRequestDTO; -import com.security.DTOs.LoginResponseDTO; -import com.security.DTOs.RegisterRequestDto; -import com.security.Entity.RoleEntity; -import com.security.Entity.UserEntity; -import com.security.Mappers.UserMapper; -import com.security.Repository.RoleRepository; -import com.security.Repository.UserRepository; +import com.security.dtos.LoginRequestDTO; +import com.security.dtos.LoginResponseDTO; +import com.security.dtos.RegisterRequestDto; +import com.security.entity.RoleEntity; +import com.security.entity.UserEntity; +import com.security.mappers.UserMapper; +import com.security.repository.RoleRepository; +import com.security.repository.UserRepository; import com.security.services.AuthService; -import com.security.services.CookieService; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; //import com.security.services.TokenService; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.*; diff --git a/src/main/java/com/security/services/Impl/CookieServiceImpl.java b/src/main/java/com/security/services/Impl/CookieServiceImpl.java index 21052e7..46b5327 100644 --- a/src/main/java/com/security/services/Impl/CookieServiceImpl.java +++ b/src/main/java/com/security/services/Impl/CookieServiceImpl.java @@ -1,6 +1,6 @@ package com.security.services.Impl; -import com.security.DTOs.LoginResponseDTO; +import com.security.dtos.LoginResponseDTO; import com.security.services.CookieService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/security/services/Impl/CustomUserDetailsService.java b/src/main/java/com/security/services/Impl/CustomUserDetailsService.java index 2c3cc84..52c95c9 100644 --- a/src/main/java/com/security/services/Impl/CustomUserDetailsService.java +++ b/src/main/java/com/security/services/Impl/CustomUserDetailsService.java @@ -1,6 +1,6 @@ package com.security.services.Impl; -import com.security.Repository.UserRepository; +import com.security.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; diff --git a/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java b/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java index a70796b..73e8a6e 100644 --- a/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java +++ b/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java @@ -1,9 +1,9 @@ package com.security.services.Impl; -import com.security.DTOs.AuthResponseDTO; -import com.security.Entity.UserEntity; -import com.security.Repository.UserRepository; -import com.security.config.Audit; +import com.security.dtos.AuthResponseDTO; +import com.security.entity.UserEntity; +import com.security.repository.UserRepository; +import com.security.config.audit.Audit; import com.security.services.UserAccountService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java b/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java index 23b1fdd..2815148 100644 --- a/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java +++ b/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java @@ -1,11 +1,11 @@ package com.security.services.Impl; -import com.security.DTOs.AuthResponseDTO; -import com.security.DTOs.RolesResponseDTO; -import com.security.Entity.RoleEntity; -import com.security.Entity.UserEntity; -import com.security.Repository.RoleRepository; -import com.security.Repository.UserRepository; +import com.security.dtos.AuthResponseDTO; +import com.security.dtos.RolesResponseDTO; +import com.security.entity.RoleEntity; +import com.security.entity.UserEntity; +import com.security.repository.RoleRepository; +import com.security.repository.UserRepository; import com.security.services.UserRoleService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/security/services/UserAccountService.java b/src/main/java/com/security/services/UserAccountService.java index be937b2..239a7a2 100644 --- a/src/main/java/com/security/services/UserAccountService.java +++ b/src/main/java/com/security/services/UserAccountService.java @@ -1,6 +1,6 @@ package com.security.services; -import com.security.DTOs.AuthResponseDTO; +import com.security.dtos.AuthResponseDTO; import java.util.UUID; diff --git a/src/main/java/com/security/services/UserRoleService.java b/src/main/java/com/security/services/UserRoleService.java index 4db678f..6ec2e5f 100644 --- a/src/main/java/com/security/services/UserRoleService.java +++ b/src/main/java/com/security/services/UserRoleService.java @@ -1,9 +1,8 @@ package com.security.services; -import com.security.DTOs.AuthResponseDTO; -import com.security.DTOs.RolesResponseDTO; +import com.security.dtos.AuthResponseDTO; +import com.security.dtos.RolesResponseDTO; -import java.util.Set; import java.util.UUID; public interface UserRoleService { From 4f11e19ce5ebe197983ae0b0bb26952f26bc9898 Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Thu, 25 Sep 2025 09:29:57 -0500 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20actualizar=20Dockerfile=20y=20agr?= =?UTF-8?q?egar=20configuraci=C3=B3n=20de=20Kafka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile.dev | 67 +++----------------------------------------------- 1 file changed, 3 insertions(+), 64 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 9b2c43f..13a088f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,70 +1,9 @@ FROM openjdk:21-jdk-slim WORKDIR /app -COPY pom.xml . -COPY mvnw . COPY .mvn .mvn +COPY mvnw pom.xml ./ RUN chmod +x mvnw -RUN ./mvnw dependency:go-offline +RUN ./mvnw -T 4 dependency:go-offline COPY src ./src EXPOSE 9091 5005 -CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"] - -# FROM openjdk:21-jdk-slim - -# # Instalar Maven -# RUN apt-get update && apt-get install -y maven && rm -rf /var/lib/apt/lists/* - -# WORKDIR /app - -# # ✅ Instalar security-common primero -# COPY security-common /app/security-common -# WORKDIR /app/security-common -# RUN mvn clean install -DskipTests - -# # ✅ Construir msvc-security -# WORKDIR /app/msvc-security -# COPY msvc-security/pom.xml . -# COPY msvc-security/mvnw . -# COPY msvc-security/.mvn .mvn -# RUN chmod +x mvnw - -# COPY msvc-security/src ./src - -# EXPOSE 9091 -# CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.fork=false"] - - - -# FROM openjdk:21-jdk-slim - -# RUN apt-get update && \ -# apt-get install -y maven && \ -# rm -rf /var/lib/apt/lists/* && \ -# mvn --version - -# WORKDIR /app - -# COPY security-common/pom.xml /app/security-common/pom.xml -# COPY msvc-security/pom.xml /app/msvc-security/pom.xml - -# WORKDIR /app/security-common -# RUN mvn dependency:resolve || true - -# COPY security-common /app/security-common -# RUN mvn clean install -DskipTests -q - -# WORKDIR /app/msvc-security -# COPY msvc-security/mvnw . -# COPY msvc-security/.mvn .mvn -# RUN chmod +x mvnw - -# RUN ./mvnw dependency:go-offline -q || true - -# COPY msvc-security/src ./src - -# EXPOSE 9091 -# ENV SPRING_PROFILES_ACTIVE=dev - -# CMD ["./mvnw", "spring-boot:run", \ -# "-Dspring-boot.run.fork=false", \ -# "-Dspring-boot.run.jvmArguments=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"] \ No newline at end of file +CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"] \ No newline at end of file From 66de0d950623b029717ea739f098a97141a42e19 Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Sun, 28 Sep 2025 17:35:38 -0500 Subject: [PATCH 08/14] fix: add config for kafka producer --- pom.xml | 27 ++++--- .../java/com/security/config/KafkaConfig.java | 79 +++++++++++++++++++ .../security/config/auth/SecurityConfig.java | 3 +- .../security/controllers/HelloController.java | 21 ++++- .../security/events/NotificationEvent.java | 11 +++ .../services/Impl/AuthServiceImpl.java | 17 +++- .../Impl/NotificationServiceImpl.java | 24 ++++++ 7 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/security/config/KafkaConfig.java create mode 100644 src/main/java/com/security/events/NotificationEvent.java create mode 100644 src/main/java/com/security/services/Impl/NotificationServiceImpl.java diff --git a/pom.xml b/pom.xml index 7c8def6..28f6c88 100644 --- a/pom.xml +++ b/pom.xml @@ -27,12 +27,12 @@ UTF-8 - - - - - - + + + + + + @@ -141,13 +141,16 @@ ${mapstruct.version} provided + + org.springframework.kafka + spring-kafka + - - - - - - + + + + + diff --git a/src/main/java/com/security/config/KafkaConfig.java b/src/main/java/com/security/config/KafkaConfig.java new file mode 100644 index 0000000..7f071af --- /dev/null +++ b/src/main/java/com/security/config/KafkaConfig.java @@ -0,0 +1,79 @@ +package com.security.config; + +import com.security.events.NotificationEvent; +import lombok.RequiredArgsConstructor; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.TopicBuilder; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; +import org.springframework.kafka.support.serializer.JsonSerializer; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +@RequiredArgsConstructor +public class KafkaConfig { + + // private final KafkaProperties kafkaProperties; + @Value("${spring.kafka.producer.bootstrap-servers}") + private String bootstrapServers; + @Value("${spring.kafka.producer.acks}") + private String acks; + @Value("${spring.kafka.producer.properties.delivery.timeout.ms}") + private String deliveryTimeout; + @Value("${spring.kafka.producer.properties.linger.ms}") + private String linger; +// @Value("${spring.kafka.producer.properties.request.timeout.ms}") +// private String requestTimeout; + + @Value("${spring.kafka.producer.properties.enable.idempotence}") + private boolean idempotence; + @Value("${spring.kafka.producer.properties.max.in.flight.requests.per.connection:5}") + private Integer inflightRequests; + + @Bean + Map producerConfigs() { + Map config = new HashMap<>(); + config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + config.putIfAbsent(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + config.putIfAbsent(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + config.putIfAbsent(ProducerConfig.ACKS_CONFIG, acks); + config.putIfAbsent(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG,deliveryTimeout); + config.putIfAbsent(ProducerConfig.LINGER_MS_CONFIG, linger); + config.putIfAbsent(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, idempotence); + config.putIfAbsent(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inflightRequests); + config.putIfAbsent(ProducerConfig.RETRIES_CONFIG, 10); + + + return config; + } + + @Bean + ProducerFactory producerFactory() { + return new DefaultKafkaProducerFactory<>(producerConfigs()); + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate(producerFactory()); + } + + @Bean + NewTopic createNotificationTopic() { + return TopicBuilder + .name("user-created-event-topic") + .partitions(1) + .replicas(1) + .configs(Map.of("min.insync.replicas", "1")) + .build(); + } + +} diff --git a/src/main/java/com/security/config/auth/SecurityConfig.java b/src/main/java/com/security/config/auth/SecurityConfig.java index ca7642a..feb31da 100644 --- a/src/main/java/com/security/config/auth/SecurityConfig.java +++ b/src/main/java/com/security/config/auth/SecurityConfig.java @@ -87,7 +87,8 @@ public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CookieA "/auth/login", "/auth/refresh", "/auth/register", - "/saludo", + "/test/saludo", + "/test/notification", "/" ).permitAll() .anyRequest().authenticated() diff --git a/src/main/java/com/security/controllers/HelloController.java b/src/main/java/com/security/controllers/HelloController.java index 27c73dc..64f5fb1 100644 --- a/src/main/java/com/security/controllers/HelloController.java +++ b/src/main/java/com/security/controllers/HelloController.java @@ -1,14 +1,27 @@ package com.security.controllers; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; +import com.security.services.Impl.NotificationServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +@RequestMapping("/test") @RestController +@RequiredArgsConstructor public class HelloController { + + private final NotificationServiceImpl notificationService; + @GetMapping("/saludo") - public String saludo() { - return "Hola Microservicio Security"; + public ResponseEntity saludo() { + return ResponseEntity.ok("Hola Microservicio Security"); + } + + @PostMapping("/notification") + public ResponseEntity sendNotification(@RequestBody String message) { + notificationService.sendNotification(message); + return ResponseEntity.ok("Mensaje enviado a kafka: " + message); } } diff --git a/src/main/java/com/security/events/NotificationEvent.java b/src/main/java/com/security/events/NotificationEvent.java new file mode 100644 index 0000000..c91bf2f --- /dev/null +++ b/src/main/java/com/security/events/NotificationEvent.java @@ -0,0 +1,11 @@ +package com.security.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +public record NotificationEvent( + String message +) { +} \ No newline at end of file diff --git a/src/main/java/com/security/services/Impl/AuthServiceImpl.java b/src/main/java/com/security/services/Impl/AuthServiceImpl.java index 1dbd2c0..6696c34 100644 --- a/src/main/java/com/security/services/Impl/AuthServiceImpl.java +++ b/src/main/java/com/security/services/Impl/AuthServiceImpl.java @@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j; //import com.security.services.TokenService; import org.springframework.http.HttpStatus; +import org.springframework.kafka.core.KafkaTemplate; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.*; @@ -22,6 +23,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -38,6 +40,7 @@ public class AuthServiceImpl implements AuthService { private final UserMapper userMapper; private final JwtEncoder jwtEncoder; private final JwtDecoder jwtDecoder; +// private final KafkaTemplate kafkaTemplate; private final Set validRefreshTokens = ConcurrentHashMap.newKeySet(); @@ -145,7 +148,6 @@ public void logout(String accessToken) { } - @Override public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { if (userRepository.existsByEmail(registerRequestDto.email())) { @@ -176,6 +178,19 @@ public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { .build(); userRepository.save(user); +// +// Map event = Map.of( +// "type", "user.created", +// "user", Map.of( +// "id", user.getId(), +// "username", user.getUsername(), +// "email", user.getEmail(), +// "firstName", user.getFirstName(), +// "lastName", user.getLastName() +// ) +// ); +// kafkaTemplate.send("user.created", user.getId().toString(), event); + String accessToken = generateAccessToken(user); String refreshToken = generateRefreshToken(user); validRefreshTokens.add(refreshToken); diff --git a/src/main/java/com/security/services/Impl/NotificationServiceImpl.java b/src/main/java/com/security/services/Impl/NotificationServiceImpl.java new file mode 100644 index 0000000..750b037 --- /dev/null +++ b/src/main/java/com/security/services/Impl/NotificationServiceImpl.java @@ -0,0 +1,24 @@ +package com.security.services.Impl; + +import com.security.events.NotificationEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Slf4j +@RequiredArgsConstructor +public class NotificationServiceImpl { + private final KafkaTemplate kafkaTemplate; + + @Transactional + public void sendNotification(String message) { + log.info("Antes de publicar el mensaje"); + NotificationEvent event = new NotificationEvent(message); + kafkaTemplate.send("user-created-event-topic", event); + log.info("Mensaje enviado {}", event); + } + +} From 7ec2e91e3a42790330e781139289d524230feae7 Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Mon, 29 Sep 2025 11:33:59 -0500 Subject: [PATCH 09/14] fix: add config for kafka producer --- pom.xml | 14 +------------- .../config/{ => auth}/AuthProperties.java | 2 +- .../KafkaProducerConfig.java} | 18 +++++++++++------- .../com/security/events/NotificationEvent.java | 5 ----- src/main/resources/application.yml | 9 --------- src/main/resources/banner.txt | 8 ++++++++ 6 files changed, 21 insertions(+), 35 deletions(-) rename src/main/java/com/security/config/{ => auth}/AuthProperties.java (95%) rename src/main/java/com/security/config/{KafkaConfig.java => kafka/KafkaProducerConfig.java} (86%) delete mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/banner.txt diff --git a/pom.xml b/pom.xml index 28f6c88..838e28a 100644 --- a/pom.xml +++ b/pom.xml @@ -27,13 +27,7 @@ UTF-8 - - - - - - - + org.springframework.boot @@ -146,12 +140,6 @@ spring-kafka - - - - - - org.springframework.security diff --git a/src/main/java/com/security/config/AuthProperties.java b/src/main/java/com/security/config/auth/AuthProperties.java similarity index 95% rename from src/main/java/com/security/config/AuthProperties.java rename to src/main/java/com/security/config/auth/AuthProperties.java index 68d548a..62cfd4a 100644 --- a/src/main/java/com/security/config/AuthProperties.java +++ b/src/main/java/com/security/config/auth/AuthProperties.java @@ -1,4 +1,4 @@ -package com.security.config; +package com.security.config.auth; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; diff --git a/src/main/java/com/security/config/KafkaConfig.java b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java similarity index 86% rename from src/main/java/com/security/config/KafkaConfig.java rename to src/main/java/com/security/config/kafka/KafkaProducerConfig.java index 7f071af..1214342 100644 --- a/src/main/java/com/security/config/KafkaConfig.java +++ b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java @@ -1,12 +1,12 @@ -package com.security.config; +package com.security.config.kafka; import com.security.events.NotificationEvent; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.config.TopicBuilder; @@ -20,9 +20,9 @@ @Configuration @RequiredArgsConstructor -public class KafkaConfig { +@Slf4j +public class KafkaProducerConfig { - // private final KafkaProperties kafkaProperties; @Value("${spring.kafka.producer.bootstrap-servers}") private String bootstrapServers; @Value("${spring.kafka.producer.acks}") @@ -31,25 +31,29 @@ public class KafkaConfig { private String deliveryTimeout; @Value("${spring.kafka.producer.properties.linger.ms}") private String linger; -// @Value("${spring.kafka.producer.properties.request.timeout.ms}") -// private String requestTimeout; + @Value("${spring.kafka.producer.properties.request.timeout.ms}") + private String requestTimeout; @Value("${spring.kafka.producer.properties.enable.idempotence}") private boolean idempotence; @Value("${spring.kafka.producer.properties.max.in.flight.requests.per.connection:5}") private Integer inflightRequests; + @Bean Map producerConfigs() { Map config = new HashMap<>(); + config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); config.putIfAbsent(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); config.putIfAbsent(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); config.putIfAbsent(ProducerConfig.ACKS_CONFIG, acks); - config.putIfAbsent(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG,deliveryTimeout); + config.putIfAbsent(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, deliveryTimeout); config.putIfAbsent(ProducerConfig.LINGER_MS_CONFIG, linger); + config.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, requestTimeout); config.putIfAbsent(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, idempotence); config.putIfAbsent(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inflightRequests); +// config.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE); //Optional ya que por defecto ya lo es config.putIfAbsent(ProducerConfig.RETRIES_CONFIG, 10); diff --git a/src/main/java/com/security/events/NotificationEvent.java b/src/main/java/com/security/events/NotificationEvent.java index c91bf2f..fe332b3 100644 --- a/src/main/java/com/security/events/NotificationEvent.java +++ b/src/main/java/com/security/events/NotificationEvent.java @@ -1,10 +1,5 @@ package com.security.events; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - - public record NotificationEvent( String message ) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 2ae2fcd..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,9 +0,0 @@ -spring: - application: - name: msvc-security -springdoc: - api-docs: - path: /v3/api-docs - swagger-ui: - path: / - url: /v3/api-docs \ No newline at end of file diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..02a980d --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,8 @@ +______ ____________ __________ _____________________________ _________________________ __ +___ |/ /_ ___/_ | / /_ ____/ __ ___/__ ____/_ ____/_ / / /__ __ \___ _/__ __/ \/ / +__ /|_/ /_____ \__ | / /_ / ____________ \__ __/ _ / _ / / /__ /_/ /__ / __ / __ / +_ / / / ____/ /__ |/ / / /___/_____/___/ /_ /___ / /___ / /_/ / _ _, _/__/ / _ / _ / +/_/ /_/ /____/ _____/ \____/ /____/ /_____/ \____/ \____/ /_/ |_| /___/ /_/ /_/ + +${application.title} ${application.version} +Powered by Spring Boot ${spring-boot.version} \ No newline at end of file From 0cd50dd11d964249c049872b21e75c471ff72da6 Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Mon, 29 Sep 2025 13:51:25 -0500 Subject: [PATCH 10/14] =?UTF-8?q?refactor:=20simplificar=20configuraci?= =?UTF-8?q?=C3=B3n=20del=20productor=20de=20Kafka=20y=20agregar=20mapeo=20?= =?UTF-8?q?de=20tipo=20para=20NotificationEvent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/kafka/KafkaProducerConfig.java | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/security/config/kafka/KafkaProducerConfig.java b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java index 1214342..bcead29 100644 --- a/src/main/java/com/security/config/kafka/KafkaProducerConfig.java +++ b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java @@ -19,7 +19,6 @@ import java.util.Map; @Configuration -@RequiredArgsConstructor @Slf4j public class KafkaProducerConfig { @@ -39,24 +38,20 @@ public class KafkaProducerConfig { @Value("${spring.kafka.producer.properties.max.in.flight.requests.per.connection:5}") private Integer inflightRequests; - - @Bean Map producerConfigs() { Map config = new HashMap<>(); config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - config.putIfAbsent(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - config.putIfAbsent(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); - config.putIfAbsent(ProducerConfig.ACKS_CONFIG, acks); - config.putIfAbsent(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, deliveryTimeout); - config.putIfAbsent(ProducerConfig.LINGER_MS_CONFIG, linger); + config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + config.put(ProducerConfig.ACKS_CONFIG, acks); + config.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, deliveryTimeout); + config.put(ProducerConfig.LINGER_MS_CONFIG, linger); config.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, requestTimeout); - config.putIfAbsent(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, idempotence); - config.putIfAbsent(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inflightRequests); -// config.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE); //Optional ya que por defecto ya lo es - config.putIfAbsent(ProducerConfig.RETRIES_CONFIG, 10); - - + config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, idempotence); + config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inflightRequests); + config.put(JsonSerializer.TYPE_MAPPINGS, "NotificationEvent:com.security.events.NotificationEvent"); + config.put(ProducerConfig.RETRIES_CONFIG, 10); return config; } From f3ba89d9b3df9ac885f726c04cf0b2343c8592b7 Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Mon, 29 Sep 2025 18:24:44 -0500 Subject: [PATCH 11/14] send message with user created for microservices members --- .../com/security/config/CommonPointcuts.java | 4 +- .../com/security/config/DataInitializer.java | 105 ------------------ .../com/security/config/RoleInitializer.java | 34 ++++++ .../config/kafka/KafkaProducerConfig.java | 10 +- .../java/com/security/entity/UserEntity.java | 10 +- .../com/security/events/CreatedUserEvent.java | 10 ++ .../security/repository/UserRepository.java | 8 -- .../services/Impl/AuthServiceImpl.java | 54 +++------ .../Impl/CustomUserDetailsService.java | 11 +- .../Impl/NotificationServiceImpl.java | 2 +- 10 files changed, 77 insertions(+), 171 deletions(-) delete mode 100644 src/main/java/com/security/config/DataInitializer.java create mode 100644 src/main/java/com/security/config/RoleInitializer.java create mode 100644 src/main/java/com/security/events/CreatedUserEvent.java diff --git a/src/main/java/com/security/config/CommonPointcuts.java b/src/main/java/com/security/config/CommonPointcuts.java index d7f3d95..7f37b67 100644 --- a/src/main/java/com/security/config/CommonPointcuts.java +++ b/src/main/java/com/security/config/CommonPointcuts.java @@ -9,8 +9,8 @@ @Component public class CommonPointcuts { @Pointcut("execution(* com.security.services.*.*(..))") - public void greetingLoggerServices(){}; + public void greetingLoggerServices(){} @Pointcut("execution(* com.security.controllers.*.*(..))") - public void greetingLoggerControllers(){}; + public void greetingLoggerControllers(){} } diff --git a/src/main/java/com/security/config/DataInitializer.java b/src/main/java/com/security/config/DataInitializer.java deleted file mode 100644 index 4d8a788..0000000 --- a/src/main/java/com/security/config/DataInitializer.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.security.config; - -import com.security.entity.RoleEntity; -import com.security.entity.UserEntity; -import com.security.repository.RoleRepository; -import com.security.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.CommandLineRunner; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -import java.util.Set; - -@Component -@RequiredArgsConstructor -@Slf4j -public class DataInitializer implements CommandLineRunner { - - private final UserRepository userRepository; - private final RoleRepository roleRepository; - private final PasswordEncoder passwordEncoder; - - - - @Override - public void run(String... args) throws Exception { - RoleEntity adminRole = roleRepository.findByName("ADMIN") - .orElseGet(() -> { - log.info("Creating ADMIN role"); - return roleRepository.save(RoleEntity.builder() - .name("ADMIN") - .description("Administrator role") - .build()); - }); - - RoleEntity userRole = roleRepository.findByName("USER") - .orElseGet(() -> { - log.info("Creating USER role"); - return roleRepository.save(RoleEntity.builder() - .name("USER") - .description("User role") - .build()); - }); - - RoleEntity trainerRole = roleRepository.findByName("TRAINER") - .orElseGet(() -> { - log.info("Creating TRAINER role"); - return roleRepository.save(RoleEntity.builder() - .name("TRAINER") - .description("Trainer role") - .build()); - }); - - if (!userRepository.existsByUsername("admin")) { - String encodedPassword = passwordEncoder.encode("admin123"); - log.info("Creating admin user with encoded password length: {}", encodedPassword.length()); - - UserEntity admin = UserEntity.builder() - .username("admin") - .email("admin@fitdesk.com") - .password(encodedPassword) - .firstName("Admin") - .lastName("User") - .roles(Set.of(adminRole)) - .build(); - userRepository.save(admin); - log.info("Admin user created successfully"); - } else { - log.info("Admin user already exists"); - } - - if (!userRepository.existsByUsername("user")) { - String encodedPassword = passwordEncoder.encode("user123"); - log.info("Creating regular user"); - - UserEntity user = UserEntity.builder() - .username("user") - .email("user@fitdesk.com") - .password(encodedPassword) - .firstName("Regular") - .lastName("User") - .roles(Set.of(userRole)) - .build(); - userRepository.save(user); - log.info("Regular user created successfully"); - } - - if (!userRepository.existsByUsername("trainer")) { - String encodedPassword = passwordEncoder.encode("trainer123"); - log.info("Creating trainer user"); - - UserEntity trainer = UserEntity.builder() - .username("trainer") - .email("trainer@fitdesk.com") - .password(encodedPassword) - .firstName("Gym") - .lastName("Trainer") - .roles(Set.of(trainerRole)) - .build(); - userRepository.save(trainer); - log.info("Trainer user created successfully"); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/security/config/RoleInitializer.java b/src/main/java/com/security/config/RoleInitializer.java new file mode 100644 index 0000000..e7cbf95 --- /dev/null +++ b/src/main/java/com/security/config/RoleInitializer.java @@ -0,0 +1,34 @@ +package com.security.config; + +import com.security.entity.RoleEntity; +import com.security.repository.RoleRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + + +@Component +@RequiredArgsConstructor +@Slf4j +public class RoleInitializer implements CommandLineRunner { + private final RoleRepository roleRepository; + + @Override + public void run(String... args) throws Exception { + createRoleIfNotExists("ADMIN", "Administrator role"); + createRoleIfNotExists("USER", "User role"); + createRoleIfNotExists("TRAINER", "Trainer role"); + } + + private void createRoleIfNotExists(String name, String description) { + roleRepository.findByName(name) + .orElseGet(() -> { + log.info("Creando {} role", name); + return roleRepository.save(RoleEntity.builder() + .name(name) + .description(description) + .build()); + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/security/config/kafka/KafkaProducerConfig.java b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java index bcead29..c0c296b 100644 --- a/src/main/java/com/security/config/kafka/KafkaProducerConfig.java +++ b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java @@ -1,7 +1,5 @@ package com.security.config.kafka; -import com.security.events.NotificationEvent; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.producer.ProducerConfig; @@ -50,19 +48,19 @@ Map producerConfigs() { config.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, requestTimeout); config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, idempotence); config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inflightRequests); - config.put(JsonSerializer.TYPE_MAPPINGS, "NotificationEvent:com.security.events.NotificationEvent"); + config.put(JsonSerializer.TYPE_MAPPINGS, "NotificationEvent:com.security.events.NotificationEvent,CreatedUserEvent:com.security.events.CreatedUserEvent"); config.put(ProducerConfig.RETRIES_CONFIG, 10); return config; } @Bean - ProducerFactory producerFactory() { + ProducerFactory producerFactory() { return new DefaultKafkaProducerFactory<>(producerConfigs()); } @Bean - public KafkaTemplate kafkaTemplate() { - return new KafkaTemplate(producerFactory()); + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate(producerFactory()); } @Bean diff --git a/src/main/java/com/security/entity/UserEntity.java b/src/main/java/com/security/entity/UserEntity.java index 305047d..b6141f3 100644 --- a/src/main/java/com/security/entity/UserEntity.java +++ b/src/main/java/com/security/entity/UserEntity.java @@ -28,13 +28,13 @@ public class UserEntity implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - private String username; +// private String username; private String email; private String password; - private String firstName; - private String lastName; - private String dni; - private String phone; +// private String firstName; +// private String lastName; +// private String dni; +// private String phone; @Embedded private Audit audit; diff --git a/src/main/java/com/security/events/CreatedUserEvent.java b/src/main/java/com/security/events/CreatedUserEvent.java new file mode 100644 index 0000000..9393928 --- /dev/null +++ b/src/main/java/com/security/events/CreatedUserEvent.java @@ -0,0 +1,10 @@ +package com.security.events; + +public record CreatedUserEvent( + String userId, + String firstName, + String lastName, + String dni, + String phone +) { +} diff --git a/src/main/java/com/security/repository/UserRepository.java b/src/main/java/com/security/repository/UserRepository.java index 5a89f92..9a91359 100644 --- a/src/main/java/com/security/repository/UserRepository.java +++ b/src/main/java/com/security/repository/UserRepository.java @@ -7,17 +7,9 @@ import java.util.UUID; public interface UserRepository extends JpaRepository { - Optional findByUsername(String username); - Optional findByEmail(String email); - boolean existsByUsername(String username); - boolean existsByEmail(String email); - boolean existsByDni(String dni); - - boolean existsByPhone(String phone); - Optional findByDni(String dni); } diff --git a/src/main/java/com/security/services/Impl/AuthServiceImpl.java b/src/main/java/com/security/services/Impl/AuthServiceImpl.java index 6696c34..771d9e0 100644 --- a/src/main/java/com/security/services/Impl/AuthServiceImpl.java +++ b/src/main/java/com/security/services/Impl/AuthServiceImpl.java @@ -5,13 +5,13 @@ import com.security.dtos.RegisterRequestDto; import com.security.entity.RoleEntity; import com.security.entity.UserEntity; +import com.security.events.CreatedUserEvent; import com.security.mappers.UserMapper; import com.security.repository.RoleRepository; import com.security.repository.UserRepository; import com.security.services.AuthService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -//import com.security.services.TokenService; import org.springframework.http.HttpStatus; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.security.authentication.BadCredentialsException; @@ -23,7 +23,6 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -40,7 +39,7 @@ public class AuthServiceImpl implements AuthService { private final UserMapper userMapper; private final JwtEncoder jwtEncoder; private final JwtDecoder jwtDecoder; -// private final KafkaTemplate kafkaTemplate; + private final KafkaTemplate kafkaTemplate; private final Set validRefreshTokens = ConcurrentHashMap.newKeySet(); @@ -154,49 +153,34 @@ public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { throw new ResponseStatusException(HttpStatus.CONFLICT, "Error al inicial sesion"); } - if (userRepository.existsByUsername(registerRequestDto.firstName())) { - throw new ResponseStatusException(HttpStatus.CONFLICT, "Error al registrarse verifica tus credenciales"); - } - - if (userRepository.existsByDni(registerRequestDto.dni())) { - throw new ResponseStatusException(HttpStatus.CONFLICT, "Error al registrarse verifica tus credenciales"); - } RoleEntity userRole = roleRepository.findByName("USER").orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Error al encontrar el rol de Usuario")); UserEntity user = UserEntity.builder() - .firstName(registerRequestDto.firstName()) - .lastName(registerRequestDto.lastName()) .email(registerRequestDto.email()) - .username((registerRequestDto.firstName() + "." + registerRequestDto.lastName()).toLowerCase()) - .dni(registerRequestDto.dni()) - .phone(registerRequestDto.phone()) .password(passwordEncoder.encode(registerRequestDto.password())) .roles(Set.of(userRole)) .enabled(true) .build(); userRepository.save(user); -// -// Map event = Map.of( -// "type", "user.created", -// "user", Map.of( -// "id", user.getId(), -// "username", user.getUsername(), -// "email", user.getEmail(), -// "firstName", user.getFirstName(), -// "lastName", user.getLastName() -// ) -// ); -// kafkaTemplate.send("user.created", user.getId().toString(), event); - String accessToken = generateAccessToken(user); - String refreshToken = generateRefreshToken(user); - validRefreshTokens.add(refreshToken); + CreatedUserEvent event = new CreatedUserEvent( + user.getId().toString(), + registerRequestDto.firstName(), + registerRequestDto.lastName(), + registerRequestDto.dni(), + registerRequestDto.phone() + ); + + log.info("Enviando evento {}", event); + kafkaTemplate.send("user-created-event-topic", event); + log.info("Evento enviado {}", event); + return LoginResponseDTO.builder() - .accessToken(accessToken) - .refreshToken(refreshToken) + .accessToken(generateAccessToken(user)) + .refreshToken(generateRefreshToken(user)) .tokenType("Bearer") .expiresAt(Instant.now().plus(15, ChronoUnit.MINUTES)) .scope("read write") @@ -221,10 +205,6 @@ private String generateRefreshToken(UserEntity user) { return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); } - public boolean isTokenBlacklisted(String token) { - return blacklistedTokens.contains(token); - } - private String generateAccessToken(UserEntity user) { Instant now = Instant.now(); @@ -242,8 +222,6 @@ private String generateAccessToken(UserEntity user) { .claim("user_id", user.getId().toString()) .claim("username", user.getUsername()) .claim("email", user.getEmail()) - .claim("firstName", user.getFirstName()) - .claim("lastName", user.getLastName()) .build(); return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); diff --git a/src/main/java/com/security/services/Impl/CustomUserDetailsService.java b/src/main/java/com/security/services/Impl/CustomUserDetailsService.java index 52c95c9..b7f4885 100644 --- a/src/main/java/com/security/services/Impl/CustomUserDetailsService.java +++ b/src/main/java/com/security/services/Impl/CustomUserDetailsService.java @@ -16,14 +16,13 @@ public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetails loadUserByUsername(String emailOrUsername) throws UsernameNotFoundException { - log.debug("Loading user by email or username: {}", emailOrUsername); + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + log.debug("Loading user by email or username: {}", email); - return userRepository.findByEmail(emailOrUsername) - .or(() -> userRepository.findByUsername(emailOrUsername)) + return userRepository.findByEmail(email) .orElseThrow(() -> { - log.warn("User not found with email or username: {}", emailOrUsername); - return new UsernameNotFoundException("Usuario no encontrado: " + emailOrUsername); + log.warn("User not found with email : {}", email); + return new UsernameNotFoundException("Usuario no encontrado: " + email); }); } } diff --git a/src/main/java/com/security/services/Impl/NotificationServiceImpl.java b/src/main/java/com/security/services/Impl/NotificationServiceImpl.java index 750b037..d790b79 100644 --- a/src/main/java/com/security/services/Impl/NotificationServiceImpl.java +++ b/src/main/java/com/security/services/Impl/NotificationServiceImpl.java @@ -11,7 +11,7 @@ @Slf4j @RequiredArgsConstructor public class NotificationServiceImpl { - private final KafkaTemplate kafkaTemplate; + private final KafkaTemplate kafkaTemplate; @Transactional public void sendNotification(String message) { From e2363701e58ee25464d5eca6411d79aadb92433e Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Tue, 30 Sep 2025 10:53:42 -0500 Subject: [PATCH 12/14] =?UTF-8?q?refactor:=20mejorar=20la=20configuraci?= =?UTF-8?q?=C3=B3n=20de=20cookies=20y=20agregar=20comentarios=20sobre=20Sa?= =?UTF-8?q?meSite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/Impl/CookieServiceImpl.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/security/services/Impl/CookieServiceImpl.java b/src/main/java/com/security/services/Impl/CookieServiceImpl.java index 46b5327..d1c844a 100644 --- a/src/main/java/com/security/services/Impl/CookieServiceImpl.java +++ b/src/main/java/com/security/services/Impl/CookieServiceImpl.java @@ -40,7 +40,6 @@ public void clearTokenCookies(HttpServletResponse response) { log.debug("Token cookies cleared"); } - @Override public String extractAccessTokenFromCookies(HttpServletRequest request) { return extractTokenFromCookies(request, "access_token"); @@ -51,7 +50,6 @@ public String extractRefreshTokenFromCookies(HttpServletRequest request) { return extractTokenFromCookies(request, "refresh_token"); } - private String extractTokenFromCookies(HttpServletRequest request, String tokenName) { if (request.getCookies() != null) { for (Cookie cookie : request.getCookies()) { @@ -63,34 +61,41 @@ private String extractTokenFromCookies(HttpServletRequest request, String tokenN return null; } - private void setSecureCookie(HttpServletResponse response, String name, String value, int maxAge) { Cookie cookie = new Cookie(name, value); cookie.setHttpOnly(true); cookie.setSecure(cookieSecure); cookie.setPath("/"); cookie.setMaxAge(maxAge); - if (!"localhost".equals(cookieDomain)) { cookie.setDomain(cookieDomain); } - response.addCookie(cookie); - String sameSiteValue = cookieSecure ? "None" : "Strict"; + /** + * SameSite previene ataques de CSRF -> Cross Site Request Forgery + * + * Strict -> Las cookies solo se envian en solicitudes realizadas desde el mismo dominio, no se recomineda + * si el front y el back esta en diferentes dominios + * + * Lax -> Las cookies se envian en solicitudes de navegador a nivel superior (no fetch en js), adecuada para mayoria de casos + * + * None -> Las cookies se envian en toda las peticiones incluidas las de cross-origin , requiere que la cookie tenga el atributo Secure habilitado osea + * HTTPS , util si el dominio del back y front es diferente + */ + String sameSiteValue = cookieSecure ? "None" : "Lax"; + String cookieHeader = String.format("%s=%s; Path=/; HttpOnly; Max-Age=%d; SameSite=%s%s%s", name, value, maxAge, sameSiteValue, cookieSecure ? "; Secure" : "", - !"localhost".equals(cookieDomain) ? "; Domain=" + cookieDomain : "" - ); + !"localhost".equals(cookieDomain) ? "; Domain=" + cookieDomain : ""); response.addHeader("Set-Cookie", cookieHeader); } - private void clearCookie(HttpServletResponse response, String name) { Cookie cookie = new Cookie(name, ""); cookie.setMaxAge(0); @@ -101,5 +106,4 @@ private void clearCookie(HttpServletResponse response, String name) { response.addHeader("Set-Cookie", String.format("%s=; Path=/; HttpOnly; Max-Age=0", name)); } - } From c22622886982b6a3f2cd9f980779d6d16eb5f347 Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Fri, 3 Oct 2025 15:37:34 -0500 Subject: [PATCH 13/14] update files --- .../com/security/config/kafka/KafkaProducerConfig.java | 2 +- .../com/security/controllers/AdminUserController.java | 6 +++--- .../java/com/security/controllers/AuthController.java | 8 ++++---- .../com/security/dtos/{ => auth}/AuthResponseDTO.java | 2 +- .../com/security/dtos/{ => auth}/LoginRequestDTO.java | 2 +- .../com/security/dtos/{ => auth}/LoginResponseDTO.java | 3 ++- .../com/security/dtos/{ => auth}/RegisterRequestDto.java | 2 +- .../dtos/{ => autorization}/RoleChangeRequestDTO.java | 2 +- .../com/security/dtos/{ => autorization}/RoleDTO.java | 2 +- .../dtos/{ => autorization}/RolesResponseDTO.java | 2 +- .../com/security/dtos/{ => autorization}/UserDTO.java | 2 +- .../events/{ => notification}/CreatedUserEvent.java | 2 +- .../events/{ => notification}/NotificationEvent.java | 2 +- src/main/java/com/security/mappers/RoleMapper.java | 2 +- src/main/java/com/security/mappers/UserMapper.java | 2 +- src/main/java/com/security/services/AuthService.java | 6 +++--- src/main/java/com/security/services/CookieService.java | 2 +- .../java/com/security/services/Impl/AuthServiceImpl.java | 8 ++++---- .../com/security/services/Impl/CookieServiceImpl.java | 2 +- .../security/services/Impl/NotificationServiceImpl.java | 2 +- .../security/services/Impl/UserAccountServiceImpl.java | 2 +- .../com/security/services/Impl/UserRoleServiceImpl.java | 4 ++-- .../java/com/security/services/UserAccountService.java | 2 +- src/main/java/com/security/services/UserRoleService.java | 4 ++-- 24 files changed, 37 insertions(+), 36 deletions(-) rename src/main/java/com/security/dtos/{ => auth}/AuthResponseDTO.java (81%) rename src/main/java/com/security/dtos/{ => auth}/LoginRequestDTO.java (93%) rename src/main/java/com/security/dtos/{ => auth}/LoginResponseDTO.java (81%) rename src/main/java/com/security/dtos/{ => auth}/RegisterRequestDto.java (97%) rename src/main/java/com/security/dtos/{ => autorization}/RoleChangeRequestDTO.java (81%) rename src/main/java/com/security/dtos/{ => autorization}/RoleDTO.java (83%) rename src/main/java/com/security/dtos/{ => autorization}/RolesResponseDTO.java (76%) rename src/main/java/com/security/dtos/{ => autorization}/UserDTO.java (89%) rename src/main/java/com/security/events/{ => notification}/CreatedUserEvent.java (78%) rename src/main/java/com/security/events/{ => notification}/NotificationEvent.java (59%) diff --git a/src/main/java/com/security/config/kafka/KafkaProducerConfig.java b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java index c0c296b..697ba3c 100644 --- a/src/main/java/com/security/config/kafka/KafkaProducerConfig.java +++ b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java @@ -48,7 +48,7 @@ Map producerConfigs() { config.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, requestTimeout); config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, idempotence); config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inflightRequests); - config.put(JsonSerializer.TYPE_MAPPINGS, "NotificationEvent:com.security.events.NotificationEvent,CreatedUserEvent:com.security.events.CreatedUserEvent"); + config.put(JsonSerializer.TYPE_MAPPINGS, "NotificationEvent:com.security.events.notification.NotificationEvent,CreatedUserEvent:com.security.events.notification.CreatedUserEvent"); config.put(ProducerConfig.RETRIES_CONFIG, 10); return config; } diff --git a/src/main/java/com/security/controllers/AdminUserController.java b/src/main/java/com/security/controllers/AdminUserController.java index e3b7ab8..70b4e94 100644 --- a/src/main/java/com/security/controllers/AdminUserController.java +++ b/src/main/java/com/security/controllers/AdminUserController.java @@ -1,8 +1,8 @@ package com.security.controllers; -import com.security.dtos.AuthResponseDTO; -import com.security.dtos.RoleChangeRequestDTO; -import com.security.dtos.RolesResponseDTO; +import com.security.dtos.auth.AuthResponseDTO; +import com.security.dtos.autorization.RoleChangeRequestDTO; +import com.security.dtos.autorization.RolesResponseDTO; import com.security.annotations.AdminAccess; import com.security.services.UserAccountService; import com.security.services.UserRoleService; diff --git a/src/main/java/com/security/controllers/AuthController.java b/src/main/java/com/security/controllers/AuthController.java index 34c193e..dac7ee2 100644 --- a/src/main/java/com/security/controllers/AuthController.java +++ b/src/main/java/com/security/controllers/AuthController.java @@ -1,9 +1,9 @@ package com.security.controllers; -import com.security.dtos.AuthResponseDTO; -import com.security.dtos.LoginRequestDTO; -import com.security.dtos.LoginResponseDTO; -import com.security.dtos.RegisterRequestDto; +import com.security.dtos.auth.AuthResponseDTO; +import com.security.dtos.auth.LoginRequestDTO; +import com.security.dtos.auth.LoginResponseDTO; +import com.security.dtos.auth.RegisterRequestDto; import com.security.annotations.AuthenticatedAccess; import com.security.services.AuthService; import com.security.services.CookieService; diff --git a/src/main/java/com/security/dtos/AuthResponseDTO.java b/src/main/java/com/security/dtos/auth/AuthResponseDTO.java similarity index 81% rename from src/main/java/com/security/dtos/AuthResponseDTO.java rename to src/main/java/com/security/dtos/auth/AuthResponseDTO.java index 3894962..44c65bf 100644 --- a/src/main/java/com/security/dtos/AuthResponseDTO.java +++ b/src/main/java/com/security/dtos/auth/AuthResponseDTO.java @@ -1,4 +1,4 @@ -package com.security.dtos; +package com.security.dtos.auth; import java.time.Instant; diff --git a/src/main/java/com/security/dtos/LoginRequestDTO.java b/src/main/java/com/security/dtos/auth/LoginRequestDTO.java similarity index 93% rename from src/main/java/com/security/dtos/LoginRequestDTO.java rename to src/main/java/com/security/dtos/auth/LoginRequestDTO.java index 5e90c0d..c3d28d7 100644 --- a/src/main/java/com/security/dtos/LoginRequestDTO.java +++ b/src/main/java/com/security/dtos/auth/LoginRequestDTO.java @@ -1,4 +1,4 @@ -package com.security.dtos; +package com.security.dtos.auth; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/security/dtos/LoginResponseDTO.java b/src/main/java/com/security/dtos/auth/LoginResponseDTO.java similarity index 81% rename from src/main/java/com/security/dtos/LoginResponseDTO.java rename to src/main/java/com/security/dtos/auth/LoginResponseDTO.java index d751cca..c854b97 100644 --- a/src/main/java/com/security/dtos/LoginResponseDTO.java +++ b/src/main/java/com/security/dtos/auth/LoginResponseDTO.java @@ -1,5 +1,6 @@ -package com.security.dtos; +package com.security.dtos.auth; +import com.security.dtos.autorization.UserDTO; import lombok.*; import java.time.Instant; diff --git a/src/main/java/com/security/dtos/RegisterRequestDto.java b/src/main/java/com/security/dtos/auth/RegisterRequestDto.java similarity index 97% rename from src/main/java/com/security/dtos/RegisterRequestDto.java rename to src/main/java/com/security/dtos/auth/RegisterRequestDto.java index dbda513..25ace29 100644 --- a/src/main/java/com/security/dtos/RegisterRequestDto.java +++ b/src/main/java/com/security/dtos/auth/RegisterRequestDto.java @@ -1,4 +1,4 @@ -package com.security.dtos; +package com.security.dtos.auth; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/security/dtos/RoleChangeRequestDTO.java b/src/main/java/com/security/dtos/autorization/RoleChangeRequestDTO.java similarity index 81% rename from src/main/java/com/security/dtos/RoleChangeRequestDTO.java rename to src/main/java/com/security/dtos/autorization/RoleChangeRequestDTO.java index 464a55e..7bf0a5e 100644 --- a/src/main/java/com/security/dtos/RoleChangeRequestDTO.java +++ b/src/main/java/com/security/dtos/autorization/RoleChangeRequestDTO.java @@ -1,4 +1,4 @@ -package com.security.dtos; +package com.security.dtos.autorization; import jakarta.validation.constraints.NotBlank; diff --git a/src/main/java/com/security/dtos/RoleDTO.java b/src/main/java/com/security/dtos/autorization/RoleDTO.java similarity index 83% rename from src/main/java/com/security/dtos/RoleDTO.java rename to src/main/java/com/security/dtos/autorization/RoleDTO.java index ba19d92..557ab6e 100644 --- a/src/main/java/com/security/dtos/RoleDTO.java +++ b/src/main/java/com/security/dtos/autorization/RoleDTO.java @@ -1,4 +1,4 @@ -package com.security.dtos; +package com.security.dtos.autorization; import lombok.*; diff --git a/src/main/java/com/security/dtos/RolesResponseDTO.java b/src/main/java/com/security/dtos/autorization/RolesResponseDTO.java similarity index 76% rename from src/main/java/com/security/dtos/RolesResponseDTO.java rename to src/main/java/com/security/dtos/autorization/RolesResponseDTO.java index c6a60cd..807c73f 100644 --- a/src/main/java/com/security/dtos/RolesResponseDTO.java +++ b/src/main/java/com/security/dtos/autorization/RolesResponseDTO.java @@ -1,4 +1,4 @@ -package com.security.dtos; +package com.security.dtos.autorization; import java.util.Set; import java.util.UUID; diff --git a/src/main/java/com/security/dtos/UserDTO.java b/src/main/java/com/security/dtos/autorization/UserDTO.java similarity index 89% rename from src/main/java/com/security/dtos/UserDTO.java rename to src/main/java/com/security/dtos/autorization/UserDTO.java index bfff417..60f6fea 100644 --- a/src/main/java/com/security/dtos/UserDTO.java +++ b/src/main/java/com/security/dtos/autorization/UserDTO.java @@ -1,4 +1,4 @@ -package com.security.dtos; +package com.security.dtos.autorization; import lombok.*; diff --git a/src/main/java/com/security/events/CreatedUserEvent.java b/src/main/java/com/security/events/notification/CreatedUserEvent.java similarity index 78% rename from src/main/java/com/security/events/CreatedUserEvent.java rename to src/main/java/com/security/events/notification/CreatedUserEvent.java index 9393928..c1521ab 100644 --- a/src/main/java/com/security/events/CreatedUserEvent.java +++ b/src/main/java/com/security/events/notification/CreatedUserEvent.java @@ -1,4 +1,4 @@ -package com.security.events; +package com.security.events.notification; public record CreatedUserEvent( String userId, diff --git a/src/main/java/com/security/events/NotificationEvent.java b/src/main/java/com/security/events/notification/NotificationEvent.java similarity index 59% rename from src/main/java/com/security/events/NotificationEvent.java rename to src/main/java/com/security/events/notification/NotificationEvent.java index fe332b3..221f65e 100644 --- a/src/main/java/com/security/events/NotificationEvent.java +++ b/src/main/java/com/security/events/notification/NotificationEvent.java @@ -1,4 +1,4 @@ -package com.security.events; +package com.security.events.notification; public record NotificationEvent( String message diff --git a/src/main/java/com/security/mappers/RoleMapper.java b/src/main/java/com/security/mappers/RoleMapper.java index 352232f..4d14cbd 100644 --- a/src/main/java/com/security/mappers/RoleMapper.java +++ b/src/main/java/com/security/mappers/RoleMapper.java @@ -1,6 +1,6 @@ package com.security.mappers; -import com.security.dtos.RoleDTO; +import com.security.dtos.autorization.RoleDTO; import com.security.entity.RoleEntity; import com.security.config.MapStructConfig; import org.mapstruct.Mapper; diff --git a/src/main/java/com/security/mappers/UserMapper.java b/src/main/java/com/security/mappers/UserMapper.java index e5574ca..4f7d260 100644 --- a/src/main/java/com/security/mappers/UserMapper.java +++ b/src/main/java/com/security/mappers/UserMapper.java @@ -1,6 +1,6 @@ package com.security.mappers; -import com.security.dtos.UserDTO; +import com.security.dtos.autorization.UserDTO; import com.security.entity.UserEntity; import com.security.config.MapStructConfig; import org.mapstruct.Mapper; diff --git a/src/main/java/com/security/services/AuthService.java b/src/main/java/com/security/services/AuthService.java index 93960ec..ff0456f 100644 --- a/src/main/java/com/security/services/AuthService.java +++ b/src/main/java/com/security/services/AuthService.java @@ -1,8 +1,8 @@ package com.security.services; -import com.security.dtos.LoginRequestDTO; -import com.security.dtos.LoginResponseDTO; -import com.security.dtos.RegisterRequestDto; +import com.security.dtos.auth.LoginRequestDTO; +import com.security.dtos.auth.LoginResponseDTO; +import com.security.dtos.auth.RegisterRequestDto; public interface AuthService { diff --git a/src/main/java/com/security/services/CookieService.java b/src/main/java/com/security/services/CookieService.java index 81c4117..bd9681b 100644 --- a/src/main/java/com/security/services/CookieService.java +++ b/src/main/java/com/security/services/CookieService.java @@ -1,7 +1,7 @@ package com.security.services; -import com.security.dtos.LoginResponseDTO; +import com.security.dtos.auth.LoginResponseDTO; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/security/services/Impl/AuthServiceImpl.java b/src/main/java/com/security/services/Impl/AuthServiceImpl.java index 771d9e0..af37d70 100644 --- a/src/main/java/com/security/services/Impl/AuthServiceImpl.java +++ b/src/main/java/com/security/services/Impl/AuthServiceImpl.java @@ -1,11 +1,11 @@ package com.security.services.Impl; -import com.security.dtos.LoginRequestDTO; -import com.security.dtos.LoginResponseDTO; -import com.security.dtos.RegisterRequestDto; +import com.security.dtos.auth.LoginRequestDTO; +import com.security.dtos.auth.LoginResponseDTO; +import com.security.dtos.auth.RegisterRequestDto; import com.security.entity.RoleEntity; import com.security.entity.UserEntity; -import com.security.events.CreatedUserEvent; +import com.security.events.notification.CreatedUserEvent; import com.security.mappers.UserMapper; import com.security.repository.RoleRepository; import com.security.repository.UserRepository; diff --git a/src/main/java/com/security/services/Impl/CookieServiceImpl.java b/src/main/java/com/security/services/Impl/CookieServiceImpl.java index d1c844a..607207a 100644 --- a/src/main/java/com/security/services/Impl/CookieServiceImpl.java +++ b/src/main/java/com/security/services/Impl/CookieServiceImpl.java @@ -1,6 +1,6 @@ package com.security.services.Impl; -import com.security.dtos.LoginResponseDTO; +import com.security.dtos.auth.LoginResponseDTO; import com.security.services.CookieService; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/security/services/Impl/NotificationServiceImpl.java b/src/main/java/com/security/services/Impl/NotificationServiceImpl.java index d790b79..e20de32 100644 --- a/src/main/java/com/security/services/Impl/NotificationServiceImpl.java +++ b/src/main/java/com/security/services/Impl/NotificationServiceImpl.java @@ -1,6 +1,6 @@ package com.security.services.Impl; -import com.security.events.NotificationEvent; +import com.security.events.notification.NotificationEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.kafka.core.KafkaTemplate; diff --git a/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java b/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java index 73e8a6e..4417ec6 100644 --- a/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java +++ b/src/main/java/com/security/services/Impl/UserAccountServiceImpl.java @@ -1,6 +1,6 @@ package com.security.services.Impl; -import com.security.dtos.AuthResponseDTO; +import com.security.dtos.auth.AuthResponseDTO; import com.security.entity.UserEntity; import com.security.repository.UserRepository; import com.security.config.audit.Audit; diff --git a/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java b/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java index 2815148..37f814c 100644 --- a/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java +++ b/src/main/java/com/security/services/Impl/UserRoleServiceImpl.java @@ -1,7 +1,7 @@ package com.security.services.Impl; -import com.security.dtos.AuthResponseDTO; -import com.security.dtos.RolesResponseDTO; +import com.security.dtos.auth.AuthResponseDTO; +import com.security.dtos.autorization.RolesResponseDTO; import com.security.entity.RoleEntity; import com.security.entity.UserEntity; import com.security.repository.RoleRepository; diff --git a/src/main/java/com/security/services/UserAccountService.java b/src/main/java/com/security/services/UserAccountService.java index 239a7a2..4868ed1 100644 --- a/src/main/java/com/security/services/UserAccountService.java +++ b/src/main/java/com/security/services/UserAccountService.java @@ -1,6 +1,6 @@ package com.security.services; -import com.security.dtos.AuthResponseDTO; +import com.security.dtos.auth.AuthResponseDTO; import java.util.UUID; diff --git a/src/main/java/com/security/services/UserRoleService.java b/src/main/java/com/security/services/UserRoleService.java index 6ec2e5f..1df3b26 100644 --- a/src/main/java/com/security/services/UserRoleService.java +++ b/src/main/java/com/security/services/UserRoleService.java @@ -1,7 +1,7 @@ package com.security.services; -import com.security.dtos.AuthResponseDTO; -import com.security.dtos.RolesResponseDTO; +import com.security.dtos.auth.AuthResponseDTO; +import com.security.dtos.autorization.RolesResponseDTO; import java.util.UUID; From d5da3e539aedb9c152c672618d353fa984f7ee9f Mon Sep 17 00:00:00 2001 From: Raydberg Gabriel Date: Mon, 6 Oct 2025 16:05:10 -0500 Subject: [PATCH 14/14] complete login with oauth2 for google --- pom.xml | 4 + .../auth/AuthorizationServerConfig.java | 77 ++++++++ .../com/security/config/auth/JwtConfig.java | 60 ++++++ .../security/config/auth/SecurityConfig.java | 187 +++++------------- .../OAuth2AuthenticationSuccessHandler.java | 89 +++++++++ .../config/auth/oauth2/OAuth2Config.java | 54 +++++ .../java/com/security/entity/UserEntity.java | 18 +- .../java/com/security/enums/AuthProvider.java | 5 + .../events/notification/CreatedUserEvent.java | 3 +- .../exceptions/AuthenticationException.java | 7 + .../exceptions/GlobalExceptionController.java | 39 ++++ .../exceptions/RoleNotFoundException.java | 7 + .../exceptions/UserNotFoundException.java | 7 + .../com/security/services/AuthService.java | 7 +- .../services/Impl/AuthServiceImpl.java | 91 ++++----- .../services/Impl/CookieServiceImpl.java | 3 +- .../Impl/GoogleOAuth2UserInfoImpl.java | 37 ++++ .../services/Impl/TokenServiceImpl.java | 59 ++++++ .../com/security/services/OAuth2UserInfo.java | 9 + .../com/security/services/TokenService.java | 9 + .../services/oauth2/CustomOAuth2User.java | 34 ++++ .../oauth2/CustomOAuth2UserService.java | 169 ++++++++++++++++ .../services/oauth2/CustomOidcUser.java | 53 +++++ .../oauth2/OAuth2UserInfoFactory.java | 17 ++ 24 files changed, 847 insertions(+), 198 deletions(-) create mode 100644 src/main/java/com/security/config/auth/AuthorizationServerConfig.java create mode 100644 src/main/java/com/security/config/auth/JwtConfig.java create mode 100644 src/main/java/com/security/config/auth/oauth2/OAuth2AuthenticationSuccessHandler.java create mode 100644 src/main/java/com/security/config/auth/oauth2/OAuth2Config.java create mode 100644 src/main/java/com/security/enums/AuthProvider.java create mode 100644 src/main/java/com/security/exceptions/AuthenticationException.java create mode 100644 src/main/java/com/security/exceptions/RoleNotFoundException.java create mode 100644 src/main/java/com/security/exceptions/UserNotFoundException.java create mode 100644 src/main/java/com/security/services/Impl/GoogleOAuth2UserInfoImpl.java create mode 100644 src/main/java/com/security/services/Impl/TokenServiceImpl.java create mode 100644 src/main/java/com/security/services/OAuth2UserInfo.java create mode 100644 src/main/java/com/security/services/TokenService.java create mode 100644 src/main/java/com/security/services/oauth2/CustomOAuth2User.java create mode 100644 src/main/java/com/security/services/oauth2/CustomOAuth2UserService.java create mode 100644 src/main/java/com/security/services/oauth2/CustomOidcUser.java create mode 100644 src/main/java/com/security/services/oauth2/OAuth2UserInfoFactory.java diff --git a/pom.xml b/pom.xml index 838e28a..37ccd51 100644 --- a/pom.xml +++ b/pom.xml @@ -153,6 +153,10 @@ org.springframework.security spring-security-oauth2-jose + + org.springframework.boot + spring-boot-starter-oauth2-client + diff --git a/src/main/java/com/security/config/auth/AuthorizationServerConfig.java b/src/main/java/com/security/config/auth/AuthorizationServerConfig.java new file mode 100644 index 0000000..66eb48d --- /dev/null +++ b/src/main/java/com/security/config/auth/AuthorizationServerConfig.java @@ -0,0 +1,77 @@ +package com.security.config.auth; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; + +import java.time.Duration; +import java.util.UUID; + +@Configuration +@RequiredArgsConstructor +public class AuthorizationServerConfig { + + private final AuthProperties authProperties; + private final PasswordEncoder passwordEncoder; + + @Bean + public RegisteredClientRepository registeredClientRepository() { + RegisteredClient.Builder clientBuilder = RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId("gateway-client") + .clientSecret(passwordEncoder.encode("gateway-secret")) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .redirectUri("http://localhost:9090/login/oauth2/code/gateway-client") + .redirectUri("http://localhost:9090/authorized") + .postLogoutRedirectUri("http://localhost:9090/logout") + .scope(OidcScopes.OPENID) + .scope(OidcScopes.PROFILE) + .scope(OidcScopes.EMAIL) + .scope("read") + .scope("write") + .clientSettings(ClientSettings.builder() + .requireAuthorizationConsent(false) + .requireProofKey(false) + .build()) + .tokenSettings(TokenSettings.builder() + .accessTokenTimeToLive(Duration.ofMinutes(15)) + .refreshTokenTimeToLive(Duration.ofDays(7)) + .reuseRefreshTokens(false) + .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) + .build()); + + authProperties.getClient().getRedirectUris().forEach(clientBuilder::redirectUri); + RegisteredClient client = clientBuilder.build(); + return new InMemoryRegisteredClientRepository(client); + } + + + @Bean + public AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder() + .issuer(authProperties.getServer().getIssuer()) + .authorizationEndpoint("/oauth2/authorize") + .tokenEndpoint("/oauth2/token") + .tokenIntrospectionEndpoint("/oauth2/introspect") + .tokenRevocationEndpoint("/oauth2/revoke") + .jwkSetEndpoint("/.well-known/jwks.json") + .oidcLogoutEndpoint("/connect/logout") + .oidcUserInfoEndpoint("/userinfo") + .oidcClientRegistrationEndpoint("/connect/register") + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/security/config/auth/JwtConfig.java b/src/main/java/com/security/config/auth/JwtConfig.java new file mode 100644 index 0000000..5ef5219 --- /dev/null +++ b/src/main/java/com/security/config/auth/JwtConfig.java @@ -0,0 +1,60 @@ +package com.security.config.auth; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.UUID; + +@Configuration +public class JwtConfig { + + @Bean + public JWKSource jwkSource() { + KeyPair keyPair = generateRsaKey(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + RSAKey rsaKey = new RSAKey.Builder(publicKey) + .privateKey(privateKey) + .keyID(UUID.randomUUID().toString()) + .build(); + JWKSet jwkSet = new JWKSet(rsaKey); + return new ImmutableJWKSet<>(jwkSet); + } + + private static KeyPair generateRsaKey() { + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + } catch ( + Exception ex) { + throw new IllegalStateException(ex); + } + return keyPair; + } + + @Bean + public JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } + + @Bean + public JwtEncoder jwtEncoder(JWKSource jwkSource) { + return new NimbusJwtEncoder(jwkSource); + } + +} diff --git a/src/main/java/com/security/config/auth/SecurityConfig.java b/src/main/java/com/security/config/auth/SecurityConfig.java index feb31da..10b62e0 100644 --- a/src/main/java/com/security/config/auth/SecurityConfig.java +++ b/src/main/java/com/security/config/auth/SecurityConfig.java @@ -1,10 +1,7 @@ package com.security.config.auth; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; +import com.security.config.auth.oauth2.OAuth2AuthenticationSuccessHandler; +import com.security.services.oauth2.CustomOAuth2UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -18,42 +15,29 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Map; -import java.util.UUID; - -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity -@EnableMethodSecurity(prePostEnabled = true) +@EnableMethodSecurity @RequiredArgsConstructor public class SecurityConfig { + + private final CustomOAuth2UserService customOAuth2UserService; + @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { @@ -75,7 +59,13 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h @Bean @Order(2) - public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CookieAuthenticationFilter cookieAuthenticationFilter) throws Exception { + public SecurityFilterChain defaultSecurityFilterChain( + HttpSecurity http, + CookieAuthenticationFilter cookieAuthenticationFilter, + OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler, + OAuth2UserService customOidcUserService, + AuthorizationRequestRepository authorizationRequestRepository) throws Exception { + return http .authorizeHttpRequests(authorize -> authorize .requestMatchers( @@ -87,12 +77,25 @@ public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CookieA "/auth/login", "/auth/refresh", "/auth/register", + "/oauth2/**", + "/login/oauth2/**", "/test/saludo", "/test/notification", "/" ).permitAll() .anyRequest().authenticated() ) + .oauth2Login(oauth2 -> + oauth2.authorizationEndpoint(authorization -> + authorization.authorizationRequestRepository(authorizationRequestRepository) + ) + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService) + .oidcUserService(customOidcUserService) + ) + .successHandler(oAuth2AuthenticationSuccessHandler) + ) + .oauth2Client(Customizer.withDefaults()) .addFilterBefore(cookieAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt(Customizer.withDefaults()) @@ -105,33 +108,36 @@ public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CookieA public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter scopeConverter = new JwtGrantedAuthoritiesConverter(); scopeConverter.setAuthorityPrefix("SCOPE_"); + JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(jwt -> { Collection authorities = new ArrayList<>(); - Collection scopeAuth = (Collection) scopeConverter.convert(jwt); - if (scopeAuth != null) + Collection scopeAuth = scopeConverter.convert(jwt); + if (scopeAuth != null) { authorities.addAll(scopeAuth); + } Object claim = jwt.getClaims().get("authorities"); - if (claim != null) { - if (claim instanceof String) { - String[] parts = ((String) claim).trim().split("\\s+"); - for (String p : parts) { - if (!p.isBlank()) - authorities.add(new SimpleGrantedAuthority(p)); + if (claim instanceof String authString) { + String[] parts = authString.trim().split("\\s+"); + for (String part : parts) { + if (!part.isBlank()) { + authorities.add(new SimpleGrantedAuthority(part)); } - } else if (claim instanceof Collection) { - ((Collection) claim).forEach(o -> { - if (o != null) - authorities.add(new SimpleGrantedAuthority(o.toString())); - }); - } else if (claim instanceof Map) { - ((Map) claim).values().forEach(v -> { - if (v != null) - authorities.add(new SimpleGrantedAuthority(v.toString())); - }); } + } else if (claim instanceof Collection authCollection) { + authCollection.forEach(o -> { + if (o != null) { + authorities.add(new SimpleGrantedAuthority(o.toString())); + } + }); + } else if (claim instanceof Map authMap) { + authMap.values().forEach(v -> { + if (v != null) { + authorities.add(new SimpleGrantedAuthority(v.toString())); + } + }); } return authorities; @@ -140,93 +146,8 @@ public JwtAuthenticationConverter jwtAuthenticationConverter() { return converter; } - @Bean - public RegisteredClientRepository registeredClientRepository() { - RegisteredClient gatewayClient = RegisteredClient.withId(UUID.randomUUID().toString()) - .clientId("gateway-client") - .clientSecret(passwordEncoder().encode("gateway-secret")) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .redirectUri("http://localhost:9090/login/oauth2/code/gateway-client") - .redirectUri("http://localhost:9090/authorized") - .postLogoutRedirectUri("http://localhost:9090/logout") - .scope(OidcScopes.OPENID) - .scope(OidcScopes.PROFILE) - .scope(OidcScopes.EMAIL) - .scope("read") - .scope("write") - .clientSettings(ClientSettings.builder() - .requireAuthorizationConsent(false) - .requireProofKey(false) - .build()) - - .tokenSettings(TokenSettings.builder() - .accessTokenTimeToLive(Duration.ofMinutes(15)) - .refreshTokenTimeToLive(Duration.ofDays(7)) - .reuseRefreshTokens(false) - .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED) - .build()) - .build(); - - return new InMemoryRegisteredClientRepository(gatewayClient); - } - - @Bean - public JWKSource jwkSource() { - KeyPair keyPair = generateRsaKey(); - RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); - RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); - RSAKey rsaKey = new RSAKey.Builder(publicKey) - .privateKey(privateKey) - .keyID(UUID.randomUUID().toString()) - .build(); - JWKSet jwkSet = new JWKSet(rsaKey); - return new ImmutableJWKSet<>(jwkSet); - } - - private static KeyPair generateRsaKey() { - KeyPair keyPair; - try { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); - keyPair = keyPairGenerator.generateKeyPair(); - } catch ( - Exception ex) { - throw new IllegalStateException(ex); - } - return keyPair; - } - - @Bean - public JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - public JwtEncoder jwtEncoder(JWKSource jwkSource) { - return new NimbusJwtEncoder(jwkSource); - } - - @Bean - public AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder() - .issuer("http://localhost:9091") - .authorizationEndpoint("/oauth2/authorize") - .tokenEndpoint("/oauth2/token") - .tokenIntrospectionEndpoint("/oauth2/introspect") - .tokenRevocationEndpoint("/oauth2/revoke") - .jwkSetEndpoint("/.well-known/jwks.json") - .oidcLogoutEndpoint("/connect/logout") - .oidcUserInfoEndpoint("/userinfo") - .oidcClientRegistrationEndpoint("/connect/register") - .build(); - } - @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/security/config/auth/oauth2/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/security/config/auth/oauth2/OAuth2AuthenticationSuccessHandler.java new file mode 100644 index 0000000..f8db052 --- /dev/null +++ b/src/main/java/com/security/config/auth/oauth2/OAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,89 @@ +package com.security.config.auth.oauth2; + +import com.security.entity.UserEntity; +import com.security.services.AuthService; +import com.security.services.CookieService; +import com.security.services.oauth2.CustomOAuth2User; +import com.security.services.oauth2.CustomOidcUser; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +@Slf4j +public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final AuthService authService; + private final CookieService cookieService; + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) throws IOException { + + Object principal = authentication.getPrincipal(); + UserEntity user = null; + + if (principal instanceof CustomOidcUser customOidcUser) { + user = customOidcUser.getUser(); + log.info("✅ Usuario obtenido desde CustomOidcUser: {}", user.getEmail()); + } else if (principal instanceof CustomOAuth2User customOAuth2User) { + user = customOAuth2User.getUser(); + log.info("✅ Usuario obtenido desde CustomOAuth2User: {}", user.getEmail()); + } else { + log.error("❌ Principal NO es CustomOAuth2User ni CustomOidcUser. Tipo: {}", principal.getClass().getName()); + if (principal instanceof OAuth2User oauth2User) { + log.error("❌ Atributos: {}", oauth2User.getAttributes()); + } + + getRedirectStrategy().sendRedirect(request, response, + "http://localhost:5173/auth?error=oauth_user_service_failed"); + return; + } + + if (user == null) { + log.error("❌ Usuario es NULL después de obtenerlo del principal"); + getRedirectStrategy().sendRedirect(request, response, + "http://localhost:5173/auth?error=oauth_user_null"); + return; + } + + try { + var loginResponse = authService.createTokensForOAuth2User(user); + cookieService.setSecureTokenCookies(response, loginResponse); + + log.info("✅ Cookies establecidas para usuario OAuth2: {}", user.getEmail()); + + // ✅ Redirigir al frontend + String targetUrl = UriComponentsBuilder + .fromUriString("http://localhost:5173/auth/callback") + .queryParam("success", "true") + .build() + .toUriString(); + + if (response.isCommitted()) { + log.debug("⚠️ La respuesta ya fue enviada. No se puede redirigir a {}", targetUrl); + return; + } + + clearAuthenticationAttributes(request); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + + } catch ( + Exception e) { + log.error("❌ Error generando tokens para OAuth2", e); + getRedirectStrategy().sendRedirect(request, response, + "http://localhost:5173/auth?error=token_generation_failed"); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/security/config/auth/oauth2/OAuth2Config.java b/src/main/java/com/security/config/auth/oauth2/OAuth2Config.java new file mode 100644 index 0000000..0a8e079 --- /dev/null +++ b/src/main/java/com/security/config/auth/oauth2/OAuth2Config.java @@ -0,0 +1,54 @@ +package com.security.config.auth.oauth2; + +import com.security.services.AuthService; +import com.security.services.CookieService; +import com.security.services.oauth2.CustomOAuth2UserService; +import com.security.services.oauth2.CustomOidcUser; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.user.OAuth2User; + + +@Configuration +public class OAuth2Config { + + @Bean + public OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler( + AuthService authService, + CookieService cookieService) { + return new OAuth2AuthenticationSuccessHandler(authService, cookieService); + } + + @Bean + public OAuth2UserService customOidcUserService( + CustomOAuth2UserService customOAuth2UserService) { + return new OidcUserService() { + @Override + public OidcUser loadUser(OidcUserRequest userRequest) { + OAuth2UserRequest oauth2UserRequest = new OAuth2UserRequest( + userRequest.getClientRegistration(), + userRequest.getAccessToken(), + userRequest.getAdditionalParameters() + ); + OAuth2User oauth2User = customOAuth2UserService.loadUser(oauth2UserRequest); + if (oauth2User instanceof com.security.services.oauth2.CustomOAuth2User customUser) { + return new CustomOidcUser(customUser, userRequest.getIdToken()); + } + return super.loadUser(userRequest); + } + }; + } + + @Bean + public AuthorizationRequestRepository authorizationRequestRepository() { + return new HttpSessionOAuth2AuthorizationRequestRepository(); + } +} \ No newline at end of file diff --git a/src/main/java/com/security/entity/UserEntity.java b/src/main/java/com/security/entity/UserEntity.java index b6141f3..bdda4cc 100644 --- a/src/main/java/com/security/entity/UserEntity.java +++ b/src/main/java/com/security/entity/UserEntity.java @@ -2,6 +2,7 @@ import com.security.config.audit.Audit; import com.security.config.audit.AuditListener; +import com.security.enums.AuthProvider; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -28,13 +29,14 @@ public class UserEntity implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; -// private String username; + @Column(unique = true) private String email; private String password; -// private String firstName; -// private String lastName; -// private String dni; -// private String phone; + @Column(unique = true) + private String googleId; + @Enumerated(EnumType.STRING) + @Builder.Default + private AuthProvider provider = AuthProvider.LOCAL; @Embedded private Audit audit; @@ -54,7 +56,11 @@ public class UserEntity implements UserDetails { private Boolean credentialsNonExpired = true; @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) + @JoinTable( + name = "user_roles", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id") + ) private Set roles; @Override diff --git a/src/main/java/com/security/enums/AuthProvider.java b/src/main/java/com/security/enums/AuthProvider.java new file mode 100644 index 0000000..7d4d603 --- /dev/null +++ b/src/main/java/com/security/enums/AuthProvider.java @@ -0,0 +1,5 @@ +package com.security.enums; + +public enum AuthProvider { + LOCAL, GOOGLE +} diff --git a/src/main/java/com/security/events/notification/CreatedUserEvent.java b/src/main/java/com/security/events/notification/CreatedUserEvent.java index c1521ab..606f407 100644 --- a/src/main/java/com/security/events/notification/CreatedUserEvent.java +++ b/src/main/java/com/security/events/notification/CreatedUserEvent.java @@ -5,6 +5,7 @@ public record CreatedUserEvent( String firstName, String lastName, String dni, - String phone + String phone, + String profileImageUrl ) { } diff --git a/src/main/java/com/security/exceptions/AuthenticationException.java b/src/main/java/com/security/exceptions/AuthenticationException.java new file mode 100644 index 0000000..a6b18be --- /dev/null +++ b/src/main/java/com/security/exceptions/AuthenticationException.java @@ -0,0 +1,7 @@ +package com.security.exceptions; + +public class AuthenticationException extends RuntimeException { + public AuthenticationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/security/exceptions/GlobalExceptionController.java b/src/main/java/com/security/exceptions/GlobalExceptionController.java index 59b3b87..08c1291 100644 --- a/src/main/java/com/security/exceptions/GlobalExceptionController.java +++ b/src/main/java/com/security/exceptions/GlobalExceptionController.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.NoHandlerFoundException; import java.util.Collections; @@ -174,4 +175,42 @@ public ResponseEntity> handleGenericException(Exception ex) )); } + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handleUserNotFoundException(UserNotFoundException ex) { + ErrorResponse errorResponse = new ErrorResponse( + "USER_ERROR", + "Ha ocurrido un error con el usuario", + Collections.singletonList(ex.getMessage()) + ); + + log.warn("User not found: {}", ex.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + } + + + @ExceptionHandler(RoleNotFoundException.class) + public ResponseEntity handleRoleNotFoundException(RoleNotFoundException ex) { + ErrorResponse errorResponse = new ErrorResponse( + "USER_ERROR", + "Ha ocurrido un error con el usuario", + Collections.singletonList(ex.getMessage()) + ); + + log.warn("User not found: {}", ex.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + } + + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleAuthenticationException(AuthenticationException ex) { + ErrorResponse errorResponse = new ErrorResponse( + "USER_ERROR", + "Error al registrar al usuario", + Collections.singletonList(ex.getMessage()) + ); + + log.warn("User not found: {}", ex.getMessage()); + return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse); + } + } \ No newline at end of file diff --git a/src/main/java/com/security/exceptions/RoleNotFoundException.java b/src/main/java/com/security/exceptions/RoleNotFoundException.java new file mode 100644 index 0000000..d20987b --- /dev/null +++ b/src/main/java/com/security/exceptions/RoleNotFoundException.java @@ -0,0 +1,7 @@ +package com.security.exceptions; + +public class RoleNotFoundException extends RuntimeException { + public RoleNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/security/exceptions/UserNotFoundException.java b/src/main/java/com/security/exceptions/UserNotFoundException.java new file mode 100644 index 0000000..0cec721 --- /dev/null +++ b/src/main/java/com/security/exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package com.security.exceptions; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/security/services/AuthService.java b/src/main/java/com/security/services/AuthService.java index ff0456f..9bfc407 100644 --- a/src/main/java/com/security/services/AuthService.java +++ b/src/main/java/com/security/services/AuthService.java @@ -3,17 +3,14 @@ import com.security.dtos.auth.LoginRequestDTO; import com.security.dtos.auth.LoginResponseDTO; import com.security.dtos.auth.RegisterRequestDto; +import com.security.entity.UserEntity; public interface AuthService { - LoginResponseDTO authenticateUser(LoginRequestDTO loginRequest); - void logout(String token); - - LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto); - LoginResponseDTO refreshToken(String refreshToken); + LoginResponseDTO createTokensForOAuth2User(UserEntity user); } \ No newline at end of file diff --git a/src/main/java/com/security/services/Impl/AuthServiceImpl.java b/src/main/java/com/security/services/Impl/AuthServiceImpl.java index af37d70..f7a4bf8 100644 --- a/src/main/java/com/security/services/Impl/AuthServiceImpl.java +++ b/src/main/java/com/security/services/Impl/AuthServiceImpl.java @@ -5,27 +5,30 @@ import com.security.dtos.auth.RegisterRequestDto; import com.security.entity.RoleEntity; import com.security.entity.UserEntity; +import com.security.enums.AuthProvider; import com.security.events.notification.CreatedUserEvent; +import com.security.exceptions.AuthenticationException; +import com.security.exceptions.RoleNotFoundException; +import com.security.exceptions.UserNotFoundException; import com.security.mappers.UserMapper; import com.security.repository.RoleRepository; import com.security.repository.UserRepository; import com.security.services.AuthService; +import com.security.services.TokenService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.server.ResponseStatusException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; + @Service @RequiredArgsConstructor @@ -40,6 +43,7 @@ public class AuthServiceImpl implements AuthService { private final JwtEncoder jwtEncoder; private final JwtDecoder jwtDecoder; private final KafkaTemplate kafkaTemplate; + private final TokenService tokenService; private final Set validRefreshTokens = ConcurrentHashMap.newKeySet(); @@ -50,7 +54,7 @@ public LoginResponseDTO authenticateUser(LoginRequestDTO request) { log.info("Authenticating user: {}", request.getEmail()); UserEntity user = userRepository.findByEmail(request.getEmail()) - .orElseThrow(() -> new BadCredentialsException("Usuario no encontrado")); + .orElseThrow(() -> new UserNotFoundException("Usuario no encontrado")); if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { throw new BadCredentialsException("Credenciales inválidas"); @@ -60,8 +64,8 @@ public LoginResponseDTO authenticateUser(LoginRequestDTO request) { throw new BadCredentialsException("Usuario deshabilitado"); } - String accessToken = generateAccessToken(user); - String refreshToken = generateRefreshToken(user); + String accessToken = tokenService.generateAccessToken(user); + String refreshToken = tokenService.generateRefreshToken(user); validRefreshTokens.add(refreshToken); @@ -89,7 +93,7 @@ public LoginResponseDTO refreshToken(String refreshToken) { String email = jwt.getSubject(); UserEntity user = userRepository.findByEmail(email) - .orElseThrow(() -> new BadCredentialsException("Usuario no encontrado")); + .orElseThrow(() -> new UserNotFoundException("Usuario no encontrado")); if (!user.isEnabled()) { throw new BadCredentialsException("Usuario deshabilitado"); @@ -97,8 +101,8 @@ public LoginResponseDTO refreshToken(String refreshToken) { validRefreshTokens.remove(refreshToken); - String newAccessToken = generateAccessToken(user); - String newRefreshToken = generateRefreshToken(user); + String newAccessToken = tokenService.generateAccessToken(user); + String newRefreshToken = tokenService.generateRefreshToken(user); validRefreshTokens.add(newRefreshToken); @@ -119,6 +123,25 @@ public LoginResponseDTO refreshToken(String refreshToken) { } } + @Override + public LoginResponseDTO createTokensForOAuth2User(UserEntity user) { + log.info("Creando tokens para usuarios OAuth2: {}", user.getEmail()); + + String accessToken = tokenService.generateAccessToken(user); + String refreshToken = tokenService.generateRefreshToken(user); + + validRefreshTokens.add(refreshToken); + return LoginResponseDTO.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .tokenType("Bearer") + .expiresAt(Instant.now().plus(15, ChronoUnit.MINUTES)) + .scope("read write") + .user(userMapper.toDTO(user)) + .message("Login OAuth2 exitoso") + .build(); + } + @Override public void logout(String accessToken) { log.info("Logging out user"); @@ -150,16 +173,17 @@ public void logout(String accessToken) { @Override public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { if (userRepository.existsByEmail(registerRequestDto.email())) { - throw new ResponseStatusException(HttpStatus.CONFLICT, "Error al inicial sesion"); + throw new AuthenticationException("Error al registrar usuario intente de nuevo"); } - RoleEntity userRole = roleRepository.findByName("USER").orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Error al encontrar el rol de Usuario")); + RoleEntity userRole = roleRepository.findByName("USER").orElseThrow(() -> new RoleNotFoundException("Error al encontrar el rol USER")); UserEntity user = UserEntity.builder() .email(registerRequestDto.email()) .password(passwordEncoder.encode(registerRequestDto.password())) + .provider(AuthProvider.LOCAL) .roles(Set.of(userRole)) .enabled(true) .build(); @@ -171,7 +195,8 @@ public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { registerRequestDto.firstName(), registerRequestDto.lastName(), registerRequestDto.dni(), - registerRequestDto.phone() + registerRequestDto.phone(), + null ); log.info("Enviando evento {}", event); @@ -179,8 +204,8 @@ public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { log.info("Evento enviado {}", event); return LoginResponseDTO.builder() - .accessToken(generateAccessToken(user)) - .refreshToken(generateRefreshToken(user)) + .accessToken(tokenService.generateAccessToken(user)) + .refreshToken(tokenService.generateRefreshToken(user)) .tokenType("Bearer") .expiresAt(Instant.now().plus(15, ChronoUnit.MINUTES)) .scope("read write") @@ -190,42 +215,4 @@ public LoginResponseDTO registerUser(RegisterRequestDto registerRequestDto) { } - private String generateRefreshToken(UserEntity user) { - Instant now = Instant.now(); - - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuer("http://localhost:9091") - .issuedAt(now) - .expiresAt(now.plus(7, ChronoUnit.DAYS)) - .subject(user.getEmail()) - .claim("type", "refresh") - .claim("user_id", user.getId().toString()) - .build(); - - return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); - } - - private String generateAccessToken(UserEntity user) { - Instant now = Instant.now(); - - String authorities = user.getRoles().stream() - .map(role -> "ROLE_" + role.getName()) - .collect(Collectors.joining(" ")); - - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuer("http://localhost:9091") - .issuedAt(now) - .expiresAt(now.plus(15, ChronoUnit.MINUTES)) - .subject(user.getEmail()) - .claim("scope", "read write") - .claim("authorities", authorities) - .claim("user_id", user.getId().toString()) - .claim("username", user.getUsername()) - .claim("email", user.getEmail()) - .build(); - - return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); - } - - } \ No newline at end of file diff --git a/src/main/java/com/security/services/Impl/CookieServiceImpl.java b/src/main/java/com/security/services/Impl/CookieServiceImpl.java index 607207a..5f96d22 100644 --- a/src/main/java/com/security/services/Impl/CookieServiceImpl.java +++ b/src/main/java/com/security/services/Impl/CookieServiceImpl.java @@ -71,7 +71,8 @@ private void setSecureCookie(HttpServletResponse response, String name, String v cookie.setDomain(cookieDomain); } response.addCookie(cookie); - + log.debug("Cookie '{}' establecida con maxAge={}, secure={}, path={}, domain={}", + name, maxAge, cookieSecure, cookie.getPath(), cookie.getDomain()); /** * SameSite previene ataques de CSRF -> Cross Site Request Forgery * diff --git a/src/main/java/com/security/services/Impl/GoogleOAuth2UserInfoImpl.java b/src/main/java/com/security/services/Impl/GoogleOAuth2UserInfoImpl.java new file mode 100644 index 0000000..e322b48 --- /dev/null +++ b/src/main/java/com/security/services/Impl/GoogleOAuth2UserInfoImpl.java @@ -0,0 +1,37 @@ +package com.security.services.Impl; + +import com.security.services.OAuth2UserInfo; +import lombok.RequiredArgsConstructor; + +import java.util.Map; + +@RequiredArgsConstructor +public class GoogleOAuth2UserInfoImpl implements OAuth2UserInfo { + + private final Map attributes; + + @Override + public String getId() { + return (String) attributes.get("sub"); + } + + @Override + public String getEmail() { + return (String) attributes.get("email"); + } + + @Override + public String getFirstName() { + return (String) attributes.get("given_name"); + } + + @Override + public String getLastName() { + return (String) attributes.get("family_name"); + } + + @Override + public String getProfileImageUrl() { + return (String) attributes.get("picture"); + } +} diff --git a/src/main/java/com/security/services/Impl/TokenServiceImpl.java b/src/main/java/com/security/services/Impl/TokenServiceImpl.java new file mode 100644 index 0000000..8bb1e7d --- /dev/null +++ b/src/main/java/com/security/services/Impl/TokenServiceImpl.java @@ -0,0 +1,59 @@ +package com.security.services.Impl; + +import com.security.config.auth.AuthProperties; +import com.security.entity.UserEntity; +import com.security.services.TokenService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class TokenServiceImpl implements TokenService { + private final JwtEncoder jwtEncoder; + private final AuthProperties authProperties; + + @Override + public String generateAccessToken(UserEntity user) { + Instant now = Instant.now(); + + String authorities = user.getRoles().stream() + .map(role -> "ROLE_" + role.getName()) + .collect(Collectors.joining(" ")); + + JwtClaimsSet claims = JwtClaimsSet.builder() + .issuer(authProperties.getServer().getIssuer()) + .issuedAt(now) + .expiresAt(now.plus(15, ChronoUnit.MINUTES)) + .subject(user.getEmail()) + .claim("scope", "read write") + .claim("authorities", authorities) + .claim("user_id", user.getId().toString()) + .claim("email", user.getEmail()) + .build(); + + return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + } + + @Override + public String generateRefreshToken(UserEntity user) { + Instant now = Instant.now(); + + JwtClaimsSet claims = JwtClaimsSet.builder() + .issuer(authProperties.getServer().getIssuer()) + .issuedAt(now) + .expiresAt(now.plus(7, ChronoUnit.DAYS)) + .subject(user.getEmail()) + .claim("type", "refresh") + .claim("user_id", user.getId().toString()) + .build(); + + return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + } +} diff --git a/src/main/java/com/security/services/OAuth2UserInfo.java b/src/main/java/com/security/services/OAuth2UserInfo.java new file mode 100644 index 0000000..3e42a9f --- /dev/null +++ b/src/main/java/com/security/services/OAuth2UserInfo.java @@ -0,0 +1,9 @@ +package com.security.services; + +public interface OAuth2UserInfo { + String getId(); + String getEmail(); + String getFirstName(); + String getLastName(); + String getProfileImageUrl(); +} diff --git a/src/main/java/com/security/services/TokenService.java b/src/main/java/com/security/services/TokenService.java new file mode 100644 index 0000000..b6fc140 --- /dev/null +++ b/src/main/java/com/security/services/TokenService.java @@ -0,0 +1,9 @@ +package com.security.services; + +import com.security.entity.UserEntity; + +public interface TokenService { + String generateRefreshToken(UserEntity user); + + String generateAccessToken(UserEntity user); +} diff --git a/src/main/java/com/security/services/oauth2/CustomOAuth2User.java b/src/main/java/com/security/services/oauth2/CustomOAuth2User.java new file mode 100644 index 0000000..6239c7b --- /dev/null +++ b/src/main/java/com/security/services/oauth2/CustomOAuth2User.java @@ -0,0 +1,34 @@ +package com.security.services.oauth2; + +import com.security.entity.UserEntity; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +@Getter +@RequiredArgsConstructor +public class CustomOAuth2User implements OAuth2User { + private final UserEntity user; + private final Map attributes; + + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Collection getAuthorities() { + return user.getAuthorities(); + } + + @Override + public String getName() { + return user.getEmail(); + } +} diff --git a/src/main/java/com/security/services/oauth2/CustomOAuth2UserService.java b/src/main/java/com/security/services/oauth2/CustomOAuth2UserService.java new file mode 100644 index 0000000..0fe8509 --- /dev/null +++ b/src/main/java/com/security/services/oauth2/CustomOAuth2UserService.java @@ -0,0 +1,169 @@ +package com.security.services.oauth2; + +import com.security.entity.RoleEntity; +import com.security.entity.UserEntity; +import com.security.enums.AuthProvider; +import com.security.events.notification.CreatedUserEvent; +import com.security.repository.RoleRepository; +import com.security.repository.UserRepository; +import com.security.services.OAuth2UserInfo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Set; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final UserRepository userRepository; + private final RoleRepository roleRepository; + private final KafkaTemplate kafkaTemplate; + + @Override + @Transactional + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = super.loadUser(userRequest); + + try { + OAuth2User result = processOAuth2User(userRequest, oAuth2User); + log.info("✅ CustomOAuth2UserService retornó: {}", result.getClass().getName()); + return result; + } catch (Exception ex) { + log.error("❌ Error procesando usuario OAuth2", ex); + throw new OAuth2AuthenticationException("Error procesando usuario OAuth2: " + ex.getMessage()); + } + } + + private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) { + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + log.info("🔄 Procesando OAuth2 user desde: {}", registrationId); + + OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo( + registrationId, + oAuth2User.getAttributes() + ); + + if (userInfo.getEmail() == null || userInfo.getEmail().isEmpty()) { + throw new OAuth2AuthenticationException("Email no encontrado en la respuesta de OAuth2"); + } + + log.info("📧 Email extraído: {}", userInfo.getEmail()); + + UserEntity user = userRepository.findByEmail(userInfo.getEmail()) + .map(existingUser -> { + log.info("✅ Usuario existente encontrado: {}", existingUser.getEmail()); + return updateExistingUser(existingUser, userInfo); + }) + .orElseGet(() -> { + log.info("➕ Registrando nuevo usuario: {}", userInfo.getEmail()); + return registerNewUser(userInfo, registrationId); + }); + + if (user == null) { + throw new OAuth2AuthenticationException("Usuario es NULL después de guardar/actualizar"); + } + + if (user.getId() == null) { + throw new OAuth2AuthenticationException("Usuario no tiene ID después de guardar"); + } + + log.info("✅ Usuario procesado correctamente: ID={}, Email={}", user.getId(), user.getEmail()); + + + CustomOAuth2User customUser = new CustomOAuth2User(user, oAuth2User.getAttributes()); + log.info("✅ Retornando CustomOAuth2User para: {}", user.getEmail()); + return customUser; + } + + private UserEntity registerNewUser(OAuth2UserInfo userInfo, String provider) { + log.info("📝 Registrando nuevo usuario OAuth2: {}", userInfo.getEmail()); + + RoleEntity userRole = roleRepository.findByName("USER") + .orElseThrow(() -> { + log.error("❌ Rol USER no encontrado"); + return new OAuth2AuthenticationException("Rol USER no encontrado"); + }); + + UserEntity user = UserEntity.builder() + .email(userInfo.getEmail()) + .googleId(userInfo.getId()) + .provider(AuthProvider.valueOf(provider.toUpperCase())) + .enabled(true) + .roles(Set.of(userRole)) + .build(); + + UserEntity savedUser = userRepository.save(user); + log.info("✅ Usuario guardado: ID={}, Email={}", savedUser.getId(), savedUser.getEmail()); + + try { + publishUserCreatedEvent(savedUser, userInfo); + } catch (Exception e) { + log.error("⚠️ Error publicando evento de usuario creado (no crítico)", e); + } + + return savedUser; + } + + private UserEntity updateExistingUser(UserEntity existingUser, OAuth2UserInfo userInfo) { + log.info("🔄 Actualizando usuario existente: {}", existingUser.getEmail()); + + boolean updated = false; + + if (userInfo.getId() != null && !userInfo.getId().equals(existingUser.getGoogleId())) { + existingUser.setGoogleId(userInfo.getId()); + updated = true; + } + + if (updated) { + UserEntity savedUser = userRepository.save(existingUser); + log.info("✅ Usuario actualizado: ID={}, Email={}", savedUser.getId(), savedUser.getEmail()); + + try { + publishUserUpdateEvent(savedUser, userInfo); + } catch (Exception e) { + log.error("⚠️ Error publicando evento de usuario actualizado (no crítico)", e); + } + + return savedUser; + } + + return existingUser; + } + + private void publishUserCreatedEvent(UserEntity user, OAuth2UserInfo userInfo) { + CreatedUserEvent event = new CreatedUserEvent( + user.getId().toString(), + userInfo.getFirstName(), + userInfo.getLastName(), + null, + null, + userInfo.getProfileImageUrl() + ); + + log.info("📤 Publicando evento de usuario creado: {}", event); + kafkaTemplate.send("user-created-event-topic", event); + } + + private void publishUserUpdateEvent(UserEntity user, OAuth2UserInfo userInfo) { + CreatedUserEvent event = new CreatedUserEvent( + user.getId().toString(), + userInfo.getFirstName(), + userInfo.getLastName(), + null, + null, + userInfo.getProfileImageUrl() + ); + + log.info("📤 Publicando evento de usuario actualizado: {}", event); + kafkaTemplate.send("user-updated-event-topic", event); + } +} \ No newline at end of file diff --git a/src/main/java/com/security/services/oauth2/CustomOidcUser.java b/src/main/java/com/security/services/oauth2/CustomOidcUser.java new file mode 100644 index 0000000..2afffa3 --- /dev/null +++ b/src/main/java/com/security/services/oauth2/CustomOidcUser.java @@ -0,0 +1,53 @@ +package com.security.services.oauth2; + +import com.security.entity.UserEntity; +import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; + +import java.util.Collection; +import java.util.Map; + +@Getter +@RequiredArgsConstructor +public class CustomOidcUser implements OidcUser { + private final CustomOAuth2User customOAuth2User; + private final OidcIdToken idToken; + + @Override + public Map getClaims() { + return idToken.getClaims(); + } + + @Override + public OidcUserInfo getUserInfo() { + return null; + } + + @Override + public OidcIdToken getIdToken() { + return idToken; + } + + @Override + public Map getAttributes() { + return customOAuth2User.getAttributes(); + } + + @Override + public Collection getAuthorities() { + return customOAuth2User.getAuthorities(); + } + + @Override + public String getName() { + return customOAuth2User.getName(); + } + + public UserEntity getUser() { + return customOAuth2User.getUser(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/security/services/oauth2/OAuth2UserInfoFactory.java b/src/main/java/com/security/services/oauth2/OAuth2UserInfoFactory.java new file mode 100644 index 0000000..15dc689 --- /dev/null +++ b/src/main/java/com/security/services/oauth2/OAuth2UserInfoFactory.java @@ -0,0 +1,17 @@ +package com.security.services.oauth2; + +import com.security.services.Impl.GoogleOAuth2UserInfoImpl; +import com.security.services.OAuth2UserInfo; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; + + +import java.util.Map; + +public class OAuth2UserInfoFactory { + public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) { + if ("google".equalsIgnoreCase(registrationId)) { + return new GoogleOAuth2UserInfoImpl(attributes); + } + throw new OAuth2AuthenticationException("Login con " + registrationId + " no está soportado"); + } +}