├── screenshots ├── 1.PNG ├── 2.PNG ├── 3.PNG ├── 4.PNG ├── 5.PNG ├── 6.PNG ├── 7.PNG ├── 8.PNG └── spring_boot_role_permission_main_image.png ├── src ├── test │ └── java │ │ └── com │ │ └── security │ │ └── rolepermissionexample │ │ ├── builder │ │ ├── BaseBuilder.java │ │ ├── TokenBuilder.java │ │ ├── PermissionEntityBuilder.java │ │ ├── RoleEntityBuilder.java │ │ └── UserEntityBuilder.java │ │ ├── base │ │ ├── AbstractBaseServiceTest.java │ │ └── AbstractTestContainerConfiguration.java │ │ ├── auth │ │ ├── exception │ │ │ ├── RoleNotFoundExceptionTest.java │ │ │ ├── UserAlreadyExistExceptionTest.java │ │ │ ├── PermissionNotFoundExceptionTest.java │ │ │ ├── UserStatusNotValidExceptionTest.java │ │ │ └── TokenAlreadyInvalidatedExceptionTest.java │ │ ├── model │ │ │ ├── PermissionTest.java │ │ │ ├── entity │ │ │ │ ├── PermissionEntityTest.java │ │ │ │ ├── InvalidTokenEntityTest.java │ │ │ │ ├── UserEntityTest.java │ │ │ │ └── RoleEntityTest.java │ │ │ ├── enums │ │ │ │ └── TokenTypeTest.java │ │ │ ├── RoleTest.java │ │ │ └── UserTest.java │ │ ├── utils │ │ │ └── KeyConverterTest.java │ │ └── service │ │ │ └── impl │ │ │ ├── InvalidTokenServiceImplTest.java │ │ │ ├── LogoutServiceImplTest.java │ │ │ ├── LoginServiceImplTest.java │ │ │ └── RegisterServiceImplTest.java │ │ ├── product │ │ ├── exception │ │ │ ├── ProductNotFoundExceptionTest.java │ │ │ └── ProductAlreadyExistExceptionTest.java │ │ ├── model │ │ │ ├── dto │ │ │ │ └── response │ │ │ │ │ └── ProductResponseTest.java │ │ │ ├── entity │ │ │ │ └── ProductEntityTest.java │ │ │ └── ProductTest.java │ │ └── service │ │ │ └── impl │ │ │ ├── ProductDeleteServiceImplTest.java │ │ │ ├── ProductCreateServiceImplTest.java │ │ │ ├── ProductReadServiceImplTest.java │ │ │ └── ProductUpdateServiceImplTest.java │ │ └── common │ │ ├── model │ │ └── entity │ │ │ └── BaseEntityTest.java │ │ ├── config │ │ └── OpenApiConfigTest.java │ │ └── util │ │ └── ListUtilTest.java └── main │ ├── java │ └── com │ │ └── security │ │ └── rolepermissionexample │ │ ├── product │ │ ├── service │ │ │ ├── ProductDeleteService.java │ │ │ ├── ProductCreateService.java │ │ │ ├── ProductUpdateService.java │ │ │ ├── ProductReadService.java │ │ │ └── impl │ │ │ │ ├── ProductDeleteServiceImpl.java │ │ │ │ ├── ProductCreateServiceImpl.java │ │ │ │ ├── ProductReadServiceImpl.java │ │ │ │ └── ProductUpdateServiceImpl.java │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── ProductPagingRequest.java │ │ │ │ │ ├── ProductCreateRequest.java │ │ │ │ │ └── ProductUpdateRequest.java │ │ │ │ └── response │ │ │ │ │ └── ProductResponse.java │ │ │ ├── Product.java │ │ │ ├── mapper │ │ │ │ ├── ProductEntityToProductMapper.java │ │ │ │ ├── ProductToProductEntityMapper.java │ │ │ │ ├── ProductToProductResponseMapper.java │ │ │ │ ├── ListProductEntityToListProductMapper.java │ │ │ │ ├── ProductCreateRequestToProductEntityMapper.java │ │ │ │ ├── ProductUpdateRequestToProductEntityMapper.java │ │ │ │ └── CustomPageToCustomPagingResponseMapper.java │ │ │ └── entity │ │ │ │ └── ProductEntity.java │ │ ├── repository │ │ │ └── ProductRepository.java │ │ └── exception │ │ │ ├── ProductNotFoundException.java │ │ │ └── ProductAlreadyExistException.java │ │ ├── auth │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── TokenRefreshRequest.java │ │ │ │ │ ├── LoginRequest.java │ │ │ │ │ ├── TokenInvalidateRequest.java │ │ │ │ │ └── RegisterRequest.java │ │ │ │ └── response │ │ │ │ │ └── TokenResponse.java │ │ │ ├── enums │ │ │ │ ├── TokenType.java │ │ │ │ ├── UserStatus.java │ │ │ │ ├── TokenClaims.java │ │ │ │ └── ConfigurationParameter.java │ │ │ ├── Permission.java │ │ │ ├── entity │ │ │ │ ├── PermissionEntity.java │ │ │ │ ├── InvalidTokenEntity.java │ │ │ │ ├── RoleEntity.java │ │ │ │ └── UserEntity.java │ │ │ ├── Role.java │ │ │ ├── User.java │ │ │ ├── mapper │ │ │ │ ├── UserEntityToUserMapper.java │ │ │ │ ├── TokenToTokenResponseMapper.java │ │ │ │ └── RegisterRequestToUserEntityMapper.java │ │ │ └── Token.java │ │ ├── service │ │ │ ├── LogoutService.java │ │ │ ├── LoginService.java │ │ │ ├── RegisterService.java │ │ │ ├── InvalidTokenService.java │ │ │ ├── RefreshTokenService.java │ │ │ ├── impl │ │ │ │ ├── LogoutServiceImpl.java │ │ │ │ ├── InvalidTokenServiceImpl.java │ │ │ │ ├── LoginServiceImpl.java │ │ │ │ ├── RefreshTokenServiceImpl.java │ │ │ │ └── RegisterServiceImpl.java │ │ │ └── TokenService.java │ │ ├── repository │ │ │ ├── RoleRepository.java │ │ │ ├── PermissionRepository.java │ │ │ ├── InvalidTokenRepository.java │ │ │ └── UserRepository.java │ │ ├── exception │ │ │ ├── RoleNotFoundException.java │ │ │ ├── UserNotFoundException.java │ │ │ ├── UserAlreadyExistException.java │ │ │ ├── PasswordNotValidException.java │ │ │ ├── UserStatusNotValidException.java │ │ │ ├── PermissionNotFoundException.java │ │ │ └── TokenAlreadyInvalidatedException.java │ │ ├── config │ │ │ ├── TokenConfigurationParameter.java │ │ │ └── SecurityConfig.java │ │ ├── utils │ │ │ └── KeyConverter.java │ │ ├── security │ │ │ └── CustomAuthenticationEntryPoint.java │ │ ├── filter │ │ │ └── CustomBearerTokenAuthenticationFilter.java │ │ └── controller │ │ │ └── AuthController.java │ │ ├── RolepermissionexampleApplication.java │ │ └── common │ │ ├── util │ │ └── ListUtil.java │ │ ├── model │ │ ├── mapper │ │ │ └── BaseMapper.java │ │ ├── BaseDomainModel.java │ │ ├── CustomPaging.java │ │ ├── dto │ │ │ ├── request │ │ │ │ └── CustomPagingRequest.java │ │ │ └── response │ │ │ │ ├── CustomResponse.java │ │ │ │ └── CustomPagingResponse.java │ │ ├── CustomPage.java │ │ ├── CustomError.java │ │ └── entity │ │ │ └── BaseEntity.java │ │ └── config │ │ └── OpenApiConfig.java │ └── resources │ ├── application.yaml │ └── data.sql ├── .gitignore ├── Dockerfile ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── docker-compose.yml └── .github └── workflows └── ci-cd.yml /screenshots/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/1.PNG -------------------------------------------------------------------------------- /screenshots/2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/2.PNG -------------------------------------------------------------------------------- /screenshots/3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/3.PNG -------------------------------------------------------------------------------- /screenshots/4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/4.PNG -------------------------------------------------------------------------------- /screenshots/5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/5.PNG -------------------------------------------------------------------------------- /screenshots/6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/6.PNG -------------------------------------------------------------------------------- /screenshots/7.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/7.PNG -------------------------------------------------------------------------------- /screenshots/8.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/8.PNG -------------------------------------------------------------------------------- /screenshots/spring_boot_role_permission_main_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/rolepermissionexample/HEAD/screenshots/spring_boot_role_permission_main_image.png -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/builder/BaseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.builder; 2 | 3 | import lombok.SneakyThrows; 4 | 5 | public abstract class BaseBuilder { 6 | 7 | @SneakyThrows 8 | public BaseBuilder(Class clazz) { 9 | this.data = clazz.getDeclaredConstructor().newInstance(); 10 | } 11 | 12 | T data; 13 | 14 | public T build() { 15 | return data; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/base/AbstractBaseServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.base; 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith; 4 | import org.mockito.junit.jupiter.MockitoExtension; 5 | import org.mockito.junit.jupiter.MockitoSettings; 6 | import org.mockito.quality.Strictness; 7 | 8 | @ExtendWith(MockitoExtension.class) 9 | @MockitoSettings(strictness = Strictness.LENIENT) 10 | public abstract class AbstractBaseServiceTest { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/ProductDeleteService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service; 2 | 3 | /** 4 | * Service interface named {@link ProductDeleteService} for deleting products. 5 | */ 6 | public interface ProductDeleteService { 7 | 8 | /** 9 | * Deletes a product identified by its unique ID. 10 | * 11 | * @param productId The ID of the product to delete. 12 | */ 13 | void deleteProductById(final String productId); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/dto/request/TokenRefreshRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.dto.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import lombok.*; 5 | 6 | /** 7 | * Represents a request as {@link TokenRefreshRequest} to refresh a token. 8 | */ 9 | @Getter 10 | @Setter 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class TokenRefreshRequest { 15 | 16 | @NotBlank 17 | private String refreshToken; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/dto/response/TokenResponse.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.dto.response; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Represents a response as {@link TokenResponse} containing access and refresh tokens. 7 | */ 8 | @Getter 9 | @Setter 10 | @Builder 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class TokenResponse { 14 | 15 | private String accessToken; 16 | private Long accessTokenExpiresAt; 17 | private String refreshToken; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/exception/RoleNotFoundExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class RoleNotFoundExceptionTest { 8 | 9 | @Test 10 | void testDefaultConstructor() { 11 | RoleNotFoundException exception = new RoleNotFoundException(); 12 | assertNotNull(exception); 13 | assertEquals("Role not found!\n", exception.getMessage()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/enums/TokenType.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.enums; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * Enum representing the type of token used in the authentication system as {@link TokenType}. 8 | * Each enum constant holds a string value corresponding to the token type. 9 | */ 10 | @Getter 11 | @RequiredArgsConstructor 12 | public enum TokenType { 13 | 14 | BEARER("Bearer"); 15 | 16 | private final String value; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/exception/UserAlreadyExistExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class UserAlreadyExistExceptionTest { 8 | 9 | @Test 10 | void testDefaultConstructor() { 11 | UserAlreadyExistException exception = new UserAlreadyExistException(); 12 | assertNotNull(exception); 13 | assertEquals("User already exist!\n", exception.getMessage()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/exception/ProductNotFoundExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class ProductNotFoundExceptionTest { 8 | 9 | @Test 10 | void testDefaultConstructor() { 11 | ProductNotFoundException exception = new ProductNotFoundException(); 12 | assertNotNull(exception); 13 | assertEquals("Product not found!\n", exception.getMessage()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/exception/PermissionNotFoundExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class PermissionNotFoundExceptionTest { 8 | 9 | @Test 10 | void testDefaultConstructor() { 11 | PermissionNotFoundException exception = new PermissionNotFoundException(); 12 | assertNotNull(exception); 13 | assertEquals("Permission not found!\n", exception.getMessage()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Environment file 36 | .env 37 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/dto/request/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.dto.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | /** 9 | * Represents a login request as {@link LoginRequest} with email and password. 10 | */ 11 | @Getter 12 | @Setter 13 | @Builder 14 | public class LoginRequest { 15 | 16 | @NotBlank 17 | private String email; 18 | 19 | @NotBlank 20 | private String password; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/exception/UserStatusNotValidExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class UserStatusNotValidExceptionTest { 8 | 9 | @Test 10 | void testDefaultConstructor() { 11 | UserStatusNotValidException exception = new UserStatusNotValidException(); 12 | assertNotNull(exception); 13 | assertEquals("User status is not valid!\n", exception.getMessage()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/exception/ProductAlreadyExistExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class ProductAlreadyExistExceptionTest { 8 | 9 | @Test 10 | void testDefaultConstructor() { 11 | ProductAlreadyExistException exception = new ProductAlreadyExistException(); 12 | assertNotNull(exception); 13 | assertEquals("Product already exist!\n", exception.getMessage()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/dto/request/ProductPagingRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.dto.request; 2 | 3 | import com.security.rolepermissionexample.common.model.dto.request.CustomPagingRequest; 4 | import lombok.*; 5 | import lombok.experimental.SuperBuilder; 6 | 7 | /** 8 | * Represents a paging request object for retrieving products as {@link ProductPagingRequest}. 9 | */ 10 | @Getter 11 | @Setter 12 | @SuperBuilder 13 | @NoArgsConstructor 14 | public class ProductPagingRequest extends CustomPagingRequest { 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/dto/request/TokenInvalidateRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.dto.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import lombok.*; 5 | 6 | /** 7 | * Represents a request as {@link TokenInvalidateRequest} to invalidate tokens. 8 | */ 9 | @Getter 10 | @Setter 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class TokenInvalidateRequest { 15 | 16 | @NotBlank 17 | private String accessToken; 18 | 19 | @NotBlank 20 | private String refreshToken; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/exception/TokenAlreadyInvalidatedExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class TokenAlreadyInvalidatedExceptionTest { 8 | 9 | @Test 10 | void testDefaultConstructor() { 11 | TokenAlreadyInvalidatedException exception = new TokenAlreadyInvalidatedException(); 12 | assertNotNull(exception); 13 | assertEquals("Token is already invalidated!\n", exception.getMessage()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/enums/UserStatus.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.enums; 2 | 3 | /** 4 | * Enum representing the status of a user as {@link UserStatus}. 5 | * The status can be one of the following: 6 | * 11 | */ 12 | public enum UserStatus { 13 | ACTIVE, 14 | PASSIVE, 15 | SUSPENDED 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/Permission.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model; 2 | 3 | import com.security.rolepermissionexample.common.model.BaseDomainModel; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.experimental.SuperBuilder; 8 | 9 | /** 10 | * Represents a permission domain object as {@link Permission} in the application. 11 | */ 12 | @Getter 13 | @Setter 14 | @SuperBuilder 15 | @EqualsAndHashCode(callSuper = true) 16 | public class Permission extends BaseDomainModel { 17 | private String id; 18 | private String name; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/LogoutService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service; 2 | 3 | import com.security.rolepermissionexample.auth.model.dto.request.TokenInvalidateRequest; 4 | 5 | /** 6 | * Service interface named {@link LogoutService} for handling user logout operations. 7 | */ 8 | public interface LogoutService { 9 | 10 | /** 11 | * Logs out a user session based on the provided token invalidation request. 12 | * 13 | * @param tokenInvalidateRequest The request containing tokens to invalidate for logout. 14 | */ 15 | void logout(final TokenInvalidateRequest tokenInvalidateRequest); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build stage 2 | FROM maven:3.9.7-amazoncorretto-21 AS build 3 | 4 | # Copy Maven files for dependency resolution 5 | COPY pom.xml ./ 6 | COPY .mvn .mvn 7 | 8 | # Copy application source code 9 | COPY src src 10 | 11 | # Build the project and create the executable JAR 12 | RUN mvn clean install -DskipTests 13 | 14 | # Stage 2: Run stage 15 | FROM amazoncorretto:21 16 | 17 | # Set working directory 18 | WORKDIR rolepermissionexample 19 | 20 | # Copy the JAR file from the build stage 21 | COPY --from=build target/*.jar rolepermissionexample.jar 22 | 23 | # Expose port 1225 24 | EXPOSE 1225 25 | 26 | # Set the entrypoint command for running the application 27 | ENTRYPOINT ["java", "-jar", "rolepermissionexample.jar"] -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/LoginService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service; 2 | 3 | import com.security.rolepermissionexample.auth.model.Token; 4 | import com.security.rolepermissionexample.auth.model.dto.request.LoginRequest; 5 | 6 | /** 7 | * Service interface named {@link LoginService} for handling user login operations. 8 | */ 9 | public interface LoginService { 10 | 11 | /** 12 | * Performs user login based on the provided login request. 13 | * 14 | * @param loginRequest The login request containing user credentials. 15 | * @return The token representing the user's session. 16 | */ 17 | Token login(final LoginRequest loginRequest); 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/PermissionTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class PermissionTest { 8 | 9 | 10 | @Test 11 | void testEqualsAndHashCode() { 12 | 13 | // Given 14 | Permission permission1 = Permission.builder().id("123").name("READ_PRIVILEGES").build(); 15 | Permission permission3 = Permission.builder().id("456").name("WRITE_PRIVILEGES").build(); 16 | 17 | // Then 18 | assertNotEquals(permission1, permission3); 19 | assertNotEquals(permission1.hashCode(), permission3.hashCode()); 20 | 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/entity/PermissionEntity.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import com.security.rolepermissionexample.common.model.entity.BaseEntity; 4 | import jakarta.persistence.*; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | /** 9 | * Represents an entity as {@link PermissionEntity} for permissions. 10 | */ 11 | @Entity 12 | @Getter 13 | @Setter 14 | @Table(name = "PERMISSIONS") 15 | public class PermissionEntity extends BaseEntity { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.UUID) 19 | @Column(name = "ID") 20 | private String id; 21 | 22 | @Column(name = "NAME") 23 | private String name; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/dto/response/ProductResponse.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import java.math.BigDecimal; 10 | 11 | /** 12 | * Represents a response object containing product details as {@link ProductResponse}. 13 | */ 14 | @Getter 15 | @Setter 16 | @Builder 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class ProductResponse { 20 | 21 | private String id; 22 | private String name; 23 | private BigDecimal amount; 24 | private BigDecimal unitPrice; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/RolepermissionexampleApplication.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * Main class named {@link RolepermissionexampleApplication} to start the Role Permission example application. 8 | */ 9 | @SpringBootApplication 10 | public class RolepermissionexampleApplication { 11 | 12 | /** 13 | * Main method to start the Spring Boot application. 14 | * 15 | * @param args the command line arguments 16 | */ 17 | public static void main(String[] args) { 18 | SpringApplication.run(RolepermissionexampleApplication.class, args); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/RegisterService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service; 2 | 3 | import com.security.rolepermissionexample.auth.model.User; 4 | import com.security.rolepermissionexample.auth.model.dto.request.RegisterRequest; 5 | 6 | /** 7 | * Service interface named {@link RegisterService} for user registration operations. 8 | */ 9 | public interface RegisterService { 10 | 11 | /** 12 | * Registers a new user based on the provided registration request. 13 | * 14 | * @param registerRequest The registration request containing user details. 15 | * @return The registered user entity. 16 | */ 17 | User registerUser(final RegisterRequest registerRequest); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/util/ListUtil.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.util; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Utility class named {@link ListUtil} for converting objects to lists. 9 | */ 10 | @UtilityClass 11 | public class ListUtil { 12 | 13 | /** 14 | * Converts an object to a list of a specified class type. 15 | * 16 | * @param object The object to convert. 17 | * @param clazz The target class type. 18 | * @param The type of elements in the list. 19 | * @return List of elements of type C. 20 | */ 21 | public List to(Object object, Class clazz) { 22 | return (List) object; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/Role.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.PermissionEntity; 4 | import com.security.rolepermissionexample.common.model.BaseDomainModel; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.experimental.SuperBuilder; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Represents a role domain object as {@link Role} in the application. 14 | */ 15 | @Getter 16 | @Setter 17 | @SuperBuilder 18 | @EqualsAndHashCode(callSuper = true) 19 | public class Role extends BaseDomainModel { 20 | 21 | private String id; 22 | private String name; 23 | private List permissions; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/InvalidTokenService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service; 2 | 3 | import java.util.Set; 4 | 5 | /** 6 | * Service interface named {@link InvalidTokenService} for managing invalid tokens. 7 | */ 8 | public interface InvalidTokenService { 9 | 10 | /** 11 | * Invalidates multiple tokens identified by their IDs. 12 | * 13 | * @param tokenIds The set of token IDs to invalidate. 14 | */ 15 | void invalidateTokens(final Set tokenIds); 16 | 17 | /** 18 | * Checks if a token identified by its ID is invalid. 19 | * 20 | * @param tokenId The ID of the token to check for invalidity. 21 | */ 22 | void checkForInvalidityOfToken(final String tokenId); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/RefreshTokenService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service; 2 | 3 | import com.security.rolepermissionexample.auth.model.Token; 4 | import com.security.rolepermissionexample.auth.model.dto.request.TokenRefreshRequest; 5 | 6 | /** 7 | * Service interface named {@link RefreshTokenService} for refreshing authentication tokens. 8 | */ 9 | public interface RefreshTokenService { 10 | 11 | /** 12 | * Refreshes an authentication token based on the provided refresh token request. 13 | * 14 | * @param tokenRefreshRequest The request containing the refresh token. 15 | * @return The refreshed authentication token. 16 | */ 17 | Token refreshToken(final TokenRefreshRequest tokenRefreshRequest); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/ProductCreateService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service; 2 | 3 | import com.security.rolepermissionexample.product.model.Product; 4 | import com.security.rolepermissionexample.product.model.dto.request.ProductCreateRequest; 5 | 6 | /** 7 | * Service interface named {@link ProductCreateService} for creating products. 8 | */ 9 | public interface ProductCreateService { 10 | 11 | /** 12 | * Creates a new product based on the provided product creation request. 13 | * 14 | * @param productCreateRequest The request containing data to create the product. 15 | * @return The created Product object. 16 | */ 17 | Product createProduct(final ProductCreateRequest productCreateRequest); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.repository; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.RoleEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * Repository interface named {@link RoleRepository} for managing {@link RoleEntity} entities. 10 | */ 11 | public interface RoleRepository extends JpaRepository { 12 | 13 | /** 14 | * Finds a role entity by its name. 15 | * 16 | * @param name The name of the role to find. 17 | * @return An {@link Optional} containing the found {@link RoleEntity}, or empty if not found. 18 | */ 19 | Optional findByName(String name); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.repository; 2 | 3 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | /** 7 | * Repository interface named {@link ProductRepository} for managing ProductEntity objects in the database. 8 | */ 9 | public interface ProductRepository extends JpaRepository { 10 | 11 | /** 12 | * Checks if a product entity with the given name exists in the database. 13 | * 14 | * @param name the name of the product to check for existence 15 | * @return true if a product entity with the given name exists, false otherwise 16 | */ 17 | boolean existsProductEntityByName(final String name); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/repository/PermissionRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.repository; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.PermissionEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * Repository interface named {@link PermissionRepository} for managing {@link PermissionEntity} entities. 10 | */ 11 | public interface PermissionRepository extends JpaRepository { 12 | 13 | /** 14 | * Finds a permission entity by its name. 15 | * 16 | * @param name The name of the permission to find. 17 | * @return An {@link Optional} containing the found {@link PermissionEntity}, or empty if not found. 18 | */ 19 | Optional findByName(String name); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model; 2 | 3 | import com.security.rolepermissionexample.common.model.BaseDomainModel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | import java.math.BigDecimal; 12 | 13 | /** 14 | * Represents a domain model for a product as {@link Product}. 15 | */ 16 | @Getter 17 | @Setter 18 | @SuperBuilder 19 | @EqualsAndHashCode(callSuper = true) 20 | @NoArgsConstructor 21 | @AllArgsConstructor 22 | public class Product extends BaseDomainModel { 23 | 24 | private String id; 25 | private String name; 26 | private BigDecimal amount; 27 | private BigDecimal unitPrice; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/entity/InvalidTokenEntity.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import com.security.rolepermissionexample.common.model.entity.BaseEntity; 4 | import jakarta.persistence.*; 5 | import lombok.*; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | /** 9 | * Represents an entity as {@link InvalidTokenEntity} for invalid tokens. 10 | */ 11 | @Entity 12 | @Getter 13 | @Setter 14 | @SuperBuilder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @EqualsAndHashCode(callSuper = true) 18 | @Table(name = "INVALID_TOKEN") 19 | public class InvalidTokenEntity extends BaseEntity { 20 | 21 | @Id 22 | @Column(name = "ID") 23 | @GeneratedValue(strategy = GenerationType.UUID) 24 | private String id; 25 | 26 | @Column(name = "TOKEN_ID") 27 | private String tokenId; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 1225 3 | 4 | # MYSQL 5 | spring: 6 | config: 7 | import: optional:file:.env[.properties] 8 | datasource: 9 | name: mysql 10 | url: jdbc:mysql://${SECURITY_DB_IP:localhost}:${SECURITY_DB_PORT:3306}/springbootrolepermissionexample 11 | username: ${DATABASE_USERNAME:root} 12 | password: ${DATABASE_PASSWORD:password} 13 | jpa: 14 | properties: 15 | hibernate: 16 | dialect: org.hibernate.dialect.MySQLDialect 17 | format_sql: true 18 | hibernate: 19 | ddl-auto: update 20 | naming: 21 | physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 22 | defer-datasource-initialization: true 23 | sql: 24 | init: 25 | mode: always 26 | 27 | 28 | # SWAGGER 29 | springdoc: 30 | api-docs: 31 | enabled: true 32 | show-actuator: true 33 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/entity/PermissionEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class PermissionEntityTest { 8 | 9 | @Test 10 | void testGettersAndSettersForId() { 11 | 12 | // Given 13 | String id = "test-id"; 14 | PermissionEntity permissionEntity = new PermissionEntity(); 15 | 16 | // When 17 | permissionEntity.setId(id); 18 | 19 | // Then 20 | assertEquals(id, permissionEntity.getId()); 21 | 22 | } 23 | 24 | @Test 25 | void testIdFieldInitialValue() { 26 | 27 | // Given 28 | PermissionEntity permissionEntity = new PermissionEntity(); 29 | 30 | // Then 31 | assertNull(permissionEntity.getId()); 32 | 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/builder/TokenBuilder.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.builder; 2 | 3 | import com.security.rolepermissionexample.auth.model.enums.TokenClaims; 4 | import io.jsonwebtoken.Claims; 5 | import io.jsonwebtoken.Jwts; 6 | import lombok.experimental.UtilityClass; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | 12 | @UtilityClass 13 | public class TokenBuilder { 14 | 15 | public Claims getValidClaims(String userId, String firstName) { 16 | Map mockClaimsMap = new HashMap<>(); 17 | mockClaimsMap.put(TokenClaims.JWT_ID.getValue(), UUID.randomUUID().toString()); 18 | mockClaimsMap.put(TokenClaims.USER_ID.getValue(), userId); 19 | mockClaimsMap.put(TokenClaims.USER_FIRST_NAME.getValue(), firstName); 20 | return Jwts.claims(mockClaimsMap); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/ProductUpdateService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service; 2 | 3 | import com.security.rolepermissionexample.product.model.Product; 4 | import com.security.rolepermissionexample.product.model.dto.request.ProductUpdateRequest; 5 | 6 | /** 7 | * Service interface named {@link ProductUpdateService} for updating products. 8 | */ 9 | public interface ProductUpdateService { 10 | 11 | /** 12 | * Updates a product identified by its unique ID using the provided update request. 13 | * 14 | * @param productId The ID of the product to update. 15 | * @param productUpdateRequest The request containing updated data for the product. 16 | * @return The updated Product object. 17 | */ 18 | Product updateProductById(final String productId, final ProductUpdateRequest productUpdateRequest); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/builder/PermissionEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.builder; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.PermissionEntity; 4 | 5 | import java.util.UUID; 6 | 7 | public class PermissionEntityBuilder extends BaseBuilder { 8 | 9 | public PermissionEntityBuilder() { 10 | super(PermissionEntity.class); 11 | } 12 | 13 | public PermissionEntityBuilder withValidFields() { 14 | return this 15 | .withId(UUID.randomUUID().toString()) 16 | .withName("READ_PRIVILEGES"); 17 | } 18 | 19 | public PermissionEntityBuilder withId(String id) { 20 | data.setId(id); 21 | return this; 22 | } 23 | 24 | public PermissionEntityBuilder withName(String name) { 25 | data.setName(name); 26 | return this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/repository/InvalidTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.repository; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.InvalidTokenEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * Repository interface named {@link InvalidTokenRepository} for managing {@link InvalidTokenEntity} entities. 10 | */ 11 | public interface InvalidTokenRepository extends JpaRepository { 12 | 13 | /** 14 | * Finds an invalid token entity by its tokenId. 15 | * 16 | * @param tokenId The tokenId of the invalid token to find. 17 | * @return An {@link Optional} containing the found {@link InvalidTokenEntity}, or empty if not found. 18 | */ 19 | Optional findByTokenId(final String tokenId); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/mapper/BaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model.mapper; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | 6 | /** 7 | * Interface for mapping entities or domain models (S) to DTOs (T). 8 | * 9 | * @param the source type to map from 10 | * @param the target type to map to 11 | */ 12 | public interface BaseMapper { 13 | 14 | /** 15 | * Maps a single source object to a target object. 16 | * 17 | * @param source the source object to map 18 | * @return the mapped target object 19 | */ 20 | T map(S source); 21 | 22 | /** 23 | * Maps a collection of source objects to a list of target objects. 24 | * 25 | * @param sources the collection of source objects to map 26 | * @return the list of mapped target objects 27 | */ 28 | List map(Collection sources); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/enums/TokenClaims.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.enums; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * Enum representing various claims used in JWT tokens as {@link TokenClaims}. 8 | * Each enum constant holds a string value corresponding to the claim's name. 9 | */ 10 | @Getter 11 | @RequiredArgsConstructor 12 | public enum TokenClaims { 13 | 14 | JWT_ID("jti"), 15 | USER_ID("userId"), 16 | USER_STATUS("userStatus"), 17 | USER_FIRST_NAME("userFirstName"), 18 | USER_LAST_NAME("userLastName"), 19 | USER_PERMISSIONS("userPermissions"), 20 | USER_EMAIL("userEmail"), 21 | USER_PHONE_NUMBER("userPhoneNumber"), 22 | STORE_TITLE("storeTitle"), 23 | ISSUED_AT("iat"), 24 | EXPIRES_AT("exp"), 25 | ALGORITHM("alg"), 26 | TYP("typ"); 27 | 28 | private final String value; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/User.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.RoleEntity; 4 | import com.security.rolepermissionexample.auth.model.enums.UserStatus; 5 | import com.security.rolepermissionexample.common.model.BaseDomainModel; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Represents a user domain object as {@link User} in the application. 15 | */ 16 | @Getter 17 | @Setter 18 | @SuperBuilder 19 | @EqualsAndHashCode(callSuper = true) 20 | public class User extends BaseDomainModel { 21 | 22 | private String id; 23 | private String email; 24 | private String firstName; 25 | private String lastName; 26 | private String phoneNumber; 27 | private UserStatus userStatus; 28 | private List roles; 29 | private List permissions; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/BaseDomainModel.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.MappedSuperclass; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | import java.time.LocalDateTime; 12 | 13 | /** 14 | * Base class named {@link BaseDomainModel} for domain models with common audit fields. 15 | */ 16 | @Getter 17 | @Setter 18 | @SuperBuilder 19 | @MappedSuperclass 20 | @NoArgsConstructor 21 | @AllArgsConstructor 22 | public abstract class BaseDomainModel { 23 | 24 | @Column(name = "CREATED_AT") 25 | private LocalDateTime createdAt; 26 | 27 | @Column(name = "CREATED_BY") 28 | private String createdBy; 29 | 30 | @Column(name = "UPDATED_AT") 31 | private LocalDateTime updatedAt; 32 | 33 | @Column(name = "UPDATED_BY") 34 | private String updatedBy; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 20 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/dto/request/ProductCreateRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.dto.request; 2 | 3 | import jakarta.validation.constraints.DecimalMin; 4 | import jakarta.validation.constraints.Size; 5 | import lombok.*; 6 | 7 | import java.math.BigDecimal; 8 | 9 | /** 10 | * Represents a request object for creating a new product as {@link ProductCreateRequest}. 11 | */ 12 | @Getter 13 | @Setter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Builder 17 | public class ProductCreateRequest { 18 | 19 | @Size( 20 | min = 1, 21 | message = "Product name can't be blank." 22 | ) 23 | private String name; 24 | 25 | @DecimalMin( 26 | value = "0.0001", 27 | message = "Amount must be bigger than 0" 28 | ) 29 | private BigDecimal amount; 30 | 31 | @DecimalMin( 32 | value = "0.0001", 33 | message = "Unit Price must be bigger than 0" 34 | ) 35 | private BigDecimal unitPrice; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/dto/request/ProductUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.dto.request; 2 | 3 | import jakarta.validation.constraints.DecimalMin; 4 | import jakarta.validation.constraints.Size; 5 | import lombok.*; 6 | 7 | import java.math.BigDecimal; 8 | 9 | /** 10 | * Represents a request object for updating an existing product as {@link ProductUpdateRequest}. 11 | */ 12 | @Getter 13 | @Setter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Builder 17 | public class ProductUpdateRequest { 18 | 19 | @Size( 20 | min = 1, 21 | message = "Product name can't be blank." 22 | ) 23 | private String name; 24 | 25 | @DecimalMin( 26 | value = "0.0001", 27 | message = "Amount must be bigger than 0" 28 | ) 29 | private BigDecimal amount; 30 | 31 | @DecimalMin( 32 | value = "0.0001", 33 | message = "Unit Price must be bigger than 0" 34 | ) 35 | private BigDecimal unitPrice; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/CustomPaging.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.experimental.SuperBuilder; 9 | 10 | /** 11 | * Represents pagination parameters for querying data as {@link CustomPaging}. 12 | */ 13 | @Getter 14 | @Setter 15 | @NoArgsConstructor 16 | @SuperBuilder 17 | public class CustomPaging { 18 | 19 | @Min(value = 1, message = "Page number must be bigger than 0") 20 | private Integer pageNumber; 21 | 22 | @Min(value = 1, message = "Page size must be bigger than 0") 23 | private Integer pageSize; 24 | 25 | /** 26 | * Returns the zero-based page number for internal processing. 27 | * 28 | * @return the zero-based page number derived from the specified page number 29 | */ 30 | public Integer getPageNumber() { 31 | return pageNumber - 1; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/exception/RoleNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link RoleNotFoundException} thrown when the specified role is not found. 7 | */ 8 | public class RoleNotFoundException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -6226206922525682121L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | Role not found! 15 | """; 16 | 17 | /** 18 | * Constructs a new {@link RoleNotFoundException} with the default message. 19 | */ 20 | public RoleNotFoundException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new {@link RoleNotFoundException} with the default message and an additional message. 26 | * 27 | * @param message the additional message to include. 28 | */ 29 | public RoleNotFoundException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link UserNotFoundException } thrown when the specified user is not found. 7 | */ 8 | public class UserNotFoundException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -6226206922525682121L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | User not found! 15 | """; 16 | 17 | /** 18 | * Constructs a new {@link UserNotFoundException} with the default message. 19 | */ 20 | public UserNotFoundException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new {@link UserNotFoundException} with the default message and an additional message. 26 | * 27 | * @param message the additional message to include. 28 | */ 29 | public UserNotFoundException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/builder/RoleEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.builder; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.PermissionEntity; 4 | import com.security.rolepermissionexample.auth.model.entity.RoleEntity; 5 | 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | public class RoleEntityBuilder extends BaseBuilder { 10 | 11 | public RoleEntityBuilder() { 12 | super(RoleEntity.class); 13 | } 14 | 15 | public RoleEntityBuilder withValidFields() { 16 | return this 17 | .withId(UUID.randomUUID().toString()) 18 | .withName("Admin"); 19 | } 20 | 21 | public RoleEntityBuilder withId(String id) { 22 | data.setId(id); 23 | return this; 24 | } 25 | 26 | public RoleEntityBuilder withName(String name) { 27 | data.setName(name); 28 | return this; 29 | } 30 | 31 | public RoleEntityBuilder withPermissions(List permissions) { 32 | data.setPermissions(permissions); 33 | return this; 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/exception/UserAlreadyExistException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link UserAlreadyExistException} thrown when a user already exists. 7 | */ 8 | public class UserAlreadyExistException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -3898725915862534787L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | User already exist! 15 | """; 16 | 17 | /** 18 | * Constructs a new {@link UserAlreadyExistException} with the default message. 19 | */ 20 | public UserAlreadyExistException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new {@link UserAlreadyExistException} with the default message and an additional message. 26 | * 27 | * @param message the additional message to include. 28 | */ 29 | public UserAlreadyExistException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/exception/PasswordNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link PasswordNotValidException} thrown when the password is not valid. 7 | */ 8 | public class PasswordNotValidException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -5289337704108394302L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | Password is not valid! 15 | """; 16 | 17 | /** 18 | * Constructs a new {@link PasswordNotValidException} with the default message. 19 | */ 20 | public PasswordNotValidException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new {@link PasswordNotValidException} with the default message and an additional message. 26 | * 27 | * @param message the additional message to include. 28 | */ 29 | public PasswordNotValidException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/entity/RoleEntity.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import com.security.rolepermissionexample.common.model.entity.BaseEntity; 4 | import jakarta.persistence.*; 5 | import lombok.*; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Represents an entity as {@link RoleEntity} for roles. 12 | */ 13 | @Entity 14 | @Getter 15 | @Setter 16 | @SuperBuilder 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | @EqualsAndHashCode(callSuper = true) 20 | @Table(name = "ROLES") 21 | public class RoleEntity extends BaseEntity { 22 | 23 | @Id 24 | @GeneratedValue(strategy = GenerationType.UUID) 25 | @Column(name = "ID") 26 | private String id; 27 | 28 | @Column(name = "NAME") 29 | private String name; 30 | 31 | @ManyToMany 32 | @JoinTable( 33 | name = "ROLE_PERMISSION_RELATION_TABLE", 34 | joinColumns = @JoinColumn(name = "ROLE_ID"), 35 | inverseJoinColumns = @JoinColumn(name = "PERMISSION_ID") 36 | ) 37 | private List permissions; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/exception/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link ProductNotFoundException} thrown when a requested product cannot be found. 7 | */ 8 | public class ProductNotFoundException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = 5854010258697200749L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | Product not found! 15 | """; 16 | 17 | /** 18 | * Constructs a new ProductNotFoundException with a default message. 19 | */ 20 | public ProductNotFoundException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new ProductNotFoundException with a custom message appended to the default message. 26 | * 27 | * @param message the custom message indicating details about the exception 28 | */ 29 | public ProductNotFoundException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/exception/UserStatusNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link UserStatusNotValidException} thrown when the user status is not valid. 7 | */ 8 | public class UserStatusNotValidException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -3793140292762351950L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | User status is not valid! 15 | """; 16 | 17 | /** 18 | * Constructs a new {@link UserStatusNotValidException} with the default message. 19 | */ 20 | public UserStatusNotValidException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new {@link UserStatusNotValidException} with the default message and an additional message. 26 | * 27 | * @param message the additional message to include. 28 | */ 29 | public UserStatusNotValidException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.repository; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * Repository interface named {@link UserRepository} for managing {@link UserEntity} entities. 10 | */ 11 | public interface UserRepository extends JpaRepository { 12 | 13 | /** 14 | * Checks if a user entity exists with the given email. 15 | * 16 | * @param email The email address to check. 17 | * @return {@code true} if a user entity exists with the email; otherwise {@code false}. 18 | */ 19 | boolean existsUserEntityByEmail(final String email); 20 | 21 | /** 22 | * Finds a user entity by its email address. 23 | * 24 | * @param email The email address of the user to find. 25 | * @return An {@link Optional} containing the found {@link UserEntity}, or empty if not found. 26 | */ 27 | Optional findUserEntityByEmail(final String email); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/exception/PermissionNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link PermissionNotFoundException} thrown when the specified permission is not found. 7 | */ 8 | public class PermissionNotFoundException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -6226206922525682121L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | Permission not found! 15 | """; 16 | 17 | /** 18 | * Constructs a new {@link PermissionNotFoundException} with the default message. 19 | */ 20 | public PermissionNotFoundException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new {@link PermissionNotFoundException} with the default message and an additional message. 26 | * 27 | * @param message the additional message to include. 28 | */ 29 | public PermissionNotFoundException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/ProductReadService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service; 2 | 3 | import com.security.rolepermissionexample.common.model.CustomPage; 4 | import com.security.rolepermissionexample.product.model.Product; 5 | import com.security.rolepermissionexample.product.model.dto.request.ProductPagingRequest; 6 | 7 | /** 8 | * Service interface named {@link ProductReadService} for reading products. 9 | */ 10 | public interface ProductReadService { 11 | 12 | /** 13 | * Retrieves a product by its unique ID. 14 | * 15 | * @param productId The ID of the product to retrieve. 16 | * @return The Product object corresponding to the given ID. 17 | */ 18 | Product getProductById(final String productId); 19 | 20 | /** 21 | * Retrieves a page of products based on the paging request criteria. 22 | * 23 | * @param productPagingRequest The paging request criteria. 24 | * @return A CustomPage containing the list of products that match the paging criteria. 25 | */ 26 | CustomPage getProducts(final ProductPagingRequest productPagingRequest); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/exception/ProductAlreadyExistException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link ProductAlreadyExistException} thrown when attempting to create a product that already exists. 7 | */ 8 | public class ProductAlreadyExistException extends RuntimeException{ 9 | 10 | @Serial 11 | private static final long serialVersionUID = 53457089789182737L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | Product already exist! 15 | """; 16 | 17 | /** 18 | * Constructs a new ProductAlreadyExistException with a default message. 19 | */ 20 | public ProductAlreadyExistException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new ProductAlreadyExistException with a custom message appended to the default message. 26 | * 27 | * @param message the custom message indicating details about the exception 28 | */ 29 | public ProductAlreadyExistException(final String message) { 30 | super(DEFAULT_MESSAGE + " " + message); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/base/AbstractTestContainerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.base; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.springframework.test.context.DynamicPropertyRegistry; 5 | import org.springframework.test.context.DynamicPropertySource; 6 | import org.testcontainers.containers.MySQLContainer; 7 | import org.testcontainers.junit.jupiter.Testcontainers; 8 | 9 | @Testcontainers 10 | public abstract class AbstractTestContainerConfiguration { 11 | 12 | static MySQLContainer MYSQL_CONTAINER = new MySQLContainer<>("mysql:8.0.33"); 13 | 14 | @BeforeAll 15 | static void beforeAll() { 16 | MYSQL_CONTAINER.withReuse(true); 17 | MYSQL_CONTAINER.start(); 18 | } 19 | 20 | @DynamicPropertySource 21 | private static void overrideProps(DynamicPropertyRegistry dynamicPropertyRegistry) { 22 | dynamicPropertyRegistry.add("spring.datasource.username", MYSQL_CONTAINER::getUsername); 23 | dynamicPropertyRegistry.add("spring.datasource.password", MYSQL_CONTAINER::getPassword); 24 | dynamicPropertyRegistry.add("spring.datasource.url", MYSQL_CONTAINER::getJdbcUrl); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/exception/TokenAlreadyInvalidatedException.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | /** 6 | * Exception class named {@link TokenAlreadyInvalidatedException} thrown when the token is already invalidated. 7 | */ 8 | public class TokenAlreadyInvalidatedException extends RuntimeException { 9 | 10 | @Serial 11 | private static final long serialVersionUID = -6924357230755962371L; 12 | 13 | private static final String DEFAULT_MESSAGE = """ 14 | Token is already invalidated! 15 | """; 16 | 17 | /** 18 | * Constructs a new {@link TokenAlreadyInvalidatedException} with the default message. 19 | */ 20 | public TokenAlreadyInvalidatedException() { 21 | super(DEFAULT_MESSAGE); 22 | } 23 | 24 | /** 25 | * Constructs a new {@link TokenAlreadyInvalidatedException} with the default message and token ID. 26 | * 27 | * @param tokenId the token ID that is already invalidated. 28 | */ 29 | public TokenAlreadyInvalidatedException(final String tokenId) { 30 | super(DEFAULT_MESSAGE + " TokenID = " + tokenId); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/dto/request/CustomPagingRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model.dto.request; 2 | 3 | import com.security.rolepermissionexample.common.model.CustomPaging; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.experimental.SuperBuilder; 8 | import org.springframework.data.domain.PageRequest; 9 | import org.springframework.data.domain.Pageable; 10 | 11 | /** 12 | * Represents a request object named {@link CustomPagingRequest} for custom paging configuration. 13 | * Includes pagination details using CustomPaging. 14 | */ 15 | @Getter 16 | @Setter 17 | @SuperBuilder 18 | @NoArgsConstructor 19 | public class CustomPagingRequest { 20 | 21 | private CustomPaging pagination; 22 | 23 | /** 24 | * Converts CustomPagingRequest to a Pageable object used for pagination in queries. 25 | * 26 | * @return Pageable object configured with page number and page size. 27 | */ 28 | public Pageable toPageable() { 29 | return PageRequest.of( 30 | Math.toIntExact(pagination.getPageNumber()), 31 | Math.toIntExact(pagination.getPageSize()) 32 | ); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/enums/TokenTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.enums; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class TokenTypeTest { 8 | 9 | @Test 10 | void testTokenTypeValue() { 11 | // Given 12 | TokenType tokenType = TokenType.BEARER; 13 | 14 | // When 15 | String value = tokenType.getValue(); 16 | 17 | // Then 18 | assertEquals("Bearer", value); 19 | } 20 | 21 | @Test 22 | void testTokenTypeEnum() { 23 | // Given 24 | TokenType tokenType = TokenType.BEARER; 25 | 26 | // When 27 | TokenType[] tokenTypes = TokenType.values(); 28 | 29 | // Then 30 | assertTrue(tokenTypes.length == 1); 31 | assertEquals(TokenType.BEARER, tokenTypes[0]); 32 | } 33 | 34 | @Test 35 | void testValueOfTokenType() { 36 | // Given 37 | String tokenTypeName = "BEARER"; 38 | 39 | // When 40 | TokenType tokenType = TokenType.valueOf(tokenTypeName); 41 | 42 | // Then 43 | assertEquals(TokenType.BEARER, tokenType); 44 | assertEquals("Bearer", tokenType.getValue()); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/mapper/UserEntityToUserMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.mapper; 2 | 3 | import com.security.rolepermissionexample.auth.model.User; 4 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 5 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | /** 10 | * Mapper interface namec {@link UserEntityToUserMapper} for mapping {@link UserEntity} to {@link User}. 11 | */ 12 | @Mapper 13 | public interface UserEntityToUserMapper extends BaseMapper { 14 | 15 | /** 16 | * Maps a {@link UserEntity} object to a {@link User} object. 17 | * 18 | * @param source The {@link UserEntity} object to map. 19 | * @return A {@link User} object mapped from the {@link UserEntity}. 20 | */ 21 | @Override 22 | User map(UserEntity source); 23 | 24 | /** 25 | * Initializes and returns an instance of {@code UserEntityToUserMapper}. 26 | * 27 | * @return An initialized {@code UserEntityToUserMapper} instance. 28 | */ 29 | static UserEntityToUserMapper initialize() { 30 | return Mappers.getMapper(UserEntityToUserMapper.class); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/mapper/ProductEntityToProductMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.mapper; 2 | 3 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 4 | import com.security.rolepermissionexample.product.model.Product; 5 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | /** 10 | * Mapper interface named {@link ProductEntityToProductMapper} for converting {@link ProductEntity} to {@link Product}. 11 | */ 12 | @Mapper 13 | public interface ProductEntityToProductMapper extends BaseMapper { 14 | 15 | /** 16 | * Maps ProductEntity to Product. 17 | * 18 | * @param source The ProductEntity object to map. 19 | * @return Product object containing mapped data. 20 | */ 21 | @Override 22 | Product map(ProductEntity source); 23 | 24 | /** 25 | * Initializes and returns an instance of ProductEntityToProductMapper. 26 | * 27 | * @return Initialized ProductEntityToProductMapper instance. 28 | */ 29 | static ProductEntityToProductMapper initialize() { 30 | return Mappers.getMapper(ProductEntityToProductMapper.class); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/mapper/ProductToProductEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.mapper; 2 | 3 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 4 | import com.security.rolepermissionexample.product.model.Product; 5 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | /** 10 | * Mapper interface named {@link ProductToProductEntityMapper} for converting {@link Product} to {@link ProductEntity}. 11 | */ 12 | @Mapper 13 | public interface ProductToProductEntityMapper extends BaseMapper { 14 | 15 | /** 16 | * Maps Product to ProductEntity. 17 | * 18 | * @param source The Product object to map. 19 | * @return ProductEntity object containing mapped data. 20 | */ 21 | @Override 22 | ProductEntity map(Product source); 23 | 24 | /** 25 | * Initializes and returns an instance of ProductToProductEntityMapper. 26 | * 27 | * @return Initialized ProductToProductEntityMapper instance. 28 | */ 29 | static ProductToProductEntityMapper initialize() { 30 | return Mappers.getMapper(ProductToProductEntityMapper.class); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/RoleTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class RoleTest { 10 | 11 | @Test 12 | void testEqualsAndHashCode() { 13 | 14 | // Given 15 | Permission permission1 = Permission.builder().id("1").name("READ_PRIVILEGES").build(); 16 | Permission permission2 = Permission.builder().id("2").name("WRITE_PRIVILEGES").build(); 17 | 18 | Role role1 = Role.builder() 19 | .id("123") 20 | .name("Admin") 21 | .permissions(List.of(permission1, permission2)) 22 | .build(); 23 | 24 | Role role2 = Role.builder() 25 | .id("123") 26 | .name("Admin") 27 | .permissions(List.of(permission1, permission2)) 28 | .build(); 29 | 30 | Role role3 = Role.builder() 31 | .id("456") 32 | .name("User") 33 | .permissions(List.of(permission1)) 34 | .build(); 35 | 36 | // Then 37 | assertNotEquals(role1, role3); 38 | assertNotEquals(role1.hashCode(), role3.hashCode()); 39 | 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/mapper/ProductToProductResponseMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.mapper; 2 | 3 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 4 | import com.security.rolepermissionexample.product.model.Product; 5 | import com.security.rolepermissionexample.product.model.dto.response.ProductResponse; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | /** 10 | * Mapper interface named {@link ProductToProductResponseMapper} for converting {@link Product} to {@link ProductResponse}. 11 | */ 12 | @Mapper 13 | public interface ProductToProductResponseMapper extends BaseMapper { 14 | 15 | /** 16 | * Maps Product to ProductResponse. 17 | * 18 | * @param source The Product object to map. 19 | * @return ProductResponse object containing mapped data. 20 | */ 21 | @Override 22 | ProductResponse map(Product source); 23 | 24 | /** 25 | * Initializes and returns an instance of ProductToProductResponseMapper. 26 | * 27 | * @return Initialized ProductToProductResponseMapper instance. 28 | */ 29 | static ProductToProductResponseMapper initialize() { 30 | return Mappers.getMapper(ProductToProductResponseMapper.class); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/mapper/TokenToTokenResponseMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.mapper; 2 | 3 | import com.security.rolepermissionexample.auth.model.Token; 4 | import com.security.rolepermissionexample.auth.model.dto.response.TokenResponse; 5 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | /** 10 | * Mapper interface named {@link TokenToTokenResponseMapper} for mapping {@link Token} to {@link TokenResponse}. 11 | */ 12 | @Mapper 13 | public interface TokenToTokenResponseMapper extends BaseMapper { 14 | 15 | /** 16 | * Maps a {@link Token} object to a {@link TokenResponse} object. 17 | * 18 | * @param source The {@link Token} object to map. 19 | * @return A {@link TokenResponse} object mapped from the {@link Token}. 20 | */ 21 | @Override 22 | TokenResponse map(Token source); 23 | 24 | /** 25 | * Initializes and returns an instance of {@code TokenToTokenResponseMapper}. 26 | * 27 | * @return An initialized {@code TokenToTokenResponseMapper} instance. 28 | */ 29 | static TokenToTokenResponseMapper initialize() { 30 | return Mappers.getMapper(TokenToTokenResponseMapper.class); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | 5 | database: 6 | container_name: database 7 | image: mysql:8.0.33 8 | restart: always 9 | env_file: 10 | - .env # Use the .env file for environment variables 11 | environment: 12 | MYSQL_DATABASE: springbootrolepermissionexample 13 | MYSQL_PASSWORD: ${DATABASE_PASSWORD} 14 | MYSQL_ROOT_PASSWORD: ${DATABASE_PASSWORD} 15 | MYSQL_ROOT_HOST: '%' 16 | MYSQL_PORT: 3307 17 | volumes: 18 | - ./db:/var/lib/mysql 19 | ports: 20 | - "3307:3306" 21 | networks: 22 | - rolepermissionNetwork 23 | 24 | 25 | rolepermissionexample: 26 | image: 'rolepermissionexample:latest' 27 | build: 28 | context: . 29 | dockerfile: Dockerfile 30 | container_name: rolepermissionexample 31 | restart: on-failure 32 | env_file: 33 | - .env # Use the .env file for environment variables 34 | ports: 35 | - "1225:1225" 36 | environment: 37 | - server.port=1225 38 | - spring.datasource.username=${DATABASE_USERNAME} 39 | - spring.datasource.password=${DATABASE_PASSWORD} 40 | - SECURITY_DB_IP=database 41 | - SECURITY_DB_PORT=3307 42 | - spring.datasource.url=jdbc:mysql://host.docker.internal:3307/springbootrolepermissionexample 43 | depends_on: 44 | - database 45 | networks: 46 | - rolepermissionNetwork 47 | 48 | networks: 49 | rolepermissionNetwork: -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/Token.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import org.springframework.util.StringUtils; 6 | 7 | /** 8 | * Represents a token domain object as {@link Token} used in the application. 9 | */ 10 | @Getter 11 | @Builder 12 | public class Token { 13 | 14 | private String accessToken; 15 | private Long accessTokenExpiresAt; 16 | private String refreshToken; 17 | 18 | private static final String TOKEN_PREFIX = "Bearer "; 19 | 20 | /** 21 | * Checks if the provided authorization header is a bearer token. 22 | * 23 | * @param authorizationHeader The authorization header string. 24 | * @return {@code true} if the header is a bearer token; otherwise {@code false}. 25 | */ 26 | public static boolean isBearerToken(final String authorizationHeader) { 27 | return StringUtils.hasText(authorizationHeader) && 28 | authorizationHeader.startsWith(TOKEN_PREFIX); 29 | } 30 | 31 | /** 32 | * Extracts the JWT token from the authorization header. 33 | * 34 | * @param authorizationHeader The authorization header string. 35 | * @return The JWT token string extracted from the header. 36 | */ 37 | public static String getJwt(final String authorizationHeader) { 38 | return authorizationHeader.replace(TOKEN_PREFIX, ""); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.config; 2 | 3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 4 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; 5 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 6 | import io.swagger.v3.oas.annotations.info.Contact; 7 | import io.swagger.v3.oas.annotations.info.Info; 8 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 9 | 10 | /** 11 | * Configuration class named {@link OpenApiConfig} for OpenAPI documentation. 12 | */ 13 | @OpenAPIDefinition( 14 | info = @Info( 15 | contact = @Contact( 16 | name = "Sercan Noyan Germiyanoğlu", 17 | url = "https://github.com/Rapter1990/parkinglot/" 18 | ), 19 | description = "Case Study - Role Permission Through Spring Security in Spring Boot" + 20 | "(Spring Boot, Spring Security , Mysql, JUnit, Integration Test, Docker, Test Container, Github Actions, Postman) ", 21 | title = "rolepermissionexample", 22 | version = "1.0.0" 23 | ) 24 | ) 25 | @SecurityScheme( 26 | name = "bearerAuth", 27 | description = "JWT Token", 28 | scheme = "bearer", 29 | type = SecuritySchemeType.HTTP, 30 | bearerFormat = "JWT", 31 | in = SecuritySchemeIn.HEADER 32 | ) 33 | public class OpenApiConfig { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/impl/ProductDeleteServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.product.exception.ProductNotFoundException; 4 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 5 | import com.security.rolepermissionexample.product.repository.ProductRepository; 6 | import com.security.rolepermissionexample.product.service.ProductDeleteService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * Service implementation named {@link ProductDeleteServiceImpl} for deleting products. 12 | */ 13 | @Service 14 | @RequiredArgsConstructor 15 | public class ProductDeleteServiceImpl implements ProductDeleteService { 16 | 17 | private final ProductRepository productRepository; 18 | 19 | /** 20 | * Deletes a product identified by its unique ID. 21 | * 22 | * @param productId The ID of the product to delete. 23 | * @throws ProductNotFoundException If no product with the given ID exists. 24 | */ 25 | @Override 26 | public void deleteProductById(String productId) { 27 | 28 | final ProductEntity productEntityToBeDelete = productRepository 29 | .findById(productId) 30 | .orElseThrow(() -> new ProductNotFoundException("With given productID = " + productId)); 31 | 32 | productRepository.delete(productEntityToBeDelete); 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/dto/request/RegisterRequest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.dto.request; 2 | 3 | import com.security.rolepermissionexample.auth.model.Permission; 4 | import jakarta.validation.constraints.Email; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.NotEmpty; 7 | import jakarta.validation.constraints.Size; 8 | import lombok.*; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Represents a register request as {@link RegisterRequest} with user details 14 | * including email, password, first name, last name, 15 | * phone number, roles, and permissions. 16 | */ 17 | @Getter 18 | @Setter 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | @Builder 22 | public class RegisterRequest { 23 | 24 | @Email(message = "Please enter valid e-mail address") 25 | @Size(min = 7, message = "Minimum e-mail length is 7 characters.") 26 | private String email; 27 | 28 | @Size(min = 8) 29 | private String password; 30 | 31 | @NotBlank(message = "First name can't be blank.") 32 | private String firstName; 33 | 34 | @NotBlank(message = "Last name can't be blank.") 35 | private String lastName; 36 | 37 | @NotBlank(message = "Phone number can't be blank.") 38 | @Size(min = 11, max = 20) 39 | private String phoneNumber; 40 | 41 | @NotEmpty(message = "Role name can't be empty.") 42 | private List role; 43 | 44 | @NotEmpty(message = "Permission can't be empty.") 45 | private List permissions; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/entity/ProductEntity.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.entity; 2 | 3 | import com.security.rolepermissionexample.common.model.entity.BaseEntity; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.Table; 10 | import lombok.AllArgsConstructor; 11 | import lombok.EqualsAndHashCode; 12 | import lombok.Getter; 13 | import lombok.NoArgsConstructor; 14 | import lombok.Setter; 15 | import lombok.experimental.SuperBuilder; 16 | 17 | import java.math.BigDecimal; 18 | 19 | /** 20 | * Represents a persistent entity for a product as {@link ProductEntity}. 21 | */ 22 | @Getter 23 | @Setter 24 | @SuperBuilder 25 | @EqualsAndHashCode(callSuper = true) 26 | @NoArgsConstructor 27 | @AllArgsConstructor 28 | @Entity 29 | @Table(name = "PRODUCT") 30 | public class ProductEntity extends BaseEntity { 31 | 32 | @Id 33 | @GeneratedValue(strategy = GenerationType.UUID) 34 | @Column(name = "ID") 35 | private String id; 36 | 37 | @Column(name = "NAME") 38 | private String name; 39 | 40 | @Column( 41 | name = "AMOUNT", 42 | precision = 24, 43 | scale = 4 44 | ) 45 | private BigDecimal amount; 46 | 47 | @Column( 48 | name = "UNIT_PRICE", 49 | precision = 24, 50 | scale = 4 51 | ) 52 | private BigDecimal unitPrice; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/CustomPage.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model; 2 | 3 | import lombok.*; 4 | import org.springframework.data.domain.Page; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Represents a paginated response named {@link CustomPage} containing content and pagination details. 10 | * 11 | * @param the type of content in the page 12 | */ 13 | @Getter 14 | @Builder 15 | @AllArgsConstructor 16 | public class CustomPage { 17 | private List content; 18 | 19 | private Integer pageNumber; 20 | 21 | private Integer pageSize; 22 | 23 | private Long totalElementCount; 24 | 25 | private Integer totalPageCount; 26 | 27 | /** 28 | * Constructs a CustomPage instance from a domain model page. 29 | * 30 | * @param domainModels the list of domain models in the page 31 | * @param page the Page object containing pagination information 32 | * @param the type of domain model in the page 33 | * @param the type of page 34 | * @return a CustomPage instance mapped from the provided Page 35 | */ 36 | public static CustomPage of(final List domainModels, final Page page) { 37 | return CustomPage.builder() 38 | .content(domainModels) 39 | .pageNumber(page.getNumber() + 1) 40 | .pageSize(page.getSize()) 41 | .totalPageCount(page.getTotalPages()) 42 | .totalElementCount(page.getTotalElements()) 43 | .build(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/model/dto/response/ProductResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.dto.response; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class ProductResponseTest { 10 | 11 | @Test 12 | void testNoArgsConstructor() { 13 | // When 14 | ProductResponse productResponse = new ProductResponse(); 15 | 16 | // Then 17 | assertNotNull(productResponse); 18 | assertNull(productResponse.getId()); 19 | assertNull(productResponse.getName()); 20 | assertNull(productResponse.getAmount()); 21 | assertNull(productResponse.getUnitPrice()); 22 | } 23 | 24 | @Test 25 | void testSetters() { 26 | 27 | // Given 28 | String id = "test-id"; 29 | String name = "test-name"; 30 | BigDecimal amount = new BigDecimal("10.00"); 31 | BigDecimal unitPrice = new BigDecimal("5.00"); 32 | 33 | // When 34 | ProductResponse productResponse = new ProductResponse(); 35 | productResponse.setId(id); 36 | productResponse.setName(name); 37 | productResponse.setAmount(amount); 38 | productResponse.setUnitPrice(unitPrice); 39 | 40 | // Then 41 | assertEquals(id, productResponse.getId()); 42 | assertEquals(name, productResponse.getName()); 43 | assertEquals(amount, productResponse.getAmount()); 44 | assertEquals(unitPrice, productResponse.getUnitPrice()); 45 | 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD Pipeline 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | test: 11 | name: Unit Test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v3 16 | 17 | - name: Set up JDK 21 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: '21' 21 | distribution: 'temurin' 22 | cache: 'maven' 23 | 24 | - name: Cache Maven packages 25 | uses: actions/cache@v3 26 | with: 27 | path: ~/.m2 28 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 29 | restore-keys: ${{ runner.os }}-m2 30 | 31 | - name: Build with Maven 32 | run: mvn -B package --file pom.xml 33 | 34 | - name: Run Tests 35 | run: mvn -B test 36 | 37 | build-and-push: 38 | name: Build and Push 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: Checkout code 42 | uses: actions/checkout@v3 43 | 44 | - name: Set up JDK 21 45 | uses: actions/setup-java@v3 46 | with: 47 | java-version: '21' 48 | distribution: 'temurin' 49 | cache: 'maven' 50 | 51 | - name: Build with Maven 52 | run: mvn -B package --file pom.xml 53 | 54 | - name: Dockerize & Push Docker Image 55 | uses: mr-smithers-excellent/docker-build-push@v6 56 | with: 57 | image: noyandocker/rolepermissionexample 58 | tags: latest 59 | registry: docker.io 60 | dockerfile: Dockerfile 61 | username: ${{ secrets.DOCKER_USERNAME }} 62 | password: ${{ secrets.DOCKER_PASSWORD }} -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/utils/KeyConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.utils; 2 | 3 | import com.security.rolepermissionexample.common.util.ListUtil; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | import java.lang.reflect.InvocationTargetException; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class KeyConverterTest { 12 | 13 | @Test 14 | void utilityClass_ShouldNotBeInstantiated() { 15 | assertThrows(InvocationTargetException.class, () -> { 16 | // Attempt to use reflection to create an instance of the utility class 17 | java.lang.reflect.Constructor constructor = KeyConverter.class.getDeclaredConstructor(); 18 | constructor.setAccessible(true); 19 | constructor.newInstance(); 20 | }); 21 | } 22 | 23 | @Test 24 | void testConvertPublicKeyRuntimeException() { 25 | String invalidPublicKey = "InvalidPublicKey"; 26 | 27 | RuntimeException exception = assertThrows(RuntimeException.class, () -> { 28 | KeyConverter.convertPublicKey(invalidPublicKey); 29 | }); 30 | 31 | assertNotNull(exception); 32 | assertTrue(exception.getCause() instanceof IOException); 33 | } 34 | 35 | @Test 36 | void testConvertPrivateKeyRuntimeException() { 37 | String invalidPrivateKey = "InvalidPrivateKey"; 38 | 39 | RuntimeException exception = assertThrows(RuntimeException.class, () -> { 40 | KeyConverter.convertPrivateKey(invalidPrivateKey); 41 | }); 42 | 43 | assertNotNull(exception); 44 | assertTrue(exception.getCause() instanceof IOException); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/dto/response/CustomResponse.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model.dto.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import org.springframework.http.HttpStatus; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | /** 11 | * Represents a generic response object named {@link CustomResponse} with standardized fields. 12 | * 13 | * @param Type of the response payload. 14 | */ 15 | @Getter 16 | @Builder 17 | public class CustomResponse { 18 | 19 | @Builder.Default 20 | private LocalDateTime time = LocalDateTime.now(); 21 | 22 | private HttpStatus httpStatus; 23 | 24 | private Boolean isSuccess; 25 | 26 | @JsonInclude(JsonInclude.Include.NON_NULL) 27 | private T response; 28 | 29 | /** 30 | * Default successful response with HTTP OK status and success indicator set to true. 31 | */ 32 | public static final CustomResponse SUCCESS = CustomResponse.builder() 33 | .httpStatus(HttpStatus.OK) 34 | .isSuccess(true) 35 | .build(); 36 | 37 | /** 38 | * Creates a successful response with the provided payload and HTTP OK status. 39 | * 40 | * @param Type of the response payload. 41 | * @param response Response payload. 42 | * @return CustomResponse instance with success status, HTTP OK, and the provided payload. 43 | */ 44 | public static CustomResponse successOf(final T response) { 45 | return CustomResponse.builder() 46 | .httpStatus(HttpStatus.OK) 47 | .isSuccess(true) 48 | .response(response) 49 | .build(); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/mapper/ListProductEntityToListProductMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.mapper; 2 | 3 | import com.security.rolepermissionexample.product.model.Product; 4 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 5 | import org.mapstruct.Mapper; 6 | import org.mapstruct.factory.Mappers; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * Mapper interface named {@link ListProductEntityToListProductMapper} for converting {@link List} to {@link List}. 13 | */ 14 | @Mapper 15 | public interface ListProductEntityToListProductMapper { 16 | 17 | ProductEntityToProductMapper productEntityToProductMapper = Mappers.getMapper(ProductEntityToProductMapper.class); 18 | 19 | /** 20 | * Converts a list of ProductEntity objects to a list of Product objects. 21 | * 22 | * @param productEntities The list of ProductEntity objects to convert. 23 | * @return List of Product objects containing mapped data. 24 | */ 25 | default List toProductList(List productEntities) { 26 | 27 | if (productEntities == null) { 28 | return null; 29 | } 30 | 31 | return productEntities.stream() 32 | .map(productEntityToProductMapper::map) 33 | .collect(Collectors.toList()); 34 | 35 | } 36 | 37 | /** 38 | * Initializes and returns an instance of ListProductEntityToListProductMapper. 39 | * 40 | * @return Initialized ListProductEntityToListProductMapper instance. 41 | */ 42 | static ListProductEntityToListProductMapper initialize() { 43 | return Mappers.getMapper(ListProductEntityToListProductMapper.class); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/common/model/entity/BaseEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model.entity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class BaseEntityTest { 10 | 11 | @Test 12 | void testGettersAndSetters() { 13 | 14 | BaseEntity entity = new BaseEntity(); 15 | 16 | // Test setters 17 | entity.setCreatedBy("TestUser"); 18 | entity.setUpdatedBy("AnotherUser"); 19 | 20 | // Test getters 21 | assertEquals("TestUser", entity.getCreatedBy()); 22 | assertEquals("AnotherUser", entity.getUpdatedBy()); 23 | 24 | } 25 | 26 | @Test 27 | void testSuperBuilder() { 28 | 29 | BaseEntity entity = BaseEntity.builder() 30 | .createdAt(LocalDateTime.now()) 31 | .createdBy("TestUser") 32 | .updatedAt(LocalDateTime.now()) 33 | .updatedBy("AnotherUser") 34 | .build(); 35 | 36 | assertNotNull(entity); 37 | assertEquals("TestUser", entity.getCreatedBy()); 38 | assertEquals("AnotherUser", entity.getUpdatedBy()); 39 | 40 | } 41 | 42 | @Test 43 | void testAllArgsConstructor() { 44 | LocalDateTime createdAt = LocalDateTime.of(2023, 1, 1, 10, 0); 45 | LocalDateTime updatedAt = LocalDateTime.of(2023, 1, 2, 12, 0); 46 | BaseEntity entity = new BaseEntity(createdAt, "TestUser", updatedAt, "AnotherUser"); 47 | 48 | assertNotNull(entity); 49 | assertEquals("TestUser", entity.getCreatedBy()); 50 | assertEquals("AnotherUser", entity.getUpdatedBy()); 51 | assertEquals(createdAt, entity.getCreatedAt()); 52 | assertEquals(updatedAt, entity.getUpdatedAt()); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/model/entity/ProductEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.entity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class ProductEntityTest { 10 | 11 | @Test 12 | void testAllArgsConstructor() { 13 | // Given 14 | String id = "test-id"; 15 | String name = "test-name"; 16 | BigDecimal amount = new BigDecimal("10.00"); 17 | BigDecimal unitPrice = new BigDecimal("5.00"); 18 | 19 | // When 20 | ProductEntity productEntity = new ProductEntity(id, name, amount, unitPrice); 21 | 22 | // Then 23 | assertNotNull(productEntity); 24 | assertEquals(id, productEntity.getId()); 25 | assertEquals(name, productEntity.getName()); 26 | assertEquals(amount, productEntity.getAmount()); 27 | assertEquals(unitPrice, productEntity.getUnitPrice()); 28 | } 29 | 30 | @Test 31 | void testEqualsAndHashCode() { 32 | 33 | // Given 34 | ProductEntity productEntity1 = ProductEntity.builder() 35 | .id("test-id-1") 36 | .name("test-name-1") 37 | .amount(new BigDecimal("10.00")) 38 | .unitPrice(new BigDecimal("5.00")) 39 | .build(); 40 | 41 | ProductEntity productEntity3 = ProductEntity.builder() 42 | .id("test-id-2") 43 | .name("test-name-2") 44 | .amount(new BigDecimal("20.00")) 45 | .unitPrice(new BigDecimal("10.00")) 46 | .build(); 47 | 48 | // Then 49 | assertNotEquals(productEntity1, productEntity3); 50 | assertNotEquals(productEntity1.hashCode(), productEntity3.hashCode()); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/mapper/ProductCreateRequestToProductEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.mapper; 2 | 3 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 4 | import com.security.rolepermissionexample.product.model.dto.request.ProductCreateRequest; 5 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Named; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | /** 11 | * Mapper interface named {@link ProductCreateRequestToProductEntityMapper} for converting {@link ProductCreateRequest} to {@link ProductEntity}. 12 | */ 13 | @Mapper 14 | public interface ProductCreateRequestToProductEntityMapper extends BaseMapper { 15 | 16 | /** 17 | * Maps ProductCreateRequest to ProductEntity for saving purposes. 18 | * 19 | * @param productCreateRequest The ProductCreateRequest object to map. 20 | * @return ProductEntity object containing mapped data. 21 | */ 22 | @Named("mapForSaving") 23 | default ProductEntity mapForSaving(ProductCreateRequest productCreateRequest) { 24 | return ProductEntity.builder() 25 | .amount(productCreateRequest.getAmount()) 26 | .name(productCreateRequest.getName()) 27 | .unitPrice(productCreateRequest.getUnitPrice()) 28 | .build(); 29 | } 30 | 31 | /** 32 | * Initializes and returns an instance of ProductCreateRequestToProductEntityMapper. 33 | * 34 | * @return Initialized ProductCreateRequestToProductEntityMapper instance. 35 | */ 36 | static ProductCreateRequestToProductEntityMapper initialize() { 37 | return Mappers.getMapper(ProductCreateRequestToProductEntityMapper.class); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/dto/response/CustomPagingResponse.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model.dto.response; 2 | 3 | import com.security.rolepermissionexample.common.model.CustomPage; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Represents a response object for custom paging named {@link CustomPagingResponse}. 11 | * Contains content list, page number, page size, total element count, and total page count. 12 | * 13 | * @param Type of content in the response. 14 | */ 15 | @Getter 16 | @Builder 17 | public class CustomPagingResponse { 18 | 19 | private List content; 20 | 21 | private Integer pageNumber; 22 | 23 | private Integer pageSize; 24 | 25 | private Long totalElementCount; 26 | 27 | private Integer totalPageCount; 28 | 29 | /** 30 | * Builder class for CustomPagingResponse, allowing initialization from a CustomPage object. 31 | * 32 | * @param Type of content in the response. 33 | */ 34 | public static class CustomPagingResponseBuilder { 35 | 36 | /** 37 | * Initializes the builder with data from a CustomPage object. 38 | * 39 | * @param customPage CustomPage object containing pagination data. 40 | * @return CustomPagingResponseBuilder initialized with pagination details. 41 | */ 42 | public CustomPagingResponseBuilder of(final CustomPage customPage) { 43 | return CustomPagingResponse.builder() 44 | .pageNumber(customPage.getPageNumber()) 45 | .pageSize(customPage.getPageSize()) 46 | .totalElementCount(customPage.getTotalElementCount()) 47 | .totalPageCount(customPage.getTotalPageCount()); 48 | } 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/config/TokenConfigurationParameter.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.config; 2 | 3 | import com.security.rolepermissionexample.auth.model.enums.ConfigurationParameter; 4 | import com.security.rolepermissionexample.auth.utils.KeyConverter; 5 | import lombok.Getter; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import java.security.PrivateKey; 9 | import java.security.PublicKey; 10 | 11 | /** 12 | * Configuration class named {@link TokenConfigurationParameter} for token parameters used in authentication and authorization. 13 | */ 14 | @Getter 15 | @Configuration 16 | public class TokenConfigurationParameter { 17 | private final String issuer; 18 | private final int accessTokenExpireMinute; 19 | private final int refreshTokenExpireDay; 20 | private final PublicKey publicKey; 21 | private final PrivateKey privateKey; 22 | 23 | /** 24 | * Constructs a new {@link TokenConfigurationParameter} instance with default values from {@link ConfigurationParameter}. 25 | */ 26 | public TokenConfigurationParameter() { 27 | 28 | this.issuer = ConfigurationParameter.ISSUER.getDefaultValue(); 29 | 30 | this.accessTokenExpireMinute = Integer.parseInt( 31 | ConfigurationParameter.AUTH_ACCESS_TOKEN_EXPIRE_MINUTE.getDefaultValue() 32 | ); 33 | 34 | this.refreshTokenExpireDay = Integer.parseInt( 35 | ConfigurationParameter.AUTH_REFRESH_TOKEN_EXPIRE_DAY.getDefaultValue() 36 | ); 37 | 38 | this.publicKey = KeyConverter.convertPublicKey( 39 | ConfigurationParameter.AUTH_PUBLIC_KEY.getDefaultValue() 40 | ); 41 | 42 | this.privateKey = KeyConverter.convertPrivateKey( 43 | ConfigurationParameter.AUTH_PRIVATE_KEY.getDefaultValue() 44 | ); 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/mapper/RegisterRequestToUserEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.mapper; 2 | 3 | import com.security.rolepermissionexample.auth.model.dto.request.RegisterRequest; 4 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 5 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Named; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | /** 11 | * Mapper interface named {@link RegisterRequestToUserEntityMapper} for mapping {@link RegisterRequest} to {@link UserEntity}. 12 | */ 13 | @Mapper 14 | public interface RegisterRequestToUserEntityMapper extends BaseMapper { 15 | 16 | /** 17 | * Maps a {@link RegisterRequest} instance to a {@link UserEntity} instance for saving purposes. 18 | * 19 | * @param registerRequest The {@link RegisterRequest} object to map. 20 | * @return A {@link UserEntity} object mapped from the {@link RegisterRequest}. 21 | */ 22 | @Named("mapForSaving") 23 | default UserEntity mapForSaving(RegisterRequest registerRequest) { 24 | return UserEntity.builder() 25 | .email(registerRequest.getEmail()) 26 | .firstName(registerRequest.getFirstName()) 27 | .lastName(registerRequest.getLastName()) 28 | .phoneNumber(registerRequest.getPhoneNumber()) 29 | .build(); 30 | } 31 | 32 | /** 33 | * Initializes and returns an instance of {@code RegisterRequestToUserEntityMapper}. 34 | * 35 | * @return An initialized {@code RegisterRequestToUserEntityMapper} instance. 36 | */ 37 | static RegisterRequestToUserEntityMapper initialize() { 38 | return Mappers.getMapper(RegisterRequestToUserEntityMapper.class); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/mapper/ProductUpdateRequestToProductEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.mapper; 2 | 3 | import com.security.rolepermissionexample.common.model.mapper.BaseMapper; 4 | import com.security.rolepermissionexample.product.model.dto.request.ProductUpdateRequest; 5 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Named; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | /** 11 | * Mapper interface named {@link ProductUpdateRequestToProductEntityMapper} for updating {@link ProductEntity} using {@link ProductUpdateRequest}. 12 | */ 13 | @Mapper 14 | public interface ProductUpdateRequestToProductEntityMapper extends BaseMapper { 15 | 16 | /** 17 | * Maps fields from ProductUpdateRequest to update ProductEntity. 18 | * 19 | * @param productEntityToBeUpdate The ProductEntity object to be updated. 20 | * @param productUpdateRequest The ProductUpdateRequest object containing updated fields. 21 | */ 22 | @Named("mapForUpdating") 23 | default void mapForUpdating(ProductEntity productEntityToBeUpdate, ProductUpdateRequest productUpdateRequest) { 24 | productEntityToBeUpdate.setName(productUpdateRequest.getName()); 25 | productEntityToBeUpdate.setAmount(productUpdateRequest.getAmount()); 26 | productEntityToBeUpdate.setUnitPrice(productUpdateRequest.getUnitPrice()); 27 | } 28 | 29 | /** 30 | * Initializes and returns an instance of ProductUpdateRequestToProductEntityMapper. 31 | * 32 | * @return Initialized ProductUpdateRequestToProductEntityMapper instance. 33 | */ 34 | static ProductUpdateRequestToProductEntityMapper initialize() { 35 | return Mappers.getMapper(ProductUpdateRequestToProductEntityMapper.class); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/entity/InvalidTokenEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class InvalidTokenEntityTest { 10 | 11 | @Test 12 | void testSetters() { 13 | 14 | // Given 15 | InvalidTokenEntity invalidTokenEntity = new InvalidTokenEntity(); 16 | String id = "test-id"; 17 | String tokenId = "test-token-id"; 18 | LocalDateTime createdAt = LocalDateTime.now(); 19 | String createdBy = "test-created-by"; 20 | LocalDateTime updatedAt = LocalDateTime.now(); 21 | String updatedBy = "test-updated-by"; 22 | 23 | // When 24 | invalidTokenEntity.setId(id); 25 | invalidTokenEntity.setTokenId(tokenId); 26 | invalidTokenEntity.setCreatedAt(createdAt); 27 | invalidTokenEntity.setCreatedBy(createdBy); 28 | invalidTokenEntity.setUpdatedAt(updatedAt); 29 | invalidTokenEntity.setUpdatedBy(updatedBy); 30 | 31 | // Then 32 | assertEquals(id, invalidTokenEntity.getId()); 33 | assertEquals(tokenId, invalidTokenEntity.getTokenId()); 34 | assertEquals(createdAt, invalidTokenEntity.getCreatedAt()); 35 | assertEquals(createdBy, invalidTokenEntity.getCreatedBy()); 36 | assertEquals(updatedAt, invalidTokenEntity.getUpdatedAt()); 37 | assertEquals(updatedBy, invalidTokenEntity.getUpdatedBy()); 38 | 39 | } 40 | 41 | @Test 42 | void testAllArgsConstructor() { 43 | // Given 44 | String id = "test-id"; 45 | String tokenId = "test-token-id"; 46 | 47 | // When 48 | InvalidTokenEntity invalidTokenEntity = new InvalidTokenEntity(id,tokenId); 49 | 50 | // Then 51 | assertEquals(id, invalidTokenEntity.getId()); 52 | assertEquals(tokenId, invalidTokenEntity.getTokenId()); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/CustomError.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.http.HttpStatus; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | /** 13 | * Represents a custom error response named {@link CustomError} structure for REST APIs. 14 | */ 15 | @Getter 16 | @Builder 17 | public class CustomError { 18 | 19 | @Builder.Default 20 | private LocalDateTime time = LocalDateTime.now(); 21 | 22 | private HttpStatus httpStatus; 23 | 24 | private String header; 25 | 26 | @JsonInclude(JsonInclude.Include.NON_NULL) 27 | private String message; 28 | 29 | @Builder.Default 30 | private final Boolean isSuccess = false; 31 | 32 | @JsonInclude(JsonInclude.Include.NON_NULL) 33 | private List subErrors; 34 | 35 | /** 36 | * Represents a sub-error with specific details as {@link CustomSubError}. 37 | */ 38 | @Getter 39 | @Builder 40 | public static class CustomSubError { 41 | 42 | private String message; 43 | 44 | private String field; 45 | 46 | @JsonInclude(JsonInclude.Include.NON_NULL) 47 | private Object value; 48 | 49 | @JsonInclude(JsonInclude.Include.NON_NULL) 50 | private String type; 51 | 52 | } 53 | 54 | /** 55 | * Enumerates common error headers for categorizing errors as {@link Header}. 56 | */ 57 | @Getter 58 | @RequiredArgsConstructor 59 | public enum Header { 60 | 61 | API_ERROR("API ERROR"), 62 | 63 | ALREADY_EXIST("ALREADY EXIST"), 64 | 65 | NOT_FOUND("NOT EXIST"), 66 | 67 | VALIDATION_ERROR("VALIDATION ERROR"), 68 | 69 | DATABASE_ERROR("DATABASE ERROR"), 70 | 71 | PROCESS_ERROR("PROCESS ERROR"), 72 | 73 | AUTH_ERROR("AUTH ERROR"); 74 | 75 | 76 | private final String name; 77 | 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | -- Insert roles 2 | INSERT INTO ROLES (ID, NAME, CREATED_AT, CREATED_BY, UPDATED_AT, UPDATED_BY) 3 | VALUES (UUID(), 'ADMIN', NOW(), 'system', NOW(), 'system'); 4 | 5 | INSERT INTO ROLES (ID, NAME, CREATED_AT, CREATED_BY, UPDATED_AT, UPDATED_BY) 6 | VALUES (UUID(), 'USER', NOW(), 'system', NOW(), 'system'); 7 | 8 | -- Insert permissions 9 | INSERT INTO PERMISSIONS (ID, NAME, CREATED_AT, CREATED_BY, UPDATED_AT, UPDATED_BY) 10 | VALUES (UUID(), 'admin:create', NOW(), 'system', NOW(), 'system'); 11 | 12 | INSERT INTO PERMISSIONS (ID, NAME, CREATED_AT, CREATED_BY, UPDATED_AT, UPDATED_BY) 13 | VALUES (UUID(), 'admin:get', NOW(), 'system', NOW(), 'system'); 14 | 15 | INSERT INTO PERMISSIONS (ID, NAME, CREATED_AT, CREATED_BY, UPDATED_AT, UPDATED_BY) 16 | VALUES (UUID(), 'admin:update', NOW(), 'system', NOW(), 'system'); 17 | 18 | INSERT INTO PERMISSIONS (ID, NAME, CREATED_AT, CREATED_BY, UPDATED_AT, UPDATED_BY) 19 | VALUES (UUID(), 'admin:delete', NOW(), 'system', NOW(), 'system'); 20 | 21 | INSERT INTO PERMISSIONS (ID, NAME, CREATED_AT, CREATED_BY, UPDATED_AT, UPDATED_BY) 22 | VALUES (UUID(), 'user:get', NOW(), 'system', NOW(), 'system'); 23 | 24 | -- Insert role-permission relations 25 | INSERT INTO ROLE_PERMISSION_RELATION_TABLE (ROLE_ID, PERMISSION_ID) 26 | SELECT r.ID, p.ID FROM ROLES r, PERMISSIONS p 27 | WHERE r.NAME = 'ADMIN' AND p.NAME = 'admin:create'; 28 | 29 | INSERT INTO ROLE_PERMISSION_RELATION_TABLE (ROLE_ID, PERMISSION_ID) 30 | SELECT r.ID, p.ID FROM ROLES r, PERMISSIONS p 31 | WHERE r.NAME = 'ADMIN' AND p.NAME = 'admin:get'; 32 | 33 | INSERT INTO ROLE_PERMISSION_RELATION_TABLE (ROLE_ID, PERMISSION_ID) 34 | SELECT r.ID, p.ID FROM ROLES r, PERMISSIONS p 35 | WHERE r.NAME = 'ADMIN' AND p.NAME = 'admin:update'; 36 | 37 | INSERT INTO ROLE_PERMISSION_RELATION_TABLE (ROLE_ID, PERMISSION_ID) 38 | SELECT r.ID, p.ID FROM ROLES r, PERMISSIONS p 39 | WHERE r.NAME = 'ADMIN' AND p.NAME = 'admin:delete'; 40 | 41 | INSERT INTO ROLE_PERMISSION_RELATION_TABLE (ROLE_ID, PERMISSION_ID) 42 | SELECT r.ID, p.ID FROM ROLES r, PERMISSIONS p 43 | WHERE r.NAME = 'USER' AND p.NAME = 'user:get'; 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/impl/LogoutServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.model.dto.request.TokenInvalidateRequest; 4 | import com.security.rolepermissionexample.auth.service.InvalidTokenService; 5 | import com.security.rolepermissionexample.auth.service.LogoutService; 6 | import com.security.rolepermissionexample.auth.service.TokenService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Set; 11 | 12 | /** 13 | * Service implementation named {@link LogoutServiceImpl} for handling user logout operations. 14 | */ 15 | @Service 16 | @RequiredArgsConstructor 17 | public class LogoutServiceImpl implements LogoutService { 18 | 19 | private final TokenService tokenService; 20 | private final InvalidTokenService invalidTokenService; 21 | 22 | /** 23 | * Logs out a user session based on the provided token invalidation request. 24 | * 25 | * @param tokenInvalidateRequest The request containing tokens to invalidate for logout. 26 | */ 27 | @Override 28 | public void logout(TokenInvalidateRequest tokenInvalidateRequest) { 29 | 30 | tokenService.verifyAndValidate( 31 | Set.of( 32 | tokenInvalidateRequest.getAccessToken(), 33 | tokenInvalidateRequest.getRefreshToken() 34 | ) 35 | ); 36 | 37 | final String accessTokenId = tokenService 38 | .getPayload(tokenInvalidateRequest.getAccessToken()) 39 | .getId(); 40 | 41 | invalidTokenService.checkForInvalidityOfToken(accessTokenId); 42 | 43 | 44 | final String refreshTokenId = tokenService 45 | .getPayload(tokenInvalidateRequest.getRefreshToken()) 46 | .getId(); 47 | 48 | invalidTokenService.checkForInvalidityOfToken(refreshTokenId); 49 | 50 | invalidTokenService.invalidateTokens(Set.of(accessTokenId,refreshTokenId)); 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/entity/UserEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import com.security.rolepermissionexample.auth.model.enums.UserStatus; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | class UserEntityTest { 11 | 12 | @Test 13 | void testAllArgsConstructor() { 14 | // Given 15 | String id = "test-id"; 16 | String email = "test@example.com"; 17 | String password = "password"; 18 | String firstName = "John"; 19 | String lastName = "Doe"; 20 | String phoneNumber = "1234567890"; 21 | UserStatus userStatus = UserStatus.ACTIVE; 22 | List roles = List.of(new RoleEntity(), new RoleEntity()); 23 | 24 | // When 25 | UserEntity userEntity = new UserEntity(id, email, password, firstName, lastName, phoneNumber, userStatus, roles); 26 | 27 | // Then 28 | assertNotNull(userEntity); 29 | assertEquals(id, userEntity.getId()); 30 | assertEquals(email, userEntity.getEmail()); 31 | assertEquals(password, userEntity.getPassword()); 32 | assertEquals(firstName, userEntity.getFirstName()); 33 | assertEquals(lastName, userEntity.getLastName()); 34 | assertEquals(phoneNumber, userEntity.getPhoneNumber()); 35 | assertEquals(userStatus, userEntity.getUserStatus()); 36 | assertEquals(roles, userEntity.getRoles()); 37 | 38 | } 39 | 40 | @Test 41 | void testEqualsAndHashCode() { 42 | 43 | // Given 44 | String id1 = "test-id-1"; 45 | String id2 = "test-id-2"; 46 | UserEntity user1 = new UserEntity(); 47 | user1.setId(id1); 48 | 49 | UserEntity user2 = new UserEntity(); 50 | user2.setId(id1); 51 | 52 | UserEntity user3 = new UserEntity(); 53 | user3.setId(id2); 54 | 55 | // Then 56 | assertNotEquals(user1, user3); 57 | assertNotEquals(user1.hashCode(), user3.hashCode()); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/impl/InvalidTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.exception.TokenAlreadyInvalidatedException; 4 | import com.security.rolepermissionexample.auth.model.entity.InvalidTokenEntity; 5 | import com.security.rolepermissionexample.auth.repository.InvalidTokenRepository; 6 | import com.security.rolepermissionexample.auth.service.InvalidTokenService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Set; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * Service implementation named {@link InvalidTokenServiceImpl} for managing invalid tokens. 15 | */ 16 | @Service 17 | @RequiredArgsConstructor 18 | public class InvalidTokenServiceImpl implements InvalidTokenService { 19 | 20 | private final InvalidTokenRepository invalidTokenRepository; 21 | 22 | /** 23 | * Invalidates multiple tokens identified by their IDs. 24 | * 25 | * @param tokenIds The set of token IDs to invalidate. 26 | */ 27 | @Override 28 | public void invalidateTokens(Set tokenIds) { 29 | 30 | final Set invalidTokenEntities = tokenIds.stream() 31 | .map(tokenId -> InvalidTokenEntity.builder() 32 | .tokenId(tokenId) 33 | .build() 34 | ) 35 | .collect(Collectors.toSet()); 36 | 37 | invalidTokenRepository.saveAll(invalidTokenEntities); 38 | } 39 | 40 | /** 41 | * Checks if a token identified by its ID is invalid. 42 | * 43 | * @param tokenId The ID of the token to check for invalidity. 44 | * @throws TokenAlreadyInvalidatedException If the token is already invalidated. 45 | */ 46 | @Override 47 | public void checkForInvalidityOfToken(String tokenId) { 48 | 49 | final boolean isTokenInvalid = invalidTokenRepository.findByTokenId(tokenId).isPresent(); 50 | 51 | if (isTokenInvalid) { 52 | throw new TokenAlreadyInvalidatedException(tokenId); 53 | } 54 | 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/impl/LoginServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.exception.PasswordNotValidException; 4 | import com.security.rolepermissionexample.auth.exception.UserNotFoundException; 5 | import com.security.rolepermissionexample.auth.model.Token; 6 | import com.security.rolepermissionexample.auth.model.dto.request.LoginRequest; 7 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 8 | import com.security.rolepermissionexample.auth.repository.UserRepository; 9 | import com.security.rolepermissionexample.auth.service.LoginService; 10 | import com.security.rolepermissionexample.auth.service.TokenService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.stereotype.Service; 14 | 15 | /** 16 | * Service implementation named {@link LoginServiceImpl} for handling user login operations. 17 | */ 18 | @Service 19 | @RequiredArgsConstructor 20 | public class LoginServiceImpl implements LoginService { 21 | 22 | private final UserRepository userRepository; 23 | private final PasswordEncoder passwordEncoder; 24 | private final TokenService tokenService; 25 | 26 | /** 27 | * Performs user login based on the provided login request. 28 | * 29 | * @param loginRequest The login request containing user credentials. 30 | * @return The token representing the user's session. 31 | */ 32 | @Override 33 | public Token login(LoginRequest loginRequest) { 34 | 35 | final UserEntity userEntityFromDB = userRepository 36 | .findUserEntityByEmail(loginRequest.getEmail()) 37 | .orElseThrow( 38 | () -> new UserNotFoundException(loginRequest.getEmail()) 39 | ); 40 | 41 | if (Boolean.FALSE.equals(passwordEncoder.matches( 42 | loginRequest.getPassword(), userEntityFromDB.getPassword()))) { 43 | throw new PasswordNotValidException(); 44 | } 45 | 46 | return tokenService.generateToken(userEntityFromDB.getClaims()); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/service/impl/ProductDeleteServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 4 | import com.security.rolepermissionexample.product.exception.ProductNotFoundException; 5 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 6 | import com.security.rolepermissionexample.product.repository.ProductRepository; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | 11 | import java.util.Optional; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | class ProductDeleteServiceImplTest extends AbstractBaseServiceTest { 17 | 18 | @InjectMocks 19 | private ProductDeleteServiceImpl productDeleteService; 20 | 21 | @Mock 22 | private ProductRepository productRepository; 23 | 24 | 25 | @Test 26 | void givenProductId_whenDeleteProduct_thenReturnProductDeleted() { 27 | 28 | // Given 29 | String productId = "1"; 30 | ProductEntity existingProductEntity = new ProductEntity(); 31 | existingProductEntity.setId(productId); 32 | 33 | when(productRepository.findById(productId)).thenReturn(Optional.of(existingProductEntity)); 34 | doNothing().when(productRepository).delete(existingProductEntity); 35 | 36 | // When 37 | productDeleteService.deleteProductById(productId); 38 | 39 | // Then 40 | verify(productRepository, times(1)).findById(productId); 41 | verify(productRepository, times(1)).delete(existingProductEntity); 42 | 43 | } 44 | 45 | @Test 46 | void givenProductId_whenProductNotFound_thenThrowProductNotFoundException() { 47 | 48 | // Given 49 | String productId = "1"; 50 | when(productRepository.findById(productId)).thenReturn(Optional.empty()); 51 | 52 | // When/Then 53 | assertThrows(ProductNotFoundException.class, () -> productDeleteService.deleteProductById(productId)); 54 | 55 | // Verify 56 | verify(productRepository, times(1)).findById(productId); 57 | verify(productRepository, never()).delete(any()); 58 | 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/UserTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model; 2 | 3 | import com.security.rolepermissionexample.auth.model.enums.UserStatus; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | class UserTest { 11 | 12 | @Test 13 | void testEqualsAndHashCode() { 14 | 15 | // Given 16 | Permission permission1 = Permission.builder().id("1").name("READ_PRIVILEGES").build(); 17 | Permission permission2 = Permission.builder().id("2").name("WRITE_PRIVILEGES").build(); 18 | Role role1 = Role.builder().id("role1").name("Admin").permissions(List.of(permission1)).build(); 19 | Role role2 = Role.builder().id("role2").name("User").permissions(List.of(permission2)).build(); 20 | 21 | User user1 = User.builder() 22 | .id("user123") 23 | .email("test@example.com") 24 | .firstName("John") 25 | .lastName("Doe") 26 | .phoneNumber("1234567890") 27 | .userStatus(UserStatus.ACTIVE) 28 | .roles(List.of(role1, role2)) 29 | .permissions(List.of(permission1, permission2)) 30 | .build(); 31 | 32 | User user2 = User.builder() 33 | .id("user123") 34 | .email("test@example.com") 35 | .firstName("John") 36 | .lastName("Doe") 37 | .phoneNumber("1234567890") 38 | .userStatus(UserStatus.ACTIVE) 39 | .roles(List.of(role1, role2)) 40 | .permissions(List.of(permission1, permission2)) 41 | .build(); 42 | 43 | User user3 = User.builder() 44 | .id("user456") 45 | .email("other@example.com") 46 | .firstName("Jane") 47 | .lastName("Doe") 48 | .phoneNumber("0987654321") 49 | .userStatus(UserStatus.PASSIVE) 50 | .roles(List.of(role1)) 51 | .permissions(List.of(permission2)) 52 | .build(); 53 | 54 | // Then 55 | assertNotEquals(user1, user3); 56 | assertNotEquals(user1.hashCode(), user3.hashCode()); 57 | 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/common/config/OpenApiConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.config; 2 | 3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 4 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; 5 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 6 | import io.swagger.v3.oas.annotations.info.Contact; 7 | import io.swagger.v3.oas.annotations.info.Info; 8 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | 13 | class OpenApiConfigTest { 14 | 15 | @Test 16 | void openApiInfo() { 17 | 18 | // Given 19 | OpenAPIDefinition openAPIDefinition = OpenApiConfig.class.getAnnotation(OpenAPIDefinition.class); 20 | 21 | // Then 22 | assertEquals("1.0.0", openAPIDefinition.info().version()); 23 | assertEquals("rolepermissionexample", openAPIDefinition.info().title()); 24 | assertEquals("Case Study - Role Permission Through Spring Security in Spring Boot" + 25 | "(Spring Boot, Spring Security , Mysql, JUnit, Integration Test, Docker, Test Container, Github Actions, Postman) ", 26 | openAPIDefinition.info().description()); 27 | 28 | } 29 | 30 | @Test 31 | void securityScheme() { 32 | 33 | // Given 34 | SecurityScheme securityScheme = OpenApiConfig.class.getAnnotation(SecurityScheme.class); 35 | 36 | // Then 37 | assertEquals("bearerAuth", securityScheme.name()); 38 | assertEquals("JWT Token", securityScheme.description()); 39 | assertEquals("bearer", securityScheme.scheme()); 40 | assertEquals(SecuritySchemeType.HTTP, securityScheme.type()); 41 | assertEquals("JWT", securityScheme.bearerFormat()); 42 | assertEquals(SecuritySchemeIn.HEADER, securityScheme.in()); 43 | 44 | } 45 | 46 | @Test 47 | void contactInfo() { 48 | 49 | // Given 50 | Info info = OpenApiConfig.class.getAnnotation(OpenAPIDefinition.class).info(); 51 | Contact contact = info.contact(); 52 | 53 | // Then 54 | assertEquals("Sercan Noyan Germiyanoğlu", contact.name()); 55 | assertEquals("https://github.com/Rapter1990/parkinglot/", contact.url()); 56 | 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/common/util/ListUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class ListUtilTest { 12 | 13 | @Test 14 | void utilityClass_ShouldNotBeInstantiated() { 15 | assertThrows(InvocationTargetException.class, () -> { 16 | // Attempt to use reflection to create an instance of the utility class 17 | java.lang.reflect.Constructor constructor = ListUtil.class.getDeclaredConstructor(); 18 | constructor.setAccessible(true); 19 | constructor.newInstance(); 20 | }); 21 | } 22 | 23 | @Test 24 | void testToWithValidList() { 25 | 26 | // Arrange 27 | List originalList = Arrays.asList("one", "two", "three"); 28 | 29 | // Act 30 | List result = ListUtil.to(originalList, String.class); 31 | 32 | // Assert 33 | assertNotNull(result); 34 | assertEquals(originalList.size(), result.size()); 35 | assertEquals(originalList, result); 36 | 37 | } 38 | 39 | @Test 40 | void testToWithEmptyList() { 41 | 42 | // Arrange 43 | List originalList = Arrays.asList(); 44 | 45 | // Act 46 | List result = ListUtil.to(originalList, String.class); 47 | 48 | // Assert 49 | assertNotNull(result); 50 | assertEquals(0, result.size()); 51 | 52 | } 53 | 54 | @Test 55 | void testToWithNullList() { 56 | 57 | // Arrange 58 | List originalList = null; 59 | 60 | // Act 61 | List result = ListUtil.to(originalList, String.class); 62 | 63 | // Assert 64 | assertNull(result); 65 | 66 | } 67 | 68 | @Test 69 | void testToWithDifferentClassType() { 70 | 71 | // Arrange 72 | List originalList = Arrays.asList(1, 2, 3); 73 | 74 | // Act 75 | List result = ListUtil.to(originalList, Integer.class); 76 | 77 | // Assert 78 | assertNotNull(result); 79 | assertEquals(originalList.size(), result.size()); 80 | assertEquals(originalList, result); 81 | 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/utils/KeyConverter.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.utils; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 5 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; 6 | import org.bouncycastle.openssl.PEMParser; 7 | import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; 8 | 9 | import java.io.IOException; 10 | import java.io.StringReader; 11 | import java.security.PrivateKey; 12 | import java.security.PublicKey; 13 | 14 | /** 15 | * Utility class named {@link KeyConverter} for converting PEM formatted keys to Java PublicKey and PrivateKey objects. 16 | */ 17 | @UtilityClass 18 | public class KeyConverter { 19 | 20 | /** 21 | * Converts a PEM formatted public key string to a Java PublicKey object. 22 | * 23 | * @param publicPemKey The PEM formatted public key string. 24 | * @return The converted PublicKey object. 25 | * @throws RuntimeException If an IOException occurs during key conversion. 26 | */ 27 | public PublicKey convertPublicKey(final String publicPemKey) { 28 | 29 | final StringReader keyReader = new StringReader(publicPemKey); 30 | try { 31 | SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo 32 | .getInstance(new PEMParser(keyReader).readObject()); 33 | return new JcaPEMKeyConverter().getPublicKey(publicKeyInfo); 34 | } catch (IOException exception) { 35 | throw new RuntimeException(exception); 36 | } 37 | 38 | } 39 | 40 | /** 41 | * Converts a PEM formatted private key string to a Java PrivateKey object. 42 | * 43 | * @param privatePemKey The PEM formatted private key string. 44 | * @return The converted PrivateKey object. 45 | * @throws RuntimeException If an IOException occurs during key conversion. 46 | */ 47 | public PrivateKey convertPrivateKey(final String privatePemKey) { 48 | 49 | StringReader keyReader = new StringReader(privatePemKey); 50 | try { 51 | PrivateKeyInfo privateKeyInfo = PrivateKeyInfo 52 | .getInstance(new PEMParser(keyReader).readObject()); 53 | return new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo); 54 | } catch (IOException exception) { 55 | throw new RuntimeException(exception); 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/model/ProductTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class ProductTest { 10 | 11 | @Test 12 | void testAllArgsConstructor() { 13 | // Given 14 | String id = "test-id"; 15 | String name = "test-name"; 16 | BigDecimal amount = new BigDecimal("10.00"); 17 | BigDecimal unitPrice = new BigDecimal("5.00"); 18 | 19 | // When 20 | Product product = new Product(id, name, amount, unitPrice); 21 | 22 | // Then 23 | assertNotNull(product); 24 | assertEquals(id, product.getId()); 25 | assertEquals(name, product.getName()); 26 | assertEquals(amount, product.getAmount()); 27 | assertEquals(unitPrice, product.getUnitPrice()); 28 | } 29 | 30 | @Test 31 | void testGettersAndSetters() { 32 | 33 | // Given 34 | Product product = new Product(); 35 | 36 | // When 37 | product.setId("test-id"); 38 | product.setName("test-name"); 39 | product.setAmount(new BigDecimal("10.00")); 40 | product.setUnitPrice(new BigDecimal("5.00")); 41 | 42 | // Then 43 | assertEquals("test-id", product.getId()); 44 | assertEquals("test-name", product.getName()); 45 | assertEquals(new BigDecimal("10.00"), product.getAmount()); 46 | assertEquals(new BigDecimal("5.00"), product.getUnitPrice()); 47 | } 48 | 49 | @Test 50 | void testEqualsAndHashCode() { 51 | // Given 52 | Product product1 = Product.builder() 53 | .id("test-id-1") 54 | .name("test-name-1") 55 | .amount(new BigDecimal("10.00")) 56 | .unitPrice(new BigDecimal("5.00")) 57 | .createdBy("test-user") 58 | .updatedBy("test-user") 59 | .build(); 60 | 61 | Product product3 = Product.builder() 62 | .id("test-id-2") 63 | .name("test-name-2") 64 | .amount(new BigDecimal("20.00")) 65 | .unitPrice(new BigDecimal("10.00")) 66 | .createdBy("another-user") 67 | .updatedBy("another-user") 68 | .build(); 69 | 70 | // Then 71 | assertNotEquals(product1, product3); 72 | assertNotEquals(product1.hashCode(), product3.hashCode()); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/service/impl/InvalidTokenServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.exception.TokenAlreadyInvalidatedException; 4 | import com.security.rolepermissionexample.auth.model.entity.InvalidTokenEntity; 5 | import com.security.rolepermissionexample.auth.repository.InvalidTokenRepository; 6 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | 11 | import java.util.Collections; 12 | import java.util.Optional; 13 | import java.util.Set; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertThrows; 16 | import static org.mockito.Mockito.*; 17 | 18 | class InvalidTokenServiceImplTest extends AbstractBaseServiceTest { 19 | 20 | @InjectMocks 21 | private InvalidTokenServiceImpl invalidTokenService; 22 | 23 | @Mock 24 | private InvalidTokenRepository invalidTokenRepository; 25 | 26 | @Test 27 | void invalidateTokens_ShouldSaveAllInvalidTokens() { 28 | 29 | // Given 30 | Set tokenIds = Set.of("token-id-1", "token-id-2"); 31 | 32 | // When 33 | when(invalidTokenRepository.saveAll(any(Set.class))).thenReturn(Collections.emptyList()); 34 | 35 | // Then 36 | invalidTokenService.invalidateTokens(tokenIds); 37 | 38 | // Verify 39 | verify(invalidTokenRepository, times(1)).saveAll(any(Set.class)); 40 | 41 | } 42 | 43 | @Test 44 | void checkForInvalidityOfToken_ShouldThrowExceptionIfTokenInvalid() { 45 | 46 | // Given 47 | String tokenId = "test-token-id"; 48 | 49 | // When 50 | when(invalidTokenRepository.findByTokenId(tokenId)).thenReturn(Optional.of(new InvalidTokenEntity())); 51 | 52 | // Then 53 | assertThrows(TokenAlreadyInvalidatedException.class, () -> { 54 | invalidTokenService.checkForInvalidityOfToken(tokenId); 55 | }); 56 | 57 | // Verify 58 | verify(invalidTokenRepository, times(1)).findByTokenId(tokenId); 59 | 60 | } 61 | 62 | @Test 63 | void checkForInvalidityOfToken_ShouldNotThrowExceptionIfTokenValid() { 64 | 65 | // Given 66 | String tokenId = "test-token-id"; 67 | 68 | // When 69 | when(invalidTokenRepository.findByTokenId(tokenId)).thenReturn(Optional.empty()); 70 | 71 | // Then 72 | invalidTokenService.checkForInvalidityOfToken(tokenId); 73 | 74 | // Verify 75 | verify(invalidTokenRepository, times(1)).findByTokenId(tokenId); 76 | 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import com.security.rolepermissionexample.auth.model.enums.TokenClaims; 4 | import com.security.rolepermissionexample.auth.model.enums.UserStatus; 5 | import com.security.rolepermissionexample.common.model.entity.BaseEntity; 6 | import jakarta.persistence.*; 7 | import lombok.*; 8 | import lombok.experimental.SuperBuilder; 9 | 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * Represents an entity as {@link UserEntity} for users. 16 | */ 17 | @Entity 18 | @Getter 19 | @Setter 20 | @SuperBuilder 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | @EqualsAndHashCode(callSuper = true) 24 | @Table(name = "USERS") 25 | public class UserEntity extends BaseEntity { 26 | 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.UUID) 29 | @Column(name = "ID") 30 | private String id; 31 | 32 | @Column(name = "EMAIL") 33 | private String email; 34 | 35 | @Column(name = "PASSWORD") 36 | private String password; 37 | 38 | @Column(name = "FIRST_NAME") 39 | private String firstName; 40 | 41 | @Column(name = "LAST_NAME") 42 | private String lastName; 43 | 44 | @Column( 45 | name = "PHONE_NUMBER", 46 | length = 20 47 | ) 48 | private String phoneNumber; 49 | 50 | @Builder.Default 51 | @Enumerated(EnumType.STRING) 52 | private UserStatus userStatus = UserStatus.ACTIVE; 53 | 54 | @ManyToMany 55 | @JoinTable( 56 | name = "USER_ROLE_RELATION_TABLE", 57 | joinColumns = @JoinColumn(name = "USER_ID"), 58 | inverseJoinColumns = @JoinColumn(name = "ROLE_ID") 59 | ) 60 | private List roles; 61 | 62 | public Map getClaims() { 63 | 64 | final Map claims = new HashMap<>(); 65 | 66 | claims.put(TokenClaims.USER_ID.getValue(), this.id); 67 | claims.put(TokenClaims.USER_PERMISSIONS.getValue(), this.roles.stream() 68 | .map(RoleEntity::getPermissions) 69 | .flatMap(List::stream) 70 | .map(PermissionEntity::getName) 71 | .toList()); 72 | claims.put(TokenClaims.USER_STATUS.getValue(), this.userStatus); 73 | claims.put(TokenClaims.USER_FIRST_NAME.getValue(), this.firstName); 74 | claims.put(TokenClaims.USER_LAST_NAME.getValue(), this.lastName); 75 | claims.put(TokenClaims.USER_EMAIL.getValue(), this.email); 76 | claims.put(TokenClaims.USER_PHONE_NUMBER.getValue(), this.phoneNumber); 77 | 78 | return claims; 79 | 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/model/entity/RoleEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.entity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class RoleEntityTest { 10 | 11 | @Test 12 | void testSuperBuilder() { 13 | 14 | // Given 15 | String id = "test-id"; 16 | String name = "Admin"; 17 | List permissions = List.of(new PermissionEntity(), new PermissionEntity()); 18 | 19 | // When 20 | RoleEntity roleEntity = RoleEntity.builder() 21 | .id(id) 22 | .name(name) 23 | .permissions(permissions) 24 | .build(); 25 | 26 | // Then 27 | assertNotNull(roleEntity); 28 | assertEquals(id, roleEntity.getId()); 29 | assertEquals(name, roleEntity.getName()); 30 | assertEquals(permissions, roleEntity.getPermissions()); 31 | 32 | } 33 | 34 | @Test 35 | void testAllArgsConstructor() { 36 | // Given 37 | String id = "test-id"; 38 | String name = "Admin"; 39 | List permissions = List.of(new PermissionEntity(), new PermissionEntity()); 40 | 41 | // When 42 | RoleEntity roleEntity = new RoleEntity(id, name, permissions); 43 | 44 | // Then 45 | assertNotNull(roleEntity); 46 | assertEquals(id, roleEntity.getId()); 47 | assertEquals(name, roleEntity.getName()); 48 | assertEquals(permissions, roleEntity.getPermissions()); 49 | } 50 | 51 | @Test 52 | void testEqualsAndHashCode() { 53 | // Given 54 | String id1 = "test-id-1"; 55 | String id2 = "test-id-2"; 56 | RoleEntity role1 = RoleEntity.builder().id(id1).build(); 57 | RoleEntity role3 = RoleEntity.builder().id(id2).build(); 58 | 59 | // Then 60 | assertNotEquals(role1, role3); 61 | assertNotEquals(role1.hashCode(), role3.hashCode()); 62 | } 63 | 64 | @Test 65 | void testGettersAndSetters() { 66 | 67 | // Given 68 | String id = "test-id"; 69 | String name = "Admin"; 70 | List permissions = List.of(new PermissionEntity(), new PermissionEntity()); 71 | RoleEntity roleEntity = new RoleEntity(); 72 | 73 | // When 74 | roleEntity.setId(id); 75 | roleEntity.setName(name); 76 | roleEntity.setPermissions(permissions); 77 | 78 | // Then 79 | assertEquals(id, roleEntity.getId()); 80 | assertEquals(name, roleEntity.getName()); 81 | assertEquals(permissions, roleEntity.getPermissions()); 82 | 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service; 2 | 3 | import com.security.rolepermissionexample.auth.model.Token; 4 | import io.jsonwebtoken.Claims; 5 | import io.jsonwebtoken.Jws; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | /** 12 | * Service interface named {@link TokenService} for managing authentication tokens. 13 | */ 14 | public interface TokenService { 15 | 16 | /** 17 | * Generates an authentication token with specified claims. 18 | * 19 | * @param claims The claims to include in the token. 20 | * @return The generated authentication token. 21 | */ 22 | Token generateToken(final Map claims); 23 | 24 | /** 25 | * Generates an authentication token with specified claims and a refresh token. 26 | * 27 | * @param claims The claims to include in the token. 28 | * @param refreshToken The refresh token associated with the authentication token. 29 | * @return The generated authentication token. 30 | */ 31 | Token generateToken(final Map claims, final String refreshToken); 32 | 33 | /** 34 | * Retrieves authentication information from a token. 35 | * 36 | * @param token The authentication token. 37 | * @return Authentication details extracted from the token. 38 | */ 39 | UsernamePasswordAuthenticationToken getAuthentication(final String token); 40 | 41 | /** 42 | * Verifies and validates a JWT token. 43 | * 44 | * @param jwt The JWT token to verify and validate. 45 | */ 46 | void verifyAndValidate(final String jwt); 47 | 48 | /** 49 | * Verifies and validates multiple JWT tokens. 50 | * 51 | * @param jwts The set of JWT tokens to verify and validate. 52 | */ 53 | void verifyAndValidate(final Set jwts); 54 | 55 | /** 56 | * Retrieves the claims embedded within a JWT token. 57 | * 58 | * @param jwt The JWT token. 59 | * @return The claims extracted from the JWT token. 60 | */ 61 | Jws getClaims(final String jwt); 62 | 63 | /** 64 | * Retrieves the payload (claims) embedded within a JWT token. 65 | * 66 | * @param jwt The JWT token. 67 | * @return The payload (claims) extracted from the JWT token. 68 | */ 69 | Claims getPayload(final String jwt); 70 | 71 | /** 72 | * Retrieves the ID (subject) from a JWT token. 73 | * 74 | * @param jwt The JWT token. 75 | * @return The ID (subject) extracted from the JWT token. 76 | */ 77 | String getId(final String jwt); 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/security/CustomAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.security; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 5 | import com.security.rolepermissionexample.common.model.CustomError; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.security.core.AuthenticationException; 11 | import org.springframework.security.web.AuthenticationEntryPoint; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.io.IOException; 15 | import java.text.DateFormat; 16 | 17 | /** 18 | * Custom implementation as {@link CustomAuthenticationEntryPoint} of {@link AuthenticationEntryPoint} used to handle authentication failures. 19 | * Returns an HTTP 401 Unauthorized response with a JSON error message. 20 | */ 21 | @Component 22 | public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { 23 | 24 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 25 | 26 | static { 27 | OBJECT_MAPPER.registerModule(new JavaTimeModule()); 28 | } 29 | 30 | /** 31 | * Invoked when authentication fails. Sends an HTTP 401 Unauthorized response with a JSON error message 32 | * describing the authentication error. 33 | * 34 | * @param httpServletRequest The request that resulted in an authentication failure. 35 | * @param httpServletResponse The response to send to the client. 36 | * @param authenticationException The authentication exception that occurred. 37 | * @throws IOException If an input or output exception occurs during the response writing. 38 | */ 39 | @Override 40 | public void commence(final HttpServletRequest httpServletRequest, 41 | final HttpServletResponse httpServletResponse, 42 | final AuthenticationException authenticationException) throws IOException { 43 | 44 | httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); 45 | httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); 46 | 47 | final CustomError customError = CustomError.builder() 48 | .header(CustomError.Header.AUTH_ERROR.getName()) 49 | .httpStatus(HttpStatus.UNAUTHORIZED) 50 | .isSuccess(false) 51 | .build(); 52 | 53 | final String responseBody = OBJECT_MAPPER 54 | .writer(DateFormat.getDateInstance()) 55 | .writeValueAsString(customError); 56 | 57 | httpServletResponse.getOutputStream() 58 | .write(responseBody.getBytes()); 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/common/model/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.common.model.entity; 2 | 3 | import com.security.rolepermissionexample.auth.model.enums.TokenClaims; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.MappedSuperclass; 6 | import jakarta.persistence.PrePersist; 7 | import jakarta.persistence.PreUpdate; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.Setter; 12 | import lombok.experimental.SuperBuilder; 13 | import org.springframework.security.core.Authentication; 14 | import org.springframework.security.core.context.SecurityContextHolder; 15 | import org.springframework.security.oauth2.jwt.Jwt; 16 | 17 | import java.time.LocalDateTime; 18 | import java.util.Optional; 19 | 20 | /** 21 | * Base entity class named {@link BaseEntity} with common fields for audit tracking and lifecycle management. 22 | * Provides automatic population of audit fields using JPA lifecycle annotations. 23 | */ 24 | @Getter 25 | @Setter 26 | @SuperBuilder 27 | @MappedSuperclass 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | public class BaseEntity { 31 | 32 | @Column(name = "CREATED_AT") 33 | private LocalDateTime createdAt; 34 | 35 | @Column(name = "CREATED_BY") 36 | private String createdBy; 37 | 38 | @Column(name = "UPDATED_AT") 39 | private LocalDateTime updatedAt; 40 | 41 | @Column(name = "UPDATED_BY") 42 | private String updatedBy; 43 | 44 | /** 45 | * Sets the createdBy and createdAt fields before persisting the entity. 46 | * If no authenticated user is found, sets createdBy to "anonymousUser". 47 | */ 48 | @PrePersist 49 | public void prePersist() { 50 | this.createdBy = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) 51 | .map(Authentication::getPrincipal) 52 | .filter(user -> !"anonymousUser".equals(user)) 53 | .map(Jwt.class::cast) 54 | .map(jwt -> jwt.getClaim(TokenClaims.USER_EMAIL.getValue()).toString()) 55 | .orElse("anonymousUser"); 56 | this.createdAt = LocalDateTime.now(); 57 | } 58 | 59 | /** 60 | * Sets the updatedBy and updatedAt fields before updating the entity. 61 | * If no authenticated user is found, sets updatedBy to "anonymousUser". 62 | */ 63 | @PreUpdate 64 | public void preUpdate() { 65 | this.updatedBy = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) 66 | .map(Authentication::getPrincipal) 67 | .filter(user -> !"anonymousUser".equals(user)) 68 | .map(Jwt.class::cast) 69 | .map(jwt -> jwt.getClaim(TokenClaims.USER_EMAIL.getValue()).toString()) 70 | .orElse("anonymousUser"); 71 | this.updatedAt = LocalDateTime.now(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/impl/RefreshTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.exception.UserNotFoundException; 4 | import com.security.rolepermissionexample.auth.exception.UserStatusNotValidException; 5 | import com.security.rolepermissionexample.auth.model.Token; 6 | import com.security.rolepermissionexample.auth.model.dto.request.TokenRefreshRequest; 7 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 8 | import com.security.rolepermissionexample.auth.model.enums.TokenClaims; 9 | import com.security.rolepermissionexample.auth.model.enums.UserStatus; 10 | import com.security.rolepermissionexample.auth.repository.UserRepository; 11 | import com.security.rolepermissionexample.auth.service.RefreshTokenService; 12 | import com.security.rolepermissionexample.auth.service.TokenService; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.stereotype.Service; 15 | 16 | /** 17 | * Service implementation named {@link RefreshTokenServiceImpl} for refreshing authentication tokens. 18 | */ 19 | @Service 20 | @RequiredArgsConstructor 21 | public class RefreshTokenServiceImpl implements RefreshTokenService { 22 | 23 | private final UserRepository userRepository; 24 | private final TokenService tokenService; 25 | 26 | /** 27 | * Refreshes an authentication token based on the provided refresh token request. 28 | * 29 | * @param tokenRefreshRequest The request containing the refresh token. 30 | * @return The refreshed authentication token. 31 | */ 32 | @Override 33 | public Token refreshToken(TokenRefreshRequest tokenRefreshRequest) { 34 | 35 | tokenService.verifyAndValidate(tokenRefreshRequest.getRefreshToken()); 36 | 37 | final String adminId = tokenService 38 | .getPayload(tokenRefreshRequest.getRefreshToken()) 39 | .get(TokenClaims.USER_ID.getValue()) 40 | .toString(); 41 | 42 | final UserEntity userEntityFromDB = userRepository 43 | .findById(adminId) 44 | .orElseThrow(UserNotFoundException::new); 45 | 46 | this.validateAdminStatus(userEntityFromDB); 47 | 48 | return tokenService.generateToken( 49 | userEntityFromDB.getClaims(), 50 | tokenRefreshRequest.getRefreshToken() 51 | ); 52 | } 53 | 54 | /** 55 | * Validates the status of an admin user. 56 | * 57 | * @param userEntity The user entity to validate. 58 | * @throws UserStatusNotValidException If the user status is not valid. 59 | */ 60 | private void validateAdminStatus(final UserEntity userEntity) { 61 | if (!(UserStatus.ACTIVE.equals(userEntity.getUserStatus()))) { 62 | throw new UserStatusNotValidException("UserStatus = " + userEntity.getUserStatus()); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/model/mapper/CustomPageToCustomPagingResponseMapper.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.model.mapper; 2 | 3 | import com.security.rolepermissionexample.common.model.CustomPage; 4 | import com.security.rolepermissionexample.common.model.dto.response.CustomPagingResponse; 5 | import com.security.rolepermissionexample.product.model.Product; 6 | import com.security.rolepermissionexample.product.model.dto.response.ProductResponse; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * Mapper interface named {@link CustomPageToCustomPagingResponseMapper} for converting {@link CustomPage} to {@link CustomPagingResponse}. 15 | */ 16 | @Mapper 17 | public interface CustomPageToCustomPagingResponseMapper { 18 | 19 | ProductToProductResponseMapper productToProductResponseMapper = Mappers.getMapper(ProductToProductResponseMapper.class); 20 | 21 | /** 22 | * Converts a CustomPage object to CustomPagingResponse. 23 | * 24 | * @param productPage The CustomPage object to convert. 25 | * @return CustomPagingResponse object containing mapped data. 26 | */ 27 | default CustomPagingResponse toPagingResponse(CustomPage productPage) { 28 | 29 | if (productPage == null) { 30 | return null; 31 | } 32 | 33 | return CustomPagingResponse.builder() 34 | .content(toProductResponseList(productPage.getContent())) 35 | .totalElementCount(productPage.getTotalElementCount()) 36 | .totalPageCount(productPage.getTotalPageCount()) 37 | .pageNumber(productPage.getPageNumber()) 38 | .pageSize(productPage.getPageSize()) 39 | .build(); 40 | 41 | } 42 | 43 | /** 44 | * Converts a list of Product objects to a list of ProductResponse objects. 45 | * 46 | * @param products The list of Product objects to convert. 47 | * @return List of ProductResponse objects containing mapped data. 48 | */ 49 | default List toProductResponseList(List products) { 50 | 51 | if (products == null) { 52 | return null; 53 | } 54 | 55 | return products.stream() 56 | .map(productToProductResponseMapper::map) 57 | .collect(Collectors.toList()); 58 | 59 | } 60 | 61 | /** 62 | * Initializes and returns an instance of CustomPageToCustomPagingResponseMapper. 63 | * 64 | * @return Initialized CustomPageToCustomPagingResponseMapper instance. 65 | */ 66 | static CustomPageToCustomPagingResponseMapper initialize() { 67 | return Mappers.getMapper(CustomPageToCustomPagingResponseMapper.class); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/impl/ProductCreateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.product.exception.ProductAlreadyExistException; 4 | import com.security.rolepermissionexample.product.model.Product; 5 | import com.security.rolepermissionexample.product.model.dto.request.ProductCreateRequest; 6 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 7 | import com.security.rolepermissionexample.product.model.mapper.ProductCreateRequestToProductEntityMapper; 8 | import com.security.rolepermissionexample.product.model.mapper.ProductEntityToProductMapper; 9 | import com.security.rolepermissionexample.product.repository.ProductRepository; 10 | import com.security.rolepermissionexample.product.service.ProductCreateService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Service; 13 | 14 | /** 15 | * Service implementation named {@link ProductCreateServiceImpl} for creating products. 16 | */ 17 | @Service 18 | @RequiredArgsConstructor 19 | public class ProductCreateServiceImpl implements ProductCreateService { 20 | 21 | private final ProductRepository productRepository; 22 | 23 | private final ProductCreateRequestToProductEntityMapper productCreateRequestToProductEntityMapper = 24 | ProductCreateRequestToProductEntityMapper.initialize(); 25 | 26 | private final ProductEntityToProductMapper productEntityToProductMapper = ProductEntityToProductMapper.initialize(); 27 | 28 | /** 29 | * Creates a new product based on the provided product creation request. 30 | * 31 | * @param productCreateRequest The request containing data to create the product. 32 | * @return The created Product object. 33 | * @throws ProductAlreadyExistException If a product with the same name already exists. 34 | */ 35 | @Override 36 | public Product createProduct(ProductCreateRequest productCreateRequest) { 37 | 38 | checkUniquenessProductName(productCreateRequest.getName()); 39 | 40 | final ProductEntity productEntityToBeSave = productCreateRequestToProductEntityMapper.mapForSaving(productCreateRequest); 41 | 42 | ProductEntity savedProductEntity = productRepository.save(productEntityToBeSave); 43 | 44 | return productEntityToProductMapper.map(savedProductEntity); 45 | 46 | } 47 | 48 | /** 49 | * Checks if a product with the given name already exists in the repository. 50 | * 51 | * @param productName The name of the product to check. 52 | * @throws ProductAlreadyExistException If a product with the same name already exists. 53 | */ 54 | private void checkUniquenessProductName(final String productName) { 55 | if (productRepository.existsProductEntityByName(productName)) { 56 | throw new ProductAlreadyExistException("There is another product with given name: " + productName); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/filter/CustomBearerTokenAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.filter; 2 | 3 | import com.security.rolepermissionexample.auth.model.Token; 4 | import com.security.rolepermissionexample.auth.service.InvalidTokenService; 5 | import com.security.rolepermissionexample.auth.service.TokenService; 6 | import jakarta.servlet.FilterChain; 7 | import jakarta.servlet.ServletException; 8 | import jakarta.servlet.http.HttpServletRequest; 9 | import jakarta.servlet.http.HttpServletResponse; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.http.HttpHeaders; 13 | import org.springframework.lang.NonNull; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.context.SecurityContextHolder; 16 | import org.springframework.stereotype.Component; 17 | import org.springframework.web.filter.OncePerRequestFilter; 18 | 19 | import java.io.IOException; 20 | 21 | /** 22 | * A custom filter named {@link CustomBearerTokenAuthenticationFilter} for processing bearer token authentication. 23 | */ 24 | @Slf4j 25 | @Component 26 | @RequiredArgsConstructor 27 | public class CustomBearerTokenAuthenticationFilter extends OncePerRequestFilter { 28 | 29 | private final TokenService tokenService; 30 | private final InvalidTokenService invalidTokenService; 31 | 32 | /** 33 | * Processes the HTTP request and performs bearer token authentication if a valid bearer token is present. 34 | * 35 | * @param httpServletRequest the HTTP request. 36 | * @param httpServletResponse the HTTP response. 37 | * @param filterChain the filter chain. 38 | * @throws ServletException if an exception occurs that interferes with the filter chain's operation. 39 | * @throws IOException if an I/O error occurs during processing. 40 | */ 41 | @Override 42 | protected void doFilterInternal(@NonNull final HttpServletRequest httpServletRequest, 43 | @NonNull final HttpServletResponse httpServletResponse, 44 | @NonNull final FilterChain filterChain) throws ServletException, IOException { 45 | 46 | log.debug("API Request was secured with Security!"); 47 | 48 | final String authorizationHeader = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION); 49 | 50 | if (Token.isBearerToken(authorizationHeader)) { 51 | 52 | final String jwt = Token.getJwt(authorizationHeader); 53 | 54 | tokenService.verifyAndValidate(jwt); 55 | 56 | final String tokenId = tokenService.getId(jwt); 57 | 58 | invalidTokenService.checkForInvalidityOfToken(tokenId); 59 | 60 | final UsernamePasswordAuthenticationToken authentication = tokenService 61 | .getAuthentication(jwt); 62 | 63 | SecurityContextHolder.getContext().setAuthentication(authentication); 64 | 65 | } 66 | 67 | filterChain.doFilter(httpServletRequest,httpServletResponse); 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/impl/ProductReadServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.common.model.CustomPage; 4 | import com.security.rolepermissionexample.product.exception.ProductNotFoundException; 5 | import com.security.rolepermissionexample.product.model.Product; 6 | import com.security.rolepermissionexample.product.model.dto.request.ProductPagingRequest; 7 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 8 | import com.security.rolepermissionexample.product.model.mapper.ListProductEntityToListProductMapper; 9 | import com.security.rolepermissionexample.product.model.mapper.ProductEntityToProductMapper; 10 | import com.security.rolepermissionexample.product.repository.ProductRepository; 11 | import com.security.rolepermissionexample.product.service.ProductReadService; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * Service implementation named {@link ProductReadServiceImpl} for reading products. 20 | */ 21 | @Service 22 | @RequiredArgsConstructor 23 | public class ProductReadServiceImpl implements ProductReadService { 24 | 25 | private final ProductRepository productRepository; 26 | 27 | private final ProductEntityToProductMapper productEntityToProductMapper = ProductEntityToProductMapper.initialize(); 28 | 29 | private final ListProductEntityToListProductMapper listProductEntityToListProductMapper = 30 | ListProductEntityToListProductMapper.initialize(); 31 | 32 | /** 33 | * Retrieves a product by its unique ID. 34 | * 35 | * @param productId The ID of the product to retrieve. 36 | * @return The Product object corresponding to the given ID. 37 | * @throws ProductNotFoundException If no product with the given ID exists. 38 | */ 39 | @Override 40 | public Product getProductById(String productId) { 41 | 42 | final ProductEntity productEntityFromDB = productRepository 43 | .findById(productId) 44 | .orElseThrow(() -> new ProductNotFoundException("With given productID = " + productId)); 45 | 46 | return productEntityToProductMapper.map(productEntityFromDB); 47 | } 48 | 49 | /** 50 | * Retrieves a page of products based on the paging request criteria. 51 | * 52 | * @param productPagingRequest The paging request criteria. 53 | * @return A CustomPage containing the list of products that match the paging criteria. 54 | * @throws ProductNotFoundException If no products are found based on the paging criteria. 55 | */ 56 | @Override 57 | public CustomPage getProducts(ProductPagingRequest productPagingRequest) { 58 | 59 | final Page productEntityPage = productRepository.findAll(productPagingRequest.toPageable()); 60 | 61 | if (productEntityPage.getContent().isEmpty()) { 62 | throw new ProductNotFoundException("Couldn't find any Product"); 63 | } 64 | 65 | final List productDomainModels = listProductEntityToListProductMapper 66 | .toProductList(productEntityPage.getContent()); 67 | 68 | return CustomPage.of(productDomainModels, productEntityPage); 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/service/impl/LogoutServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.model.dto.request.TokenInvalidateRequest; 4 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 5 | import com.security.rolepermissionexample.auth.service.InvalidTokenService; 6 | import com.security.rolepermissionexample.auth.service.TokenService; 7 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 8 | import com.security.rolepermissionexample.builder.TokenBuilder; 9 | import com.security.rolepermissionexample.builder.UserEntityBuilder; 10 | import io.jsonwebtoken.Claims; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.InjectMocks; 13 | import org.mockito.Mock; 14 | 15 | import java.util.Set; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | import static org.mockito.Mockito.*; 19 | 20 | class LogoutServiceImplTest extends AbstractBaseServiceTest { 21 | 22 | @InjectMocks 23 | private LogoutServiceImpl logoutService; 24 | 25 | @Mock 26 | private TokenService tokenService; 27 | 28 | @Mock 29 | private InvalidTokenService invalidTokenService; 30 | 31 | @Test 32 | void givenAccessTokenAndRefreshToken_whenLogoutForAdmin_thenReturnLogout() { 33 | 34 | // Given 35 | final UserEntity mockUserEntity = new UserEntityBuilder().withValidAdminFields().build(); 36 | 37 | final Claims mockAccessTokenClaims = TokenBuilder.getValidClaims( 38 | mockUserEntity.getId(), 39 | mockUserEntity.getFirstName() 40 | ); 41 | 42 | final Claims mockRefreshTokenClaims = TokenBuilder.getValidClaims( 43 | mockUserEntity.getId(), 44 | mockUserEntity.getFirstName() 45 | ); 46 | 47 | final String mockAccessTokenId = mockAccessTokenClaims.getId(); 48 | final String mockRefreshTokenId = mockRefreshTokenClaims.getId(); 49 | 50 | 51 | final String accessToken = "validAccessToken"; 52 | final String refreshToken = "validRefreshToken"; 53 | 54 | final TokenInvalidateRequest tokenInvalidateRequest = TokenInvalidateRequest.builder() 55 | .accessToken(accessToken) 56 | .refreshToken(refreshToken) 57 | .build(); 58 | 59 | // When 60 | doNothing().when(tokenService).verifyAndValidate(Set.of(accessToken, refreshToken)); 61 | when(tokenService.getPayload(accessToken)).thenReturn(mockAccessTokenClaims); 62 | doNothing().when(invalidTokenService).checkForInvalidityOfToken(mockAccessTokenId); 63 | when(tokenService.getPayload(refreshToken)).thenReturn(mockRefreshTokenClaims); 64 | doNothing().when(invalidTokenService).checkForInvalidityOfToken(mockRefreshTokenId); 65 | doNothing().when(invalidTokenService).invalidateTokens(Set.of(mockAccessTokenId, mockRefreshTokenId)); 66 | 67 | // Then 68 | logoutService.logout(tokenInvalidateRequest); 69 | 70 | // Verify 71 | verify(tokenService).verifyAndValidate(Set.of(accessToken, refreshToken)); 72 | verify(tokenService, times(2)).getPayload(anyString()); 73 | verify(invalidTokenService, times(2)).checkForInvalidityOfToken(anyString()); 74 | verify(invalidTokenService).invalidateTokens(anySet()); 75 | 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/model/enums/ConfigurationParameter.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.model.enums; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * Enum representing various configuration parameters as {@link ConfigurationParameter}. 8 | * Each enum constant holds a default value for the corresponding configuration parameter. 9 | */ 10 | @Getter 11 | @RequiredArgsConstructor 12 | public enum ConfigurationParameter { 13 | 14 | ISSUER("ISSUER"), 15 | 16 | AUTH_ACCESS_TOKEN_EXPIRE_MINUTE("30"), 17 | AUTH_REFRESH_TOKEN_EXPIRE_DAY("1"), 18 | AUTH_PUBLIC_KEY(""" 19 | -----BEGIN PUBLIC KEY----- 20 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1HmZ3A379M6Rv9UnMt9R 21 | Wq0a6bpcnoOWJxTi2exwnecW3r1X1PjeUvsDogy7RYjhlxU0G+1r38gPWfUW2FNd 22 | tsa3H+FDhJ6dcNc4uKVYPsiVJukHi4NrvWA8E8dPdLW1lNcijr4PqXjvZTLoS1QX 23 | f30wnNLBNDwdPXTESodi/n87VoSH2ChgLZUVfoS3m/NlUN8Z58gGxRcpUyjl+MmC 24 | hD2cfyWr2xdxKd+UQMrd36LfyoWh0IlONlxo0H5x8JIwlziLbPEAh7dJ9QYM0b5G 25 | msXBAzrILvW5+POSq4u1vNlzSwdLe+AZ6bnCwQVrqvMn/I7JT+4+lY8BsP/gMRQL 26 | awIDAQAB 27 | -----END PUBLIC KEY----- 28 | """), 29 | AUTH_PRIVATE_KEY(""" 30 | -----BEGIN PRIVATE KEY----- 31 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUeZncDfv0zpG/ 32 | 1Scy31FarRrpulyeg5YnFOLZ7HCd5xbevVfU+N5S+wOiDLtFiOGXFTQb7WvfyA9Z 33 | 9RbYU122xrcf4UOEnp1w1zi4pVg+yJUm6QeLg2u9YDwTx090tbWU1yKOvg+peO9l 34 | MuhLVBd/fTCc0sE0PB09dMRKh2L+fztWhIfYKGAtlRV+hLeb82VQ3xnnyAbFFylT 35 | KOX4yYKEPZx/JavbF3Ep35RAyt3fot/KhaHQiU42XGjQfnHwkjCXOIts8QCHt0n1 36 | BgzRvkaaxcEDOsgu9bn485Kri7W82XNLB0t74BnpucLBBWuq8yf8jslP7j6VjwGw 37 | /+AxFAtrAgMBAAECggEARWpw5N7AuQsfvN+DjfA9oPU6/K9BARyWWrBNKMtBQ6Uy 38 | 6JRNdKvV3qBZYIDuUdpVcUmhG5qmipbOxSH4U7ZwwH0NaOHscBBt+WanBlQmj2Ry 39 | riKlr2PBOD6Pghq0j7mp2DWs+ZuIfGKhO5u1Hp8bijA5SJLmQg19tA1I79xpcCFC 40 | flY8WaBKJCTKtH3sisS9IqAtGx3+O2fwzk5oSA6DDuX+45zTLS1vlucwhTUTDE4j 41 | UitYM5CnW9LzJ72ofWZBE29twECXQa+7YYXYniqUOzYZVhy7OiwYT9HH2fDGurmL 42 | F4adtcmVS6CUM6OYSV8z1DXbwLcidTqwiAj9uZ8suQKBgQD2hb/Bimgoh0dT6q9N 43 | pORikLW9L/6q3TuAfNJrrP63dwgooeCcHdk8Wh1zFkNibmfdhHQM5Hu3pQq0oxtG 44 | XiH1WYhluDAjyq6ahvYasgGtdzh279VcicleP9x6B6tmWop+a9kM+Yh2r3Yb5KXr 45 | ZyFWLeoEkCyeXdVr1tds119Q/QKBgQDcpMKXIAiayZmcHU03pNrDGhs7m2B18S6Z 46 | WcXSQRf1d98wbzzfsrbHM9k8gX5VGvZb+rwl95SyevT1LSaGM44On4WXjCUlpUnb 47 | 8+mKONra8yXuWutB1aclPQddj4HAFGikJyBP45e/SXNTBPrK5cm/HULbgd76FxHv 48 | QQZ2EbSOhwKBgA5pHR98LsCHv+So6Fx6khss6GLJxnJIgmztXwOKVk11ONXfOJkH 49 | qaY8glIy7/d2Cr5JOttyE8VVcX3Dtxly8Ts9Y5rGnJHLDE/eKc6/rxdry7IwLOG+ 50 | 8DWBOCst/Zf7HPNs7IA0qgR+F0JkKErNeYZnIrHnl6QeShaGtYsYP+slAoGBANRU 51 | yd5dOWqb73NIz3Jo9w0iJmrqT52wh8OTnMeFVOUogmQ96Drt5O82eiu8AjMsS0Cg 52 | vkdbRoGryefXl2c2XdK8uPbqKyVbNwSwaWJW7GYf77S9UgB89ujjHh9vZtHN0hWG 53 | gZXf07yFlrGh7Scsk0WThy9uf4H0iZHQ5cLhrvwpAoGAAj3uW/aAJDhfHHVgBGhC 54 | la8OzEqY8UbqMYrXKfrmYOzvFYKWd5BgrZvgINhSmjxMeil952gHVsH/td+KUyqe 55 | dxTAgV9WVn8KVz6KoEKJV5CarN0IOl5fZVJz1i0Q5t6VdUXLC6teh4ByrEAlsRgi 56 | h2UWcQCA6KqrmJbR2q6yj+s= 57 | -----END PRIVATE KEY----- 58 | """); 59 | 60 | private final String defaultValue; 61 | 62 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/product/service/impl/ProductUpdateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.product.exception.ProductAlreadyExistException; 4 | import com.security.rolepermissionexample.product.exception.ProductNotFoundException; 5 | import com.security.rolepermissionexample.product.model.Product; 6 | import com.security.rolepermissionexample.product.model.dto.request.ProductUpdateRequest; 7 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 8 | import com.security.rolepermissionexample.product.model.mapper.ProductEntityToProductMapper; 9 | import com.security.rolepermissionexample.product.model.mapper.ProductUpdateRequestToProductEntityMapper; 10 | import com.security.rolepermissionexample.product.repository.ProductRepository; 11 | import com.security.rolepermissionexample.product.service.ProductUpdateService; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.stereotype.Service; 14 | 15 | /** 16 | * Service implementation named {@link ProductUpdateServiceImpl} for updating products. 17 | */ 18 | @Service 19 | @RequiredArgsConstructor 20 | public class ProductUpdateServiceImpl implements ProductUpdateService { 21 | 22 | private final ProductRepository productRepository; 23 | 24 | private final ProductUpdateRequestToProductEntityMapper productUpdateRequestToProductEntityMapper = 25 | ProductUpdateRequestToProductEntityMapper.initialize(); 26 | 27 | private final ProductEntityToProductMapper productEntityToProductMapper = 28 | ProductEntityToProductMapper.initialize(); 29 | 30 | /** 31 | * Updates a product identified by its unique ID using the provided update request. 32 | * 33 | * @param productId The ID of the product to update. 34 | * @param productUpdateRequest The request containing updated data for the product. 35 | * @return The updated Product object. 36 | * @throws ProductNotFoundException If no product with the given ID exists. 37 | * @throws ProductAlreadyExistException If another product with the updated name already exists. 38 | */ 39 | @Override 40 | public Product updateProductById(String productId, ProductUpdateRequest productUpdateRequest) { 41 | 42 | checkProductNameUniqueness(productUpdateRequest.getName()); 43 | 44 | final ProductEntity productEntityToBeUpdate = productRepository 45 | .findById(productId) 46 | .orElseThrow(() -> new ProductNotFoundException("With given productID = " + productId)); 47 | 48 | productUpdateRequestToProductEntityMapper.mapForUpdating(productEntityToBeUpdate, productUpdateRequest); 49 | 50 | ProductEntity updatedProductEntity = productRepository.save(productEntityToBeUpdate); 51 | 52 | return productEntityToProductMapper.map(updatedProductEntity); 53 | 54 | } 55 | 56 | /** 57 | * Checks if a product with the updated name already exists in the repository. 58 | * 59 | * @param productName The updated name of the product to check. 60 | * @throws ProductAlreadyExistException If another product with the updated name already exists. 61 | */ 62 | private void checkProductNameUniqueness(final String productName) { 63 | if (productRepository.existsProductEntityByName(productName)) { 64 | throw new ProductAlreadyExistException("With given product name = " + productName); 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/service/impl/RegisterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.exception.PermissionNotFoundException; 4 | import com.security.rolepermissionexample.auth.exception.RoleNotFoundException; 5 | import com.security.rolepermissionexample.auth.exception.UserAlreadyExistException; 6 | import com.security.rolepermissionexample.auth.model.User; 7 | import com.security.rolepermissionexample.auth.model.dto.request.RegisterRequest; 8 | import com.security.rolepermissionexample.auth.model.entity.PermissionEntity; 9 | import com.security.rolepermissionexample.auth.model.entity.RoleEntity; 10 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 11 | import com.security.rolepermissionexample.auth.model.mapper.RegisterRequestToUserEntityMapper; 12 | import com.security.rolepermissionexample.auth.model.mapper.UserEntityToUserMapper; 13 | import com.security.rolepermissionexample.auth.repository.PermissionRepository; 14 | import com.security.rolepermissionexample.auth.repository.RoleRepository; 15 | import com.security.rolepermissionexample.auth.repository.UserRepository; 16 | import com.security.rolepermissionexample.auth.service.RegisterService; 17 | import lombok.RequiredArgsConstructor; 18 | import org.springframework.security.crypto.password.PasswordEncoder; 19 | import org.springframework.stereotype.Service; 20 | 21 | import java.util.List; 22 | import java.util.stream.Collectors; 23 | 24 | /** 25 | * Service implementation named {@link RegisterServiceImpl} for user registration operations. 26 | */ 27 | @Service 28 | @RequiredArgsConstructor 29 | public class RegisterServiceImpl implements RegisterService { 30 | 31 | private final UserRepository userRepository; 32 | 33 | private final RoleRepository roleRepository; 34 | 35 | private final PermissionRepository permissionRepository; 36 | 37 | private final RegisterRequestToUserEntityMapper registerRequestToUserEntityMapper = 38 | RegisterRequestToUserEntityMapper.initialize(); 39 | 40 | private final UserEntityToUserMapper userEntityToUserMapper = UserEntityToUserMapper.initialize(); 41 | 42 | private final PasswordEncoder passwordEncoder; 43 | 44 | /** 45 | * Registers a new user based on the provided registration request. 46 | * 47 | * @param registerRequest The registration request containing user details. 48 | * @return The registered user entity. 49 | */ 50 | @Override 51 | public User registerUser(RegisterRequest registerRequest) { 52 | 53 | if (userRepository.existsUserEntityByEmail(registerRequest.getEmail())) { 54 | throw new UserAlreadyExistException("The email is already used for another user : " + registerRequest.getEmail()); 55 | } 56 | 57 | final UserEntity userEntityToBeSaved = registerRequestToUserEntityMapper.mapForSaving(registerRequest); 58 | 59 | userEntityToBeSaved.setPassword(passwordEncoder.encode(registerRequest.getPassword())); 60 | 61 | List roles = registerRequest.getRole().stream() 62 | .map(roleName -> roleRepository.findByName(roleName) 63 | .orElseThrow(() -> new RoleNotFoundException(roleName))) 64 | .collect(Collectors.toList()); 65 | 66 | roles.forEach(role -> role.setPermissions( 67 | registerRequest.getPermissions().stream() 68 | .map(permissionName -> permissionRepository.findByName(permissionName) 69 | .orElseThrow(() -> new PermissionNotFoundException(permissionName))) 70 | .collect(Collectors.toList()) 71 | )); 72 | 73 | userEntityToBeSaved.setRoles(roles); 74 | 75 | final UserEntity savedUserEntity = userRepository.save(userEntityToBeSaved); 76 | 77 | return userEntityToUserMapper.map(savedUserEntity); 78 | 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/service/impl/ProductCreateServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 4 | import com.security.rolepermissionexample.product.exception.ProductAlreadyExistException; 5 | import com.security.rolepermissionexample.product.model.Product; 6 | import com.security.rolepermissionexample.product.model.dto.request.ProductCreateRequest; 7 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 8 | import com.security.rolepermissionexample.product.model.mapper.ProductCreateRequestToProductEntityMapper; 9 | import com.security.rolepermissionexample.product.model.mapper.ProductEntityToProductMapper; 10 | import com.security.rolepermissionexample.product.repository.ProductRepository; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.InjectMocks; 13 | import org.mockito.Mock; 14 | 15 | import java.math.BigDecimal; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | import static org.mockito.ArgumentMatchers.any; 19 | import static org.mockito.Mockito.*; 20 | 21 | class ProductCreateServiceImplTest extends AbstractBaseServiceTest { 22 | 23 | @InjectMocks 24 | private ProductCreateServiceImpl productCreateService; 25 | 26 | @Mock 27 | private ProductRepository productRepository; 28 | 29 | private final ProductCreateRequestToProductEntityMapper productCreateRequestToProductEntityMapper = 30 | ProductCreateRequestToProductEntityMapper.initialize(); 31 | 32 | private final ProductEntityToProductMapper productEntityToProductMapper = ProductEntityToProductMapper.initialize(); 33 | 34 | @Test 35 | void givenProductCreateRequest_whenProductCreated_thenReturnProduct() { 36 | 37 | // Given 38 | String productName = "Test Product"; 39 | ProductCreateRequest productCreateRequest = ProductCreateRequest.builder() 40 | .name(productName) 41 | .unitPrice(BigDecimal.valueOf(12)) 42 | .amount(BigDecimal.valueOf(5)) 43 | .build(); 44 | 45 | ProductEntity productEntity = productCreateRequestToProductEntityMapper.mapForSaving(productCreateRequest); 46 | 47 | Product expected = productEntityToProductMapper.map(productEntity); 48 | 49 | // When 50 | when(productRepository.existsProductEntityByName(productName)).thenReturn(false); 51 | when(productRepository.save(any(ProductEntity.class))).thenReturn(productEntity); 52 | 53 | // Then 54 | Product createdProduct = productCreateService.createProduct(productCreateRequest); 55 | 56 | assertNotNull(createdProduct); 57 | assertEquals(expected.getName(), createdProduct.getName()); 58 | assertEquals(expected.getAmount(), createdProduct.getAmount()); 59 | assertEquals(expected.getUnitPrice(), createdProduct.getUnitPrice()); 60 | 61 | // Verify 62 | verify(productRepository, times(1)).existsProductEntityByName(productName); 63 | verify(productRepository, times(1)).save(any(ProductEntity.class)); 64 | 65 | } 66 | 67 | @Test 68 | void givenProductCreateRequest_whenProductAlreadyExists_ThenReturnProductAlreadyExistException() { 69 | 70 | // Given 71 | String productName = "Existing Product"; 72 | ProductCreateRequest productCreateRequest = new ProductCreateRequest(); 73 | productCreateRequest.setName(productName); 74 | 75 | // When 76 | when(productRepository.existsProductEntityByName(productName)).thenReturn(true); 77 | 78 | // Then 79 | ProductAlreadyExistException productAlreadyExistException = 80 | assertThrows(ProductAlreadyExistException.class, () -> productCreateService.createProduct(productCreateRequest)); 81 | 82 | assertEquals("Product already exist!\n There is another product with given name: " + productName, 83 | productAlreadyExistException.getMessage()); 84 | 85 | // Verify 86 | verify(productRepository, times(1)).existsProductEntityByName(productName); 87 | verify(productRepository, never()).save(any(ProductEntity.class)); 88 | 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.controller; 2 | 3 | import com.security.rolepermissionexample.auth.model.Token; 4 | import com.security.rolepermissionexample.auth.model.dto.request.LoginRequest; 5 | import com.security.rolepermissionexample.auth.model.dto.request.RegisterRequest; 6 | import com.security.rolepermissionexample.auth.model.dto.request.TokenInvalidateRequest; 7 | import com.security.rolepermissionexample.auth.model.dto.request.TokenRefreshRequest; 8 | import com.security.rolepermissionexample.auth.model.dto.response.TokenResponse; 9 | import com.security.rolepermissionexample.auth.model.mapper.TokenToTokenResponseMapper; 10 | import com.security.rolepermissionexample.auth.service.LoginService; 11 | import com.security.rolepermissionexample.auth.service.LogoutService; 12 | import com.security.rolepermissionexample.auth.service.RefreshTokenService; 13 | import com.security.rolepermissionexample.auth.service.RegisterService; 14 | import com.security.rolepermissionexample.common.model.dto.response.CustomResponse; 15 | import jakarta.validation.Valid; 16 | import lombok.RequiredArgsConstructor; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.RequestBody; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | /** 23 | * Controller named {@link AuthController} 24 | * for handling authentication-related requests such as registration, login, token refresh, and logout. 25 | */ 26 | @RestController 27 | @RequestMapping("/api/v1/authentication/user") 28 | @RequiredArgsConstructor 29 | public class AuthController { 30 | 31 | private final RegisterService registerService; 32 | 33 | private final LoginService loginService; 34 | 35 | private final RefreshTokenService refreshTokenService; 36 | 37 | private final LogoutService logoutService; 38 | 39 | 40 | private final TokenToTokenResponseMapper tokenToTokenResponseMapper = TokenToTokenResponseMapper.initialize(); 41 | 42 | /** 43 | * Registers a new user. 44 | * 45 | * @param registerRequest the request object containing registration details. 46 | * @return a {@link CustomResponse} indicating success or failure. 47 | */ 48 | @PostMapping("/register") 49 | public CustomResponse registerAdmin(@RequestBody @Valid final RegisterRequest registerRequest) { 50 | registerService.registerUser(registerRequest); 51 | return CustomResponse.SUCCESS; 52 | } 53 | 54 | /** 55 | * Logs in a user and returns an access token. 56 | * 57 | * @param loginRequest the request object containing login credentials. 58 | * @return a {@link CustomResponse} containing the {@link TokenResponse}. 59 | */ 60 | @PostMapping("/login") 61 | public CustomResponse loginAdmin(@RequestBody @Valid final LoginRequest loginRequest) { 62 | final Token token = loginService.login(loginRequest); 63 | final TokenResponse tokenResponse = tokenToTokenResponseMapper.map(token); 64 | return CustomResponse.successOf(tokenResponse); 65 | } 66 | 67 | /** 68 | * Refreshes the access token using a refresh token. 69 | * 70 | * @param tokenRefreshRequest the request object containing the refresh token. 71 | * @return a {@link CustomResponse} containing the new {@link TokenResponse}. 72 | */ 73 | @PostMapping("/refresh-token") 74 | public CustomResponse refreshToken(@RequestBody @Valid final TokenRefreshRequest tokenRefreshRequest) { 75 | final Token token = refreshTokenService.refreshToken(tokenRefreshRequest); 76 | final TokenResponse tokenResponse = tokenToTokenResponseMapper.map(token); 77 | return CustomResponse.successOf(tokenResponse); 78 | } 79 | 80 | /** 81 | * Logs out a user by invalidating their token. 82 | * 83 | * @param tokenInvalidateRequest the request object containing the token to be invalidated. 84 | * @return a {@link CustomResponse} indicating success or failure. 85 | */ 86 | @PostMapping("/logout") 87 | public CustomResponse logout(@RequestBody @Valid final TokenInvalidateRequest tokenInvalidateRequest) { 88 | logoutService.logout(tokenInvalidateRequest); 89 | return CustomResponse.SUCCESS; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/builder/UserEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.builder; 2 | 3 | import com.security.rolepermissionexample.auth.model.entity.PermissionEntity; 4 | import com.security.rolepermissionexample.auth.model.entity.RoleEntity; 5 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 6 | import com.security.rolepermissionexample.auth.model.enums.UserStatus; 7 | import org.hibernate.usertype.UserType; 8 | 9 | import java.util.List; 10 | import java.util.UUID; 11 | 12 | public class UserEntityBuilder extends BaseBuilder { 13 | 14 | public UserEntityBuilder() { 15 | super(UserEntity.class); 16 | } 17 | 18 | public UserEntityBuilder withValidUserFields() { 19 | PermissionEntity userGetPermission = new PermissionEntityBuilder() 20 | .withValidFields() 21 | .withName("user:get") 22 | .build(); 23 | 24 | RoleEntity userRole = new RoleEntityBuilder() 25 | .withValidFields() 26 | .withName("User") 27 | .withPermissions(List.of(userGetPermission)) 28 | .build(); 29 | 30 | return this 31 | .withId(UUID.randomUUID().toString()) 32 | .withEmail("userexample@example.com") 33 | .withPassword("userpassword") 34 | .withFirstName("User") 35 | .withLastName("Userlastname") 36 | .withPhoneNumber("123456789011") 37 | .withUserStatus(UserStatus.ACTIVE) 38 | .withRoles(List.of(userRole)); 39 | } 40 | 41 | public UserEntityBuilder withValidAdminFields() { 42 | PermissionEntity createPermission = new PermissionEntityBuilder() 43 | .withValidFields() 44 | .withName("admin:create") 45 | .build(); 46 | 47 | PermissionEntity getPermission = new PermissionEntityBuilder() 48 | .withValidFields() 49 | .withName("admin:get") 50 | .build(); 51 | 52 | PermissionEntity updatePermission = new PermissionEntityBuilder() 53 | .withValidFields() 54 | .withName("admin:update") 55 | .build(); 56 | 57 | PermissionEntity deletePermission = new PermissionEntityBuilder() 58 | .withValidFields() 59 | .withName("admin:delete") 60 | .build(); 61 | 62 | RoleEntity adminRole = new RoleEntityBuilder() 63 | .withValidFields() 64 | .withName("Admin") 65 | .withPermissions(List.of(createPermission, getPermission, updatePermission, deletePermission)) 66 | .build(); 67 | 68 | return this 69 | .withId(UUID.randomUUID().toString()) 70 | .withEmail("adminexample@example.com") 71 | .withPassword("adminpassword") 72 | .withFirstName("Admin") 73 | .withLastName("Adminlastname") 74 | .withPhoneNumber("1234567890112") 75 | .withUserStatus(UserStatus.ACTIVE) 76 | .withRoles(List.of(adminRole)); 77 | } 78 | 79 | 80 | public UserEntityBuilder withId(String id) { 81 | data.setId(id); 82 | return this; 83 | } 84 | 85 | public UserEntityBuilder withEmail(String email) { 86 | data.setEmail(email); 87 | return this; 88 | } 89 | 90 | public UserEntityBuilder withPassword(String password) { 91 | data.setPassword(password); 92 | return this; 93 | } 94 | 95 | public UserEntityBuilder withFirstName(String firstName) { 96 | data.setFirstName(firstName); 97 | return this; 98 | } 99 | 100 | public UserEntityBuilder withLastName(String lastName) { 101 | data.setLastName(lastName); 102 | return this; 103 | } 104 | 105 | public UserEntityBuilder withPhoneNumber(String phoneNumber) { 106 | data.setPhoneNumber(phoneNumber); 107 | return this; 108 | } 109 | 110 | public UserEntityBuilder withUserStatus(UserStatus userStatus) { 111 | data.setUserStatus(userStatus); 112 | return this; 113 | } 114 | 115 | public UserEntityBuilder withRoles(List roles) { 116 | data.setRoles(roles); 117 | return this; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/service/impl/LoginServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.exception.PasswordNotValidException; 4 | import com.security.rolepermissionexample.auth.exception.UserNotFoundException; 5 | import com.security.rolepermissionexample.auth.model.Token; 6 | import com.security.rolepermissionexample.auth.model.dto.request.LoginRequest; 7 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 8 | import com.security.rolepermissionexample.auth.repository.UserRepository; 9 | import com.security.rolepermissionexample.auth.service.TokenService; 10 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 11 | import com.security.rolepermissionexample.builder.UserEntityBuilder; 12 | import org.junit.jupiter.api.Test; 13 | import org.mockito.InjectMocks; 14 | import org.mockito.Mock; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | 17 | import java.util.Optional; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | import static org.mockito.Mockito.*; 21 | 22 | class LoginServiceImplTest extends AbstractBaseServiceTest { 23 | 24 | @InjectMocks 25 | private LoginServiceImpl loginService; 26 | 27 | @Mock 28 | private UserRepository userRepository; 29 | 30 | @Mock 31 | private PasswordEncoder passwordEncoder; 32 | 33 | @Mock 34 | private TokenService tokenService; 35 | 36 | @Test 37 | void login_ValidCredentials_ReturnsToken() { 38 | 39 | // Given 40 | final LoginRequest loginRequest = LoginRequest.builder() 41 | .email("test@example.com") 42 | .password("password123") 43 | .build(); 44 | 45 | final UserEntity adminEntity = new UserEntityBuilder().withValidAdminFields().build(); 46 | 47 | final Token expectedToken = Token.builder() 48 | .accessToken("mockAccessToken") 49 | .accessTokenExpiresAt(123456789L) 50 | .refreshToken("mockRefreshToken") 51 | .build(); 52 | 53 | // When 54 | when(userRepository.findUserEntityByEmail(loginRequest.getEmail())) 55 | .thenReturn(Optional.of(adminEntity)); 56 | 57 | when(passwordEncoder.matches(loginRequest.getPassword(), adminEntity.getPassword())) 58 | .thenReturn(true); 59 | 60 | when(tokenService.generateToken(adminEntity.getClaims())).thenReturn(expectedToken); 61 | 62 | Token actualToken = loginService.login(loginRequest); 63 | 64 | // Then 65 | assertEquals(expectedToken.getAccessToken(), actualToken.getAccessToken()); 66 | assertEquals(expectedToken.getRefreshToken(), actualToken.getRefreshToken()); 67 | assertEquals(expectedToken.getAccessTokenExpiresAt(), actualToken.getAccessTokenExpiresAt()); 68 | 69 | // Verify 70 | verify(userRepository).findUserEntityByEmail(loginRequest.getEmail()); 71 | verify(passwordEncoder).matches(loginRequest.getPassword(), adminEntity.getPassword()); 72 | verify(tokenService).generateToken(adminEntity.getClaims()); 73 | 74 | } 75 | 76 | @Test 77 | void login_InvalidEmail_ThrowsAdminNotFoundException() { 78 | 79 | // Given 80 | final LoginRequest loginRequest = LoginRequest.builder() 81 | .email("nonexistent@example.com") 82 | .password("password123") 83 | .build(); 84 | 85 | // When 86 | when(userRepository.findUserEntityByEmail(loginRequest.getEmail())) 87 | .thenReturn(Optional.empty()); 88 | 89 | // Then 90 | UserNotFoundException exception = assertThrows(UserNotFoundException.class, 91 | () -> loginService.login(loginRequest)); 92 | 93 | assertEquals("User not found!\n " + loginRequest.getEmail(), exception.getMessage()); 94 | 95 | // Verify 96 | verify(userRepository).findUserEntityByEmail(loginRequest.getEmail()); 97 | verifyNoInteractions(passwordEncoder, tokenService); 98 | 99 | } 100 | 101 | @Test 102 | void login_InvalidPassword_ThrowsPasswordNotValidException() { 103 | 104 | // Given 105 | final LoginRequest loginRequest = LoginRequest.builder() 106 | .email("test@example.com") 107 | .password("invalidPassword") 108 | .build(); 109 | 110 | final UserEntity adminEntity = UserEntity.builder() 111 | .email(loginRequest.getEmail()) 112 | .password("encodedPassword") 113 | .build(); 114 | 115 | // When 116 | when(userRepository.findUserEntityByEmail(loginRequest.getEmail())) 117 | .thenReturn(Optional.of(adminEntity)); 118 | 119 | when(passwordEncoder.matches(loginRequest.getPassword(), adminEntity.getPassword())) 120 | .thenReturn(false); 121 | 122 | // Then 123 | PasswordNotValidException exception = assertThrows(PasswordNotValidException.class, 124 | () -> loginService.login(loginRequest)); 125 | 126 | assertNotNull(exception); 127 | 128 | // Verify 129 | verify(userRepository).findUserEntityByEmail(loginRequest.getEmail()); 130 | verify(passwordEncoder).matches(loginRequest.getPassword(), adminEntity.getPassword()); 131 | verifyNoInteractions(tokenService); 132 | 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /src/main/java/com/security/rolepermissionexample/auth/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.config; 2 | 3 | import com.security.rolepermissionexample.auth.filter.CustomBearerTokenAuthenticationFilter; 4 | import com.security.rolepermissionexample.auth.security.CustomAuthenticationEntryPoint; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.http.HttpMethod; 9 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 13 | import org.springframework.security.config.http.SessionCreationPolicy; 14 | import org.springframework.security.core.session.SessionRegistryImpl; 15 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 16 | import org.springframework.security.crypto.password.PasswordEncoder; 17 | import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; 18 | import org.springframework.security.web.SecurityFilterChain; 19 | import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; 20 | import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; 21 | import org.springframework.web.cors.CorsConfiguration; 22 | import org.springframework.web.cors.CorsConfigurationSource; 23 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * Security configuration class named {@link SecurityConfig} for setting up authentication and authorization in the application. 29 | */ 30 | @Configuration 31 | @EnableWebSecurity 32 | @RequiredArgsConstructor 33 | @EnableMethodSecurity 34 | public class SecurityConfig { 35 | 36 | /** 37 | * Defines the session authentication strategy to be used. 38 | * 39 | * @return a {@link SessionAuthenticationStrategy} for registering session authentication. 40 | */ 41 | @Bean 42 | protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { 43 | return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); 44 | } 45 | 46 | /** 47 | * Configures the security filter chain for the application. 48 | * 49 | * @param httpSecurity the {@link HttpSecurity} object to configure. 50 | * @param customBearerTokenAuthenticationFilter the custom filter for bearer token authentication. 51 | * @param customAuthenticationEntryPoint the custom authentication entry point. 52 | * @return a {@link SecurityFilterChain} defining the security configuration. 53 | * @throws Exception if an error occurs while configuring security. 54 | */ 55 | @Bean 56 | public SecurityFilterChain filterChain( 57 | final HttpSecurity httpSecurity, 58 | final CustomBearerTokenAuthenticationFilter customBearerTokenAuthenticationFilter, 59 | final CustomAuthenticationEntryPoint customAuthenticationEntryPoint 60 | ) throws Exception { 61 | 62 | httpSecurity 63 | .exceptionHandling(customizer -> customizer.authenticationEntryPoint(customAuthenticationEntryPoint)) 64 | .cors(customizer -> customizer.configurationSource(corsConfigurationSource())) 65 | .csrf(AbstractHttpConfigurer::disable) 66 | .authorizeHttpRequests(customizer -> customizer 67 | .requestMatchers(HttpMethod.POST, "/api/v1/authentication/**").permitAll() 68 | .requestMatchers( 69 | "/swagger-ui/**", 70 | "/swagger-ui.html", 71 | "/v2/api-docs/**", 72 | "/v3/api-docs/**" 73 | ).permitAll() 74 | .anyRequest().authenticated() 75 | ) 76 | .sessionManagement(customizer -> customizer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 77 | .addFilterBefore(customBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class); 78 | 79 | return httpSecurity.build(); 80 | } 81 | 82 | /** 83 | * Configures CORS settings for the application. 84 | * 85 | * @return a {@link CorsConfigurationSource} defining the CORS configuration. 86 | */ 87 | private CorsConfigurationSource corsConfigurationSource() { 88 | CorsConfiguration configuration = new CorsConfiguration(); 89 | configuration.setAllowedOrigins(List.of("*")); 90 | configuration.setAllowedMethods(List.of("*")); 91 | configuration.setAllowedHeaders(List.of("*")); 92 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 93 | source.registerCorsConfiguration("/**", configuration); 94 | return source; 95 | } 96 | 97 | /** 98 | * Defines the password encoder to be used for encoding passwords. 99 | * 100 | * @return a {@link PasswordEncoder} using BCrypt hashing algorithm. 101 | */ 102 | @Bean 103 | public PasswordEncoder passwordEncoder() { 104 | return new BCryptPasswordEncoder(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/auth/service/impl/RegisterServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.auth.service.impl; 2 | 3 | import com.security.rolepermissionexample.auth.exception.UserAlreadyExistException; 4 | import com.security.rolepermissionexample.auth.model.User; 5 | import com.security.rolepermissionexample.auth.model.dto.request.RegisterRequest; 6 | import com.security.rolepermissionexample.auth.model.entity.PermissionEntity; 7 | import com.security.rolepermissionexample.auth.model.entity.RoleEntity; 8 | import com.security.rolepermissionexample.auth.model.entity.UserEntity; 9 | import com.security.rolepermissionexample.auth.model.mapper.RegisterRequestToUserEntityMapper; 10 | import com.security.rolepermissionexample.auth.model.mapper.UserEntityToUserMapper; 11 | import com.security.rolepermissionexample.auth.repository.PermissionRepository; 12 | import com.security.rolepermissionexample.auth.repository.RoleRepository; 13 | import com.security.rolepermissionexample.auth.repository.UserRepository; 14 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 15 | import com.security.rolepermissionexample.builder.PermissionEntityBuilder; 16 | import com.security.rolepermissionexample.builder.RoleEntityBuilder; 17 | import org.junit.jupiter.api.Test; 18 | import org.mockito.InjectMocks; 19 | import org.mockito.Mock; 20 | import org.springframework.security.crypto.password.PasswordEncoder; 21 | 22 | import java.util.List; 23 | import java.util.Optional; 24 | import java.util.UUID; 25 | 26 | import static org.junit.jupiter.api.Assertions.*; 27 | import static org.mockito.ArgumentMatchers.any; 28 | import static org.mockito.Mockito.*; 29 | 30 | class RegisterServiceImplTest extends AbstractBaseServiceTest { 31 | 32 | @InjectMocks 33 | private RegisterServiceImpl registerService; 34 | 35 | @Mock 36 | private UserRepository userRepository; 37 | 38 | @Mock 39 | private PasswordEncoder passwordEncoder; 40 | 41 | @Mock 42 | private RoleRepository roleRepository; 43 | 44 | @Mock 45 | private PermissionRepository permissionRepository; 46 | 47 | private final RegisterRequestToUserEntityMapper registerRequestToUserEntityMapper = RegisterRequestToUserEntityMapper.initialize(); 48 | 49 | private final UserEntityToUserMapper userEntityToUserMapper = UserEntityToUserMapper.initialize(); 50 | 51 | 52 | @Test 53 | void givenAdminRegisterRequest_whenRegisterAdmin_thenReturnAdmin() { 54 | 55 | // Given 56 | final RegisterRequest request = RegisterRequest.builder() 57 | .email("admincreate@example.com") 58 | .password("password123") 59 | .firstName("admin firstName") 60 | .lastName("admin lastName") 61 | .phoneNumber("123456789010") 62 | .role(List.of("ADMIN")) 63 | .permissions(List.of("admin:create")) 64 | .build(); 65 | 66 | final String encodedPassword = "encodedPassword"; 67 | 68 | final UserEntity userEntity = registerRequestToUserEntityMapper.mapForSaving(request); 69 | 70 | final User expected = userEntityToUserMapper.map(userEntity); 71 | 72 | // Mocking roleRepository 73 | RoleEntity adminRole = new RoleEntityBuilder() 74 | .withId(UUID.randomUUID().toString()) 75 | .withName("ADMIN") 76 | .build(); 77 | 78 | // Mocking permissionRepository 79 | PermissionEntity adminCreatePermission = new PermissionEntityBuilder() 80 | .withId(UUID.randomUUID().toString()) 81 | .withName("admin:create") 82 | .build(); 83 | adminCreatePermission.setId(UUID.randomUUID().toString()); 84 | 85 | // When 86 | when(userRepository.existsUserEntityByEmail(request.getEmail())).thenReturn(false); 87 | when(passwordEncoder.encode(request.getPassword())).thenReturn(encodedPassword); 88 | when(roleRepository.findByName("ADMIN")).thenReturn(Optional.of(adminRole)); 89 | when(permissionRepository.findByName("admin:create")).thenReturn(Optional.of(adminCreatePermission)); 90 | when(userRepository.save(any(UserEntity.class))).thenReturn(userEntity); 91 | 92 | // Then 93 | User result = registerService.registerUser(request); 94 | 95 | assertEquals(expected.getId(), result.getId()); 96 | assertEquals(expected.getEmail(), result.getEmail()); 97 | assertEquals(expected.getPhoneNumber(), result.getPhoneNumber()); 98 | assertEquals(expected.getFirstName(), result.getFirstName()); 99 | assertEquals(expected.getLastName(), result.getLastName()); 100 | 101 | // Verify 102 | verify(userRepository).save(any(UserEntity.class)); 103 | 104 | } 105 | 106 | @Test 107 | void givenAdminRegisterRequest_whenEmailAlreadyExists_thenThrowAdminAlreadyExistException() { 108 | 109 | // Given 110 | final RegisterRequest request = RegisterRequest.builder() 111 | .email("admincreate@example.com") 112 | .password("password123") 113 | .firstName("admin firstName") 114 | .lastName("admin lastName") 115 | .phoneNumber("123456789010") 116 | .role(List.of("ADMIN")) 117 | .permissions(List.of("admin:create")) 118 | .build(); 119 | 120 | // When 121 | when(userRepository.existsUserEntityByEmail(request.getEmail())).thenReturn(true); 122 | 123 | // Then 124 | assertThrows(UserAlreadyExistException.class, () -> registerService.registerUser(request)); 125 | 126 | // Verify 127 | verify(userRepository, never()).save(any(UserEntity.class)); 128 | 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/service/impl/ProductReadServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 4 | import com.security.rolepermissionexample.common.model.CustomPage; 5 | import com.security.rolepermissionexample.common.model.CustomPaging; 6 | import com.security.rolepermissionexample.product.exception.ProductNotFoundException; 7 | import com.security.rolepermissionexample.product.model.Product; 8 | import com.security.rolepermissionexample.product.model.dto.request.ProductPagingRequest; 9 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 10 | import com.security.rolepermissionexample.product.model.mapper.ListProductEntityToListProductMapper; 11 | import com.security.rolepermissionexample.product.model.mapper.ProductEntityToProductMapper; 12 | import com.security.rolepermissionexample.product.repository.ProductRepository; 13 | import org.junit.jupiter.api.Test; 14 | import org.mockito.InjectMocks; 15 | import org.mockito.Mock; 16 | import org.springframework.data.domain.Page; 17 | import org.springframework.data.domain.PageImpl; 18 | import org.springframework.data.domain.Pageable; 19 | 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.Optional; 23 | 24 | import static org.junit.jupiter.api.Assertions.*; 25 | import static org.mockito.Mockito.*; 26 | 27 | class ProductReadServiceImplTest extends AbstractBaseServiceTest { 28 | 29 | @InjectMocks 30 | private ProductReadServiceImpl productReadService; 31 | 32 | @Mock 33 | private ProductRepository productRepository; 34 | 35 | private final ProductEntityToProductMapper productEntityToProductMapper = ProductEntityToProductMapper.initialize(); 36 | 37 | private final ListProductEntityToListProductMapper listProductEntityToListProductMapper = 38 | ListProductEntityToListProductMapper.initialize(); 39 | 40 | @Test 41 | void givenProductEntity_whenFindProductById_thenReturnProduct() { 42 | 43 | // Given 44 | String productId = "1"; 45 | ProductEntity productEntity = new ProductEntity(); 46 | productEntity.setId(productId); 47 | 48 | Product expected = productEntityToProductMapper.map(productEntity); 49 | 50 | // When 51 | when(productRepository.findById(productId)).thenReturn(Optional.of(productEntity)); 52 | 53 | // Then 54 | Product result = productReadService.getProductById(productId); 55 | 56 | assertNotNull(result); 57 | assertEquals(expected.getId(), result.getId()); 58 | 59 | // Verify 60 | verify(productRepository, times(1)).findById(productId); 61 | 62 | } 63 | 64 | @Test 65 | void givenProductEntity_whenProductNotFound_thenThrowProductNotFoundException() { 66 | 67 | // Given 68 | String productId = "1"; 69 | 70 | // When 71 | when(productRepository.findById(productId)).thenReturn(Optional.empty()); 72 | 73 | // Then 74 | assertThrows(ProductNotFoundException.class, () -> productReadService.getProductById(productId)); 75 | 76 | // Verify 77 | verify(productRepository, times(1)).findById(productId); 78 | 79 | } 80 | 81 | @Test 82 | void givenProductPagingRequest_WhenProductPageList_ThenReturnCustomPageProductList() { 83 | 84 | // Given 85 | ProductPagingRequest pagingRequest = ProductPagingRequest.builder() 86 | .pagination( 87 | CustomPaging.builder() 88 | .pageSize(1) 89 | .pageNumber(1) 90 | .build() 91 | ).build(); 92 | 93 | Page productEntityPage = new PageImpl<>(Collections.singletonList(new ProductEntity())); 94 | 95 | List products = listProductEntityToListProductMapper.toProductList(productEntityPage.getContent()); 96 | 97 | CustomPage expected = CustomPage.of(products, productEntityPage); 98 | 99 | // When 100 | when(productRepository.findAll(any(Pageable.class))).thenReturn(productEntityPage); 101 | 102 | // Then 103 | CustomPage result = productReadService.getProducts(pagingRequest); 104 | 105 | assertNotNull(result); 106 | assertFalse(result.getContent().isEmpty()); 107 | assertEquals(expected.getPageNumber(), result.getPageNumber()); 108 | assertEquals(expected.getContent().get(0).getId(), result.getContent().get(0).getId()); 109 | assertEquals(expected.getTotalPageCount(), result.getTotalPageCount()); 110 | assertEquals(expected.getTotalElementCount(), result.getTotalElementCount()); 111 | 112 | // Verify 113 | verify(productRepository, times(1)).findAll(any(Pageable.class)); 114 | 115 | } 116 | 117 | @Test 118 | void givenProductPagingRequest_WhenNoProductPageList_ThenThrowProductNotFoundException() { 119 | 120 | // Given 121 | ProductPagingRequest pagingRequest = ProductPagingRequest.builder() 122 | .pagination( 123 | CustomPaging.builder() 124 | .pageSize(1) 125 | .pageNumber(1) 126 | .build() 127 | ).build(); 128 | 129 | Page productEntityPage = new PageImpl<>(Collections.emptyList()); 130 | 131 | // When 132 | when(productRepository.findAll(any(Pageable.class))).thenReturn(productEntityPage); 133 | 134 | // Then 135 | assertThrows(ProductNotFoundException.class, () -> productReadService.getProducts(pagingRequest)); 136 | 137 | // Verify 138 | verify(productRepository, times(1)).findAll(any(Pageable.class)); 139 | 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /src/test/java/com/security/rolepermissionexample/product/service/impl/ProductUpdateServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.security.rolepermissionexample.product.service.impl; 2 | 3 | import com.security.rolepermissionexample.base.AbstractBaseServiceTest; 4 | import com.security.rolepermissionexample.product.exception.ProductAlreadyExistException; 5 | import com.security.rolepermissionexample.product.exception.ProductNotFoundException; 6 | import com.security.rolepermissionexample.product.model.Product; 7 | import com.security.rolepermissionexample.product.model.dto.request.ProductUpdateRequest; 8 | import com.security.rolepermissionexample.product.model.entity.ProductEntity; 9 | import com.security.rolepermissionexample.product.model.mapper.ProductEntityToProductMapper; 10 | import com.security.rolepermissionexample.product.model.mapper.ProductUpdateRequestToProductEntityMapper; 11 | import com.security.rolepermissionexample.product.repository.ProductRepository; 12 | import org.junit.jupiter.api.Test; 13 | import org.mockito.InjectMocks; 14 | import org.mockito.Mock; 15 | 16 | import java.math.BigDecimal; 17 | import java.util.Optional; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | import static org.mockito.ArgumentMatchers.any; 21 | import static org.mockito.Mockito.*; 22 | 23 | class ProductUpdateServiceImplTest extends AbstractBaseServiceTest { 24 | 25 | @InjectMocks 26 | private ProductUpdateServiceImpl productUpdateService; 27 | 28 | @Mock 29 | private ProductRepository productRepository; 30 | 31 | private ProductUpdateRequestToProductEntityMapper productUpdateRequestToProductEntityMapper = 32 | ProductUpdateRequestToProductEntityMapper.initialize(); 33 | 34 | private ProductEntityToProductMapper productEntityToProductMapper = 35 | ProductEntityToProductMapper.initialize(); 36 | 37 | @Test 38 | void givenProductUpdateRequest_whenProductUpdated_thenReturnProduct() { 39 | 40 | // Given 41 | String productId = "1"; 42 | String newProductName = "New Product Name"; 43 | 44 | ProductUpdateRequest productUpdateRequest = ProductUpdateRequest.builder() 45 | .name(newProductName) 46 | .amount(BigDecimal.valueOf(5)) 47 | .unitPrice(BigDecimal.valueOf(12)) 48 | .build(); 49 | 50 | ProductEntity existingProductEntity = ProductEntity.builder() 51 | .id(productId) 52 | .name(productUpdateRequest.getName()) 53 | .unitPrice(productUpdateRequest.getUnitPrice()) 54 | .amount(productUpdateRequest.getAmount()) 55 | .build(); 56 | 57 | productUpdateRequestToProductEntityMapper.mapForUpdating(existingProductEntity,productUpdateRequest); 58 | 59 | Product expected = productEntityToProductMapper.map(existingProductEntity); 60 | 61 | // When 62 | when(productRepository.findById(productId)).thenReturn(Optional.of(existingProductEntity)); 63 | when(productRepository.existsProductEntityByName(newProductName)).thenReturn(false); 64 | when(productRepository.save(any(ProductEntity.class))).thenReturn(existingProductEntity); 65 | 66 | // Then 67 | Product updatedProduct = productUpdateService.updateProductById(productId, productUpdateRequest); 68 | 69 | // Then 70 | assertNotNull(updatedProduct); 71 | assertEquals(expected.getId(), updatedProduct.getId()); 72 | assertEquals(expected.getName(), updatedProduct.getName()); 73 | assertEquals(expected.getAmount(), updatedProduct.getAmount()); 74 | assertEquals(expected.getUnitPrice(), updatedProduct.getUnitPrice()); 75 | 76 | // Verify 77 | verify(productRepository, times(1)).findById(productId); 78 | verify(productRepository, times(1)).existsProductEntityByName(newProductName); 79 | verify(productRepository, times(1)).save(any(ProductEntity.class)); 80 | 81 | } 82 | 83 | @Test 84 | void givenProductUpdateRequest_whenProductNotFound_thenThrowProductNotFoundException() { 85 | 86 | // Given 87 | String productId = "1"; 88 | ProductUpdateRequest productUpdateRequest = new ProductUpdateRequest(); 89 | 90 | when(productRepository.findById(productId)).thenReturn(Optional.empty()); 91 | 92 | // When/Then 93 | assertThrows(ProductNotFoundException.class, () -> productUpdateService.updateProductById(productId, productUpdateRequest)); 94 | 95 | // Verify 96 | verify(productRepository, times(1)).findById(productId); 97 | verify(productRepository, never()).existsProductEntityByName(anyString()); 98 | verify(productRepository, never()).save(any(ProductEntity.class)); 99 | 100 | } 101 | 102 | @Test 103 | void givenProductUpdateRequest_whenProductAlreadyExist_thenThrowProductAlreadyExistException() { 104 | 105 | // Given 106 | String productId = "1"; 107 | String existingProductName = "Existing Product"; 108 | ProductUpdateRequest productUpdateRequest = new ProductUpdateRequest(); 109 | productUpdateRequest.setName(existingProductName); 110 | 111 | ProductEntity existingProductEntity = new ProductEntity(); 112 | existingProductEntity.setId(productId); 113 | existingProductEntity.setName(existingProductName); 114 | 115 | when(productRepository.existsProductEntityByName(existingProductName)).thenReturn(true); 116 | 117 | // When/Then 118 | assertThrows(ProductAlreadyExistException.class, () -> productUpdateService.updateProductById(productId, productUpdateRequest)); 119 | 120 | // Verify 121 | verify(productRepository, times(1)).existsProductEntityByName(existingProductName); 122 | verify(productRepository, never()).findById(productId); 123 | verify(productRepository, never()).save(any(ProductEntity.class)); 124 | 125 | } 126 | 127 | } --------------------------------------------------------------------------------