├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Dockerfile ├── README.md ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── postman_collection └── Spring Security Example.postman_collection.json ├── screenshots ├── spring_1.PNG ├── spring_10.PNG ├── spring_11.PNG ├── spring_12.PNG ├── spring_13.PNG ├── spring_14.PNG ├── spring_15.PNG ├── spring_16.PNG ├── spring_2.PNG ├── spring_3.PNG ├── spring_4.PNG ├── spring_5.PNG ├── spring_6.PNG ├── spring_7.PNG ├── spring_8.PNG ├── spring_9.PNG └── spring_boot_security_example_jwt_implementation.png └── src ├── main ├── java │ └── com │ │ └── springboot │ │ └── springbootsecurity │ │ ├── SpringBootSecurityApplication.java │ │ ├── admin │ │ ├── controller │ │ │ └── AdminAuthController.java │ │ ├── exception │ │ │ ├── AdminAlreadyExistException.java │ │ │ └── AdminNotFoundException.java │ │ ├── model │ │ │ ├── Admin.java │ │ │ ├── dto │ │ │ │ └── request │ │ │ │ │ └── AdminRegisterRequest.java │ │ │ ├── entity │ │ │ │ └── AdminEntity.java │ │ │ └── mapper │ │ │ │ ├── AdminEntityToAdminMapper.java │ │ │ │ ├── AdminRegisterRequestToAdminEntityMapper.java │ │ │ │ └── AdminToAdminEntityMapper.java │ │ ├── repository │ │ │ └── AdminRepository.java │ │ └── service │ │ │ ├── AdminLoginService.java │ │ │ ├── AdminLogoutService.java │ │ │ ├── AdminRefreshTokenService.java │ │ │ ├── AdminRegisterService.java │ │ │ └── impl │ │ │ ├── AdminLoginServiceImpl.java │ │ │ ├── AdminLogoutServiceImpl.java │ │ │ ├── AdminRefreshTokenServiceImpl.java │ │ │ └── AdminRegisterServiceImpl.java │ │ ├── auth │ │ ├── config │ │ │ ├── SecurityConfig.java │ │ │ └── TokenConfigurationParameter.java │ │ ├── exception │ │ │ ├── PasswordNotValidException.java │ │ │ ├── TokenAlreadyInvalidatedException.java │ │ │ └── UserStatusNotValidException.java │ │ ├── filter │ │ │ └── CustomBearerTokenAuthenticationFilter.java │ │ ├── model │ │ │ ├── Identity.java │ │ │ ├── Token.java │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── LoginRequest.java │ │ │ │ │ ├── TokenInvalidateRequest.java │ │ │ │ │ └── TokenRefreshRequest.java │ │ │ │ └── response │ │ │ │ │ └── TokenResponse.java │ │ │ ├── entity │ │ │ │ └── InvalidTokenEntity.java │ │ │ ├── enums │ │ │ │ ├── ConfigurationParameter.java │ │ │ │ ├── TokenClaims.java │ │ │ │ ├── TokenType.java │ │ │ │ ├── UserStatus.java │ │ │ │ └── UserType.java │ │ │ └── mapper │ │ │ │ └── TokenToTokenResponseMapper.java │ │ ├── repository │ │ │ └── InvalidTokenRepository.java │ │ ├── security │ │ │ └── CustomAuthenticationEntryPoint.java │ │ ├── service │ │ │ ├── InvalidTokenService.java │ │ │ ├── TokenService.java │ │ │ └── impl │ │ │ │ ├── InvalidTokenServiceImpl.java │ │ │ │ └── TokenServiceImpl.java │ │ └── utils │ │ │ └── KeyConverter.java │ │ ├── common │ │ ├── exception │ │ │ └── GlobalExceptionHandler.java │ │ └── model │ │ │ ├── BaseDomainModel.java │ │ │ ├── CustomError.java │ │ │ ├── CustomPage.java │ │ │ ├── CustomPaging.java │ │ │ ├── dto │ │ │ ├── request │ │ │ │ └── CustomPagingRequest.java │ │ │ └── response │ │ │ │ ├── CustomPagingResponse.java │ │ │ │ └── CustomResponse.java │ │ │ ├── entity │ │ │ └── BaseEntity.java │ │ │ └── mapper │ │ │ └── BaseMapper.java │ │ ├── product │ │ ├── controller │ │ │ └── ProductController.java │ │ ├── exception │ │ │ ├── ProductAlreadyExistException.java │ │ │ └── ProductNotFoundException.java │ │ ├── model │ │ │ ├── Product.java │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── ProductCreateRequest.java │ │ │ │ │ ├── ProductPagingRequest.java │ │ │ │ │ └── ProductUpdateRequest.java │ │ │ │ └── response │ │ │ │ │ └── ProductResponse.java │ │ │ ├── entity │ │ │ │ └── ProductEntity.java │ │ │ └── mapper │ │ │ │ ├── CustomPageToCustomPagingResponseMapper.java │ │ │ │ ├── ListProductEntityToListProductMapper.java │ │ │ │ ├── ProductCreateRequestToProductEntityMapper.java │ │ │ │ ├── ProductEntityToProductMapper.java │ │ │ │ ├── ProductToProductEntityMapper.java │ │ │ │ ├── ProductToProductResponseMapper.java │ │ │ │ └── ProductUpdateRequestToProductEntityMapper.java │ │ ├── repository │ │ │ └── ProductRepository.java │ │ └── service │ │ │ ├── ProductCreateService.java │ │ │ ├── ProductDeleteService.java │ │ │ ├── ProductReadService.java │ │ │ ├── ProductUpdateService.java │ │ │ └── impl │ │ │ ├── ProductCreateServiceImpl.java │ │ │ ├── ProductDeleteServiceImpl.java │ │ │ ├── ProductReadServiceImpl.java │ │ │ └── ProductUpdateServiceImpl.java │ │ └── user │ │ ├── controller │ │ └── UserAuthController.java │ │ ├── exception │ │ ├── UserAlreadyExistException.java │ │ └── UserNotFoundException.java │ │ ├── model │ │ ├── User.java │ │ ├── dto │ │ │ └── request │ │ │ │ └── UserRegisterRequest.java │ │ ├── entity │ │ │ └── UserEntity.java │ │ └── mapper │ │ │ ├── UserEntityToUserMapper.java │ │ │ ├── UserRegisterRequestToUserEntityMapper.java │ │ │ └── UserToUserEntityMapper.java │ │ ├── repository │ │ └── UserRepository.java │ │ └── service │ │ ├── UserLoginService.java │ │ ├── UserLogoutService.java │ │ ├── UserRefreshTokenService.java │ │ ├── UserRegisterService.java │ │ └── impl │ │ ├── UserLoginServiceImpl.java │ │ ├── UserLogoutServiceImpl.java │ │ ├── UserRefreshTokenServiceImpl.java │ │ └── UserRegisterServiceImpl.java └── resources │ └── application.yaml └── test └── java └── com └── springboot └── springbootsecurity ├── SpringBootSecurityApplicationTests.java ├── admin ├── controller │ └── AdminAuthControllerTest.java └── service │ └── impl │ ├── AdminLoginServiceImplTest.java │ ├── AdminLogoutServiceImplTest.java │ ├── AdminRefreshTokenServiceImplTest.java │ └── AdminRegisterServiceImplTest.java ├── base ├── AbstractBaseServiceTest.java ├── AbstractRestControllerTest.java └── AbstractTestContainerConfiguration.java ├── builder ├── AdminEntityBuilder.java ├── BaseBuilder.java ├── TokenBuilder.java └── UserEntityBuilder.java ├── product ├── controller │ └── ProductControllerTest.java └── service │ └── impl │ ├── ProductCreateServiceImplTest.java │ ├── ProductDeleteServiceImplTest.java │ ├── ProductReadServiceImplTest.java │ └── ProductUpdateServiceImplTest.java └── user ├── controller └── UserAuthControllerTest.java └── service └── impl ├── UserLoginServiceImplTest.java ├── UserLogoutServiceImplTest.java ├── UserRefreshTokenServiceImplTest.java └── UserRegisterServiceImplTest.java /.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 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build stage 2 | FROM maven:3.8.4-openjdk-17-slim 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 openjdk:17-jdk-slim 16 | 17 | # Set working springbootsecurity 18 | WORKDIR springbootsecurity 19 | 20 | # Copy the JAR file from the build stage 21 | COPY --from=build target/*.jar springbootsecurityexample.jar 22 | 23 | # Expose port 1223 24 | EXPOSE 1223 25 | 26 | # Set the entrypoint command for running the application 27 | ENTRYPOINT ["java", "-jar", "springbootsecurityexample.jar"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot with Spring Security through JWT Implementation 2 | 3 |

4 | Main Information 5 |

6 | 7 | ### 📖 Information 8 | 9 | 17 | 18 | ### Explore Rest APIs 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
MethodUrlDescriptionRequest BodyHeaderValid Path VariableNo Path Variable
POST/api/v1/authentication/admin/registerAdmin RegisterAdminRegisterRequest
POST/api/v1/authentication/admin/loginAdmin LoginLoginRequest
POST/api/v1/authentication/admin/refreshtokenAdmin Refresh TokenTokenRefreshRequest
POST/api/v1/authentication/admin/logoutAdmin LogoutTokenInvalidateRequest
POST/api/v1/authentication/user/registerUser RegisterUserRegisterRequest
POST/api/v1/authentication/user/loginUser LoginLoginRequest
POST/api/v1/authentication/user/refreshtokenUser Refresh TokenTokenRefreshRequest
POST/api/v1/authentication/user/logoutUser LogoutTokenInvalidateRequest
POST/api/v1/productsCreate ProductProductCreateRequest
GET/api/v1/products/{productId}Get Product By IdProductId
GET/api/v1/productsGet ProductsProductPagingRequest
PUT/api/v1/products/{productId}Update Product By IdProductUpdateRequestProductId
DELETE/api/v1/products/{productId}Delete Product By IdProductId
148 | 149 | 150 | ### Technologies 151 | 152 | --- 153 | - Java 17 154 | - Spring Boot 3.0 155 | - Restful API 156 | - Lombok 157 | - Maven 158 | - Junit5 159 | - Mockito 160 | - Integration Tests 161 | - Docker 162 | - Docker Compose 163 | 164 | 165 | ### Prerequisites 166 | 167 | #### Define Variable in .env file 168 | 169 | ``` 170 | DATABASE_USERNAME={DATABASE_USERNAME} 171 | DATABASE_PASSWORD={DATABASE_PASSWORD} 172 | ``` 173 | 174 | --- 175 | - Maven or Docker 176 | --- 177 | 178 | 179 | ### Docker Run 180 | The application can be built and run by the `Docker` engine. The `Dockerfile` has multistage build, so you do not need to build and run separately. 181 | 182 | Please follow directions shown below in order to build and run the application with Docker Compose file; 183 | 184 | ```sh 185 | $ cd springbootsecurity 186 | $ docker-compose up -d 187 | ``` 188 | 189 | If you change anything in the project and run it on Docker, you can also use this command shown below 190 | 191 | ```sh 192 | $ cd springbootsecurity 193 | $ docker-compose up --build 194 | ``` 195 | 196 | --- 197 | ### Maven Run 198 | To build and run the application with `Maven`, please follow the directions shown below; 199 | 200 | ```sh 201 | $ cd springbootsecurity 202 | $ mvn clean install 203 | $ mvn spring-boot:run 204 | ``` 205 | 206 | ### Screenshots 207 | 208 |
209 | Click here to show the screenshots of project 210 |

Figure 1

211 | 212 |

Figure 2

213 | 214 |

Figure 3

215 | 216 |

Figure 4

217 | 218 |

Figure 5

219 | 220 |

Figure 6

221 | 222 |

Figure 7

223 | 224 |

Figure 8

225 | 226 |

Figure 9

227 | 228 |

Figure 10

229 | 230 |

Figure 11

231 | 232 |

Figure 12

233 | 234 |

Figure 13

235 | 236 |

Figure 14

237 | 238 |

Figure 15

239 | 240 |

Figure 16

241 | 242 |
-------------------------------------------------------------------------------- /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: springbootsecurityexample 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 | - springbootsecurityexampleNetwork 23 | 24 | springbootsecurityexample: 25 | image: 'springbootsecurityexample:latest' 26 | build: 27 | context: . 28 | dockerfile: Dockerfile 29 | container_name: springbootsecurity 30 | restart: on-failure 31 | env_file: 32 | - .env # Use the .env file for environment variables 33 | ports: 34 | - "1223:1223" 35 | environment: 36 | - server.port=1223 37 | - spring.datasource.username=${DATABASE_USERNAME} 38 | - spring.datasource.password=${DATABASE_PASSWORD} 39 | - SECURITY_DB_IP=database 40 | - SECURITY_DB_PORT=3307 41 | - spring.datasource.url=jdbc:mysql://host.docker.internal:3307/springbootsecurityexample 42 | depends_on: 43 | - database 44 | networks: 45 | - springbootsecurityexampleNetwork 46 | 47 | networks: 48 | springbootsecurityexampleNetwork: -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.3 9 | 10 | 11 | com.springboot 12 | springbootsecurity 13 | 0.0.1-SNAPSHOT 14 | springbootsecurity 15 | Spring Security in Spring Boot 16 | 17 | 17 18 | 1.77 19 | 0.12.3 20 | 1.5.5.Final 21 | 0.2.0 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-jpa 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-devtools 38 | runtime 39 | true 40 | 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | true 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-test 51 | test 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-validation 57 | 58 | 59 | 60 | 61 | org.bouncycastle 62 | bcpkix-jdk18on 63 | ${bouncycastle.version} 64 | 65 | 66 | 67 | 68 | 69 | org.apache.commons 70 | commons-lang3 71 | 3.14.0 72 | 73 | 74 | 75 | 76 | com.mysql 77 | mysql-connector-j 78 | runtime 79 | 80 | 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-starter-security 85 | 86 | 87 | 88 | 89 | 90 | org.springframework.boot 91 | spring-boot-starter-oauth2-client 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-oauth2-resource-server 97 | 98 | 99 | 100 | 101 | 102 | io.jsonwebtoken 103 | jjwt-api 104 | ${jsonwebtoken.version} 105 | 106 | 107 | 108 | io.jsonwebtoken 109 | jjwt-impl 110 | ${jsonwebtoken.version} 111 | 112 | 113 | 114 | io.jsonwebtoken 115 | jjwt-jackson 116 | ${jsonwebtoken.version} 117 | 118 | 119 | 120 | 121 | org.mapstruct 122 | mapstruct 123 | ${mapstruct.version} 124 | 125 | 126 | 127 | org.testcontainers 128 | testcontainers 129 | test 130 | 131 | 132 | 133 | org.testcontainers 134 | junit-jupiter 135 | test 136 | 137 | 138 | 139 | org.testcontainers 140 | mysql 141 | test 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | org.springframework.boot 150 | spring-boot-maven-plugin 151 | 152 | 153 | 154 | org.projectlombok 155 | lombok 156 | 157 | 158 | 159 | 160 | 161 | org.liquibase 162 | liquibase-maven-plugin 163 | ${liquibase.version} 164 | 165 | 166 | org.springframework.boot 167 | spring-boot-maven-plugin 168 | 169 | 170 | 171 | org.projectlombok 172 | lombok 173 | 174 | 175 | 176 | 177 | 178 | org.apache.maven.plugins 179 | maven-compiler-plugin 180 | ${maven-compiler-plugin.version} 181 | 182 | 17 183 | 17 184 | 185 | 186 | org.mapstruct 187 | mapstruct-processor 188 | ${mapstruct.version} 189 | 190 | 191 | org.projectlombok 192 | lombok 193 | ${lombok.version} 194 | 195 | 196 | org.projectlombok 197 | lombok-mapstruct-binding 198 | ${lombok-mapstruct-binding.version} 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /screenshots/spring_1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_1.PNG -------------------------------------------------------------------------------- /screenshots/spring_10.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_10.PNG -------------------------------------------------------------------------------- /screenshots/spring_11.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_11.PNG -------------------------------------------------------------------------------- /screenshots/spring_12.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_12.PNG -------------------------------------------------------------------------------- /screenshots/spring_13.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_13.PNG -------------------------------------------------------------------------------- /screenshots/spring_14.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_14.PNG -------------------------------------------------------------------------------- /screenshots/spring_15.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_15.PNG -------------------------------------------------------------------------------- /screenshots/spring_16.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_16.PNG -------------------------------------------------------------------------------- /screenshots/spring_2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_2.PNG -------------------------------------------------------------------------------- /screenshots/spring_3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_3.PNG -------------------------------------------------------------------------------- /screenshots/spring_4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_4.PNG -------------------------------------------------------------------------------- /screenshots/spring_5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_5.PNG -------------------------------------------------------------------------------- /screenshots/spring_6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_6.PNG -------------------------------------------------------------------------------- /screenshots/spring_7.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_7.PNG -------------------------------------------------------------------------------- /screenshots/spring_8.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_8.PNG -------------------------------------------------------------------------------- /screenshots/spring_9.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_9.PNG -------------------------------------------------------------------------------- /screenshots/spring_boot_security_example_jwt_implementation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rapter1990/springbootsecurity/2ec9bea644a6f2885962e25f5362b5b3e465e955/screenshots/spring_boot_security_example_jwt_implementation.png -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/SpringBootSecurityApplication.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootSecurityApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootSecurityApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/controller/AdminAuthController.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.controller; 2 | 3 | import com.springboot.springbootsecurity.admin.model.dto.request.AdminRegisterRequest; 4 | import com.springboot.springbootsecurity.admin.service.AdminLoginService; 5 | import com.springboot.springbootsecurity.admin.service.AdminLogoutService; 6 | import com.springboot.springbootsecurity.admin.service.AdminRefreshTokenService; 7 | import com.springboot.springbootsecurity.admin.service.AdminRegisterService; 8 | import com.springboot.springbootsecurity.auth.model.Token; 9 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 10 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 11 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 12 | import com.springboot.springbootsecurity.auth.model.dto.response.TokenResponse; 13 | import com.springboot.springbootsecurity.auth.model.mapper.TokenToTokenResponseMapper; 14 | import com.springboot.springbootsecurity.common.model.dto.response.CustomResponse; 15 | import jakarta.validation.Valid; 16 | import lombok.RequiredArgsConstructor; 17 | import org.springframework.security.access.prepost.PreAuthorize; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | 23 | @RestController 24 | @RequestMapping("/api/v1/authentication/admin") 25 | @RequiredArgsConstructor 26 | public class AdminAuthController { 27 | 28 | private final AdminRegisterService adminRegisterService; 29 | private final AdminLoginService adminLoginService; 30 | private final AdminRefreshTokenService adminRefreshTokenService; 31 | private final AdminLogoutService adminLogoutService; 32 | 33 | private TokenToTokenResponseMapper tokenToTokenResponseMapper = TokenToTokenResponseMapper.initialize(); 34 | 35 | @PostMapping("/register") 36 | public CustomResponse registerAdmin(@RequestBody @Valid final AdminRegisterRequest adminRegisterRequest) { 37 | adminRegisterService.registerAdmin(adminRegisterRequest); 38 | return CustomResponse.SUCCESS; 39 | } 40 | 41 | @PostMapping("/login") 42 | public CustomResponse loginAdmin(@RequestBody @Valid final LoginRequest loginRequest) { 43 | final Token token = adminLoginService.login(loginRequest); 44 | final TokenResponse tokenResponse = tokenToTokenResponseMapper.map(token); 45 | return CustomResponse.successOf(tokenResponse); 46 | } 47 | 48 | @PostMapping("/refresh-token") 49 | public CustomResponse refreshToken(@RequestBody @Valid final TokenRefreshRequest tokenRefreshRequest) { 50 | final Token token = adminRefreshTokenService.refreshToken(tokenRefreshRequest); 51 | final TokenResponse tokenResponse = tokenToTokenResponseMapper.map(token); 52 | return CustomResponse.successOf(tokenResponse); 53 | } 54 | 55 | @PostMapping("/logout") 56 | @PreAuthorize("hasAnyAuthority('ADMIN')") 57 | public CustomResponse logout(@RequestBody @Valid final TokenInvalidateRequest tokenInvalidateRequest) { 58 | adminLogoutService.logout(tokenInvalidateRequest); 59 | return CustomResponse.SUCCESS; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/exception/AdminAlreadyExistException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class AdminAlreadyExistException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = -8596955790221338007L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | Admin already exist! 12 | """; 13 | 14 | public AdminAlreadyExistException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public AdminAlreadyExistException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/exception/AdminNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class AdminNotFoundException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = -6226206922525682121L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | Admin not found! 12 | """; 13 | 14 | public AdminNotFoundException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public AdminNotFoundException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/model/Admin.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.model; 2 | 3 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 4 | import com.springboot.springbootsecurity.auth.model.enums.UserType; 5 | import com.springboot.springbootsecurity.common.model.BaseDomainModel; 6 | import lombok.*; 7 | import lombok.experimental.SuperBuilder; 8 | 9 | @Getter 10 | @Setter 11 | @SuperBuilder 12 | @EqualsAndHashCode(callSuper = true) 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class Admin extends BaseDomainModel { 16 | 17 | private String id; 18 | private String email; 19 | private String password; 20 | private String firstName; 21 | private String lastName; 22 | private String phoneNumber; 23 | 24 | @Builder.Default 25 | private UserType userType = UserType.ADMIN; 26 | 27 | @Builder.Default 28 | private UserStatus userStatus = UserStatus.ACTIVE; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/model/dto/request/AdminRegisterRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.model.dto.request; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | import lombok.*; 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | public class AdminRegisterRequest { 14 | 15 | @Email(message = "Please enter valid e-mail address") 16 | @Size(min = 7, message = "Minimum e-mail length is 7 characters.") 17 | private String email; 18 | 19 | @Size(min = 8) 20 | private String password; 21 | 22 | @NotBlank(message = "First name can't be blank.") 23 | private String firstName; 24 | 25 | @NotBlank(message = "Last name can't be blank.") 26 | private String lastName; 27 | 28 | @NotBlank(message = "Phone number can't be blank.") 29 | @Size(min = 11, max = 20) 30 | private String phoneNumber; 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/model/entity/AdminEntity.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.model.entity; 2 | 3 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 4 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 5 | import com.springboot.springbootsecurity.auth.model.enums.UserType; 6 | import com.springboot.springbootsecurity.common.model.entity.BaseEntity; 7 | import jakarta.persistence.*; 8 | import lombok.*; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | @Getter 15 | @Setter 16 | @SuperBuilder 17 | @EqualsAndHashCode(callSuper = true) 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @Entity 21 | @Table(name = "CASE_ADMIN") 22 | public class AdminEntity extends BaseEntity { 23 | 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.UUID) 26 | @Column(name = "ID") 27 | private String id; 28 | 29 | @Column(name = "EMAIL") 30 | private String email; 31 | 32 | @Column(name = "PASSWORD") 33 | private String password; 34 | 35 | @Column(name = "FIRST_NAME") 36 | private String firstName; 37 | 38 | @Column(name = "LAST_NAME") 39 | private String lastName; 40 | 41 | @Column( 42 | name = "PHONE_NUMBER", 43 | length = 20 44 | ) 45 | private String phoneNumber; 46 | 47 | @Builder.Default 48 | @Enumerated(EnumType.STRING) 49 | private UserType userType = UserType.ADMIN; 50 | 51 | @Builder.Default 52 | @Enumerated(EnumType.STRING) 53 | private UserStatus userStatus = UserStatus.ACTIVE; 54 | 55 | public Map getClaims() { 56 | 57 | final Map claims = new HashMap<>(); 58 | 59 | claims.put(TokenClaims.USER_ID.getValue(), this.id); 60 | claims.put(TokenClaims.USER_TYPE.getValue(), this.userType); 61 | claims.put(TokenClaims.USER_STATUS.getValue(), this.userStatus); 62 | claims.put(TokenClaims.USER_FIRST_NAME.getValue(), this.firstName); 63 | claims.put(TokenClaims.USER_LAST_NAME.getValue(), this.lastName); 64 | claims.put(TokenClaims.USER_EMAIL.getValue(), this.email); 65 | claims.put(TokenClaims.USER_PHONE_NUMBER.getValue(), this.phoneNumber); 66 | 67 | return claims; 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/model/mapper/AdminEntityToAdminMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.admin.model.Admin; 4 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 5 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | @Mapper 10 | public interface AdminEntityToAdminMapper extends BaseMapper { 11 | 12 | @Override 13 | Admin map(AdminEntity source); 14 | 15 | static AdminEntityToAdminMapper initialize() { 16 | return Mappers.getMapper(AdminEntityToAdminMapper.class); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/model/mapper/AdminRegisterRequestToAdminEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.admin.model.dto.request.AdminRegisterRequest; 4 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 5 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Named; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface AdminRegisterRequestToAdminEntityMapper extends BaseMapper { 12 | 13 | 14 | @Named("mapForSaving") 15 | default AdminEntity mapForSaving(AdminRegisterRequest adminRegisterRequest) { 16 | return AdminEntity.builder() 17 | .email(adminRegisterRequest.getEmail()) 18 | .firstName(adminRegisterRequest.getFirstName()) 19 | .lastName(adminRegisterRequest.getLastName()) 20 | .phoneNumber(adminRegisterRequest.getPhoneNumber()) 21 | .build(); 22 | } 23 | 24 | static AdminRegisterRequestToAdminEntityMapper initialize() { 25 | return Mappers.getMapper(AdminRegisterRequestToAdminEntityMapper.class); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/model/mapper/AdminToAdminEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.admin.model.Admin; 4 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 5 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | @Mapper 10 | public interface AdminToAdminEntityMapper extends BaseMapper { 11 | 12 | @Override 13 | AdminEntity map(Admin source); 14 | 15 | static AdminToAdminEntityMapper initialize() { 16 | return Mappers.getMapper(AdminToAdminEntityMapper.class); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/repository/AdminRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.repository; 2 | 3 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface AdminRepository extends JpaRepository { 9 | 10 | boolean existsAdminEntityByEmail(final String email); 11 | 12 | Optional findAdminEntityByEmail(final String email); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/AdminLoginService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service; 2 | 3 | import com.springboot.springbootsecurity.auth.model.Token; 4 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 5 | 6 | public interface AdminLoginService { 7 | 8 | Token login(final LoginRequest loginRequest); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/AdminLogoutService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service; 2 | 3 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 4 | 5 | public interface AdminLogoutService { 6 | 7 | void logout(final TokenInvalidateRequest tokenInvalidateRequest); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/AdminRefreshTokenService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service; 2 | 3 | import com.springboot.springbootsecurity.auth.model.Token; 4 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 5 | 6 | public interface AdminRefreshTokenService { 7 | 8 | Token refreshToken(final TokenRefreshRequest tokenRefreshRequest); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/AdminRegisterService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service; 2 | 3 | import com.springboot.springbootsecurity.admin.model.Admin; 4 | import com.springboot.springbootsecurity.admin.model.dto.request.AdminRegisterRequest; 5 | 6 | public interface AdminRegisterService { 7 | 8 | Admin registerAdmin(final AdminRegisterRequest adminRegisterRequest); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/impl/AdminLoginServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.exception.AdminNotFoundException; 4 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 5 | import com.springboot.springbootsecurity.admin.repository.AdminRepository; 6 | import com.springboot.springbootsecurity.admin.service.AdminLoginService; 7 | import com.springboot.springbootsecurity.auth.exception.PasswordNotValidException; 8 | import com.springboot.springbootsecurity.auth.model.Token; 9 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 10 | import com.springboot.springbootsecurity.auth.service.TokenService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class AdminLoginServiceImpl implements AdminLoginService { 18 | 19 | private final AdminRepository adminRepository; 20 | private final PasswordEncoder passwordEncoder; 21 | private final TokenService tokenService; 22 | 23 | @Override 24 | public Token login(LoginRequest loginRequest) { 25 | 26 | final AdminEntity adminEntityFromDB = adminRepository 27 | .findAdminEntityByEmail(loginRequest.getEmail()) 28 | .orElseThrow( 29 | () -> new AdminNotFoundException("Can't find with given email: " 30 | + loginRequest.getEmail()) 31 | ); 32 | 33 | if (Boolean.FALSE.equals(passwordEncoder.matches( 34 | loginRequest.getPassword(), adminEntityFromDB.getPassword()))) { 35 | throw new PasswordNotValidException(); 36 | } 37 | 38 | return tokenService.generateToken(adminEntityFromDB.getClaims()); 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/impl/AdminLogoutServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.service.AdminLogoutService; 4 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 5 | import com.springboot.springbootsecurity.auth.service.InvalidTokenService; 6 | import com.springboot.springbootsecurity.auth.service.TokenService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Set; 11 | 12 | @Service 13 | @RequiredArgsConstructor 14 | public class AdminLogoutServiceImpl implements AdminLogoutService { 15 | 16 | private final TokenService tokenService; 17 | private final InvalidTokenService invalidTokenService; 18 | 19 | @Override 20 | public void logout(TokenInvalidateRequest tokenInvalidateRequest) { 21 | 22 | tokenService.verifyAndValidate( 23 | Set.of( 24 | tokenInvalidateRequest.getAccessToken(), 25 | tokenInvalidateRequest.getRefreshToken() 26 | ) 27 | ); 28 | 29 | final String accessTokenId = tokenService 30 | .getPayload(tokenInvalidateRequest.getAccessToken()) 31 | .getId(); 32 | 33 | invalidTokenService.checkForInvalidityOfToken(accessTokenId); 34 | 35 | 36 | final String refreshTokenId = tokenService 37 | .getPayload(tokenInvalidateRequest.getRefreshToken()) 38 | .getId(); 39 | 40 | invalidTokenService.checkForInvalidityOfToken(refreshTokenId); 41 | 42 | invalidTokenService.invalidateTokens(Set.of(accessTokenId,refreshTokenId)); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/impl/AdminRefreshTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.exception.AdminNotFoundException; 4 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 5 | import com.springboot.springbootsecurity.admin.repository.AdminRepository; 6 | import com.springboot.springbootsecurity.admin.service.AdminRefreshTokenService; 7 | import com.springboot.springbootsecurity.auth.exception.UserStatusNotValidException; 8 | import com.springboot.springbootsecurity.auth.model.Token; 9 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 10 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 11 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 12 | import com.springboot.springbootsecurity.auth.service.TokenService; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | @RequiredArgsConstructor 18 | public class AdminRefreshTokenServiceImpl implements AdminRefreshTokenService { 19 | 20 | private final AdminRepository adminRepository; 21 | private final TokenService tokenService; 22 | 23 | @Override 24 | public Token refreshToken(TokenRefreshRequest tokenRefreshRequest) { 25 | 26 | tokenService.verifyAndValidate(tokenRefreshRequest.getRefreshToken()); 27 | 28 | final String adminId = tokenService 29 | .getPayload(tokenRefreshRequest.getRefreshToken()) 30 | .get(TokenClaims.USER_ID.getValue()) 31 | .toString(); 32 | 33 | final AdminEntity adminEntityFromDB = adminRepository 34 | .findById(adminId) 35 | .orElseThrow(AdminNotFoundException::new); 36 | 37 | this.validateAdminStatus(adminEntityFromDB); 38 | 39 | return tokenService.generateToken( 40 | adminEntityFromDB.getClaims(), 41 | tokenRefreshRequest.getRefreshToken() 42 | ); 43 | 44 | } 45 | 46 | private void validateAdminStatus(final AdminEntity adminEntity) { 47 | if (!(UserStatus.ACTIVE.equals(adminEntity.getUserStatus()))) { 48 | throw new UserStatusNotValidException("UserStatus = " + adminEntity.getUserStatus()); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/admin/service/impl/AdminRegisterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.exception.AdminAlreadyExistException; 4 | import com.springboot.springbootsecurity.admin.model.Admin; 5 | import com.springboot.springbootsecurity.admin.model.dto.request.AdminRegisterRequest; 6 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 7 | import com.springboot.springbootsecurity.admin.model.mapper.AdminEntityToAdminMapper; 8 | import com.springboot.springbootsecurity.admin.model.mapper.AdminRegisterRequestToAdminEntityMapper; 9 | import com.springboot.springbootsecurity.admin.repository.AdminRepository; 10 | import com.springboot.springbootsecurity.admin.service.AdminRegisterService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | @Service 17 | @RequiredArgsConstructor 18 | public class AdminRegisterServiceImpl implements AdminRegisterService { 19 | 20 | private final AdminRepository adminRepository; 21 | private final AdminRegisterRequestToAdminEntityMapper adminRegisterRequestToAdminEntityMapper = AdminRegisterRequestToAdminEntityMapper.initialize(); 22 | 23 | private final AdminEntityToAdminMapper adminEntityToAdminMapper = AdminEntityToAdminMapper.initialize(); 24 | 25 | private final PasswordEncoder passwordEncoder; 26 | 27 | @Override 28 | @Transactional 29 | public Admin registerAdmin(AdminRegisterRequest adminRegisterRequest) { 30 | 31 | if (adminRepository.existsAdminEntityByEmail(adminRegisterRequest.getEmail())) { 32 | throw new AdminAlreadyExistException("The email is already used for another admin : " + adminRegisterRequest.getEmail()); 33 | } 34 | 35 | final AdminEntity adminEntityToBeSave = adminRegisterRequestToAdminEntityMapper.mapForSaving(adminRegisterRequest); 36 | 37 | adminEntityToBeSave.setPassword(passwordEncoder.encode(adminRegisterRequest.getPassword())); 38 | 39 | AdminEntity savedAdminEntity = adminRepository.save(adminEntityToBeSave); 40 | 41 | return adminEntityToAdminMapper.map(savedAdminEntity); 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.config; 2 | 3 | import com.springboot.springbootsecurity.auth.filter.CustomBearerTokenAuthenticationFilter; 4 | import com.springboot.springbootsecurity.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 | @Configuration 28 | @EnableWebSecurity 29 | @RequiredArgsConstructor 30 | @EnableMethodSecurity 31 | public class SecurityConfig { 32 | 33 | @Bean 34 | protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { 35 | return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); 36 | } 37 | 38 | @Bean 39 | public SecurityFilterChain filterChain( 40 | final HttpSecurity httpSecurity, 41 | final CustomBearerTokenAuthenticationFilter customBearerTokenAuthenticationFilter, 42 | final CustomAuthenticationEntryPoint customAuthenticationEntryPoint 43 | ) throws Exception { 44 | 45 | httpSecurity 46 | .exceptionHandling(customizer -> customizer.authenticationEntryPoint(customAuthenticationEntryPoint)) 47 | .cors(customizer -> customizer.configurationSource(corsConfigurationSource())) 48 | .csrf(AbstractHttpConfigurer::disable) 49 | .authorizeHttpRequests(customizer -> customizer 50 | .requestMatchers(HttpMethod.POST, "/api/v1/authentication/**").permitAll() 51 | .anyRequest().authenticated() 52 | ) 53 | .sessionManagement(customizer -> customizer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 54 | .addFilterBefore(customBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class); 55 | 56 | return httpSecurity.build(); 57 | } 58 | 59 | 60 | private CorsConfigurationSource corsConfigurationSource() { 61 | CorsConfiguration configuration = new CorsConfiguration(); 62 | configuration.setAllowedOrigins(List.of("*")); 63 | configuration.setAllowedMethods(List.of("*")); 64 | configuration.setAllowedHeaders(List.of("*")); 65 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 66 | source.registerCorsConfiguration("/**", configuration); 67 | return source; 68 | } 69 | 70 | @Bean 71 | public PasswordEncoder passwordEncoder() { 72 | return new BCryptPasswordEncoder(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/config/TokenConfigurationParameter.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.config; 2 | 3 | import com.springboot.springbootsecurity.auth.model.enums.ConfigurationParameter; 4 | import com.springboot.springbootsecurity.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 | @Getter 12 | @Configuration 13 | public class TokenConfigurationParameter { 14 | private final String issuer; 15 | private final int accessTokenExpireMinute; 16 | private final int refreshTokenExpireDay; 17 | private final PublicKey publicKey; 18 | private final PrivateKey privateKey; 19 | 20 | public TokenConfigurationParameter() { 21 | 22 | this.issuer = ConfigurationParameter.ISSUER.getDefaultValue(); 23 | 24 | this.accessTokenExpireMinute = Integer.parseInt( 25 | ConfigurationParameter.AUTH_ACCESS_TOKEN_EXPIRE_MINUTE.getDefaultValue() 26 | ); 27 | 28 | this.refreshTokenExpireDay = Integer.parseInt( 29 | ConfigurationParameter.AUTH_REFRESH_TOKEN_EXPIRE_DAY.getDefaultValue() 30 | ); 31 | 32 | this.publicKey = KeyConverter.convertPublicKey( 33 | ConfigurationParameter.AUTH_PUBLIC_KEY.getDefaultValue() 34 | ); 35 | 36 | this.privateKey = KeyConverter.convertPrivateKey( 37 | ConfigurationParameter.AUTH_PRIVATE_KEY.getDefaultValue() 38 | ); 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/exception/PasswordNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class PasswordNotValidException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = 7389659106153108528L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | Password is not valid! 12 | """; 13 | 14 | public PasswordNotValidException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public PasswordNotValidException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/exception/TokenAlreadyInvalidatedException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class TokenAlreadyInvalidatedException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = -3922046409563858698L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | Token is already invalidated! 12 | """; 13 | 14 | public TokenAlreadyInvalidatedException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public TokenAlreadyInvalidatedException(final String tokenId) { 19 | super(DEFAULT_MESSAGE + " TokenID = " + tokenId); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/exception/UserStatusNotValidException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class UserStatusNotValidException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = 3440177088502218750L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | User status is not valid! 12 | """; 13 | 14 | public UserStatusNotValidException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public UserStatusNotValidException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/filter/CustomBearerTokenAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.filter; 2 | 3 | import com.springboot.springbootsecurity.auth.model.Token; 4 | import com.springboot.springbootsecurity.auth.service.InvalidTokenService; 5 | import com.springboot.springbootsecurity.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 | @Slf4j 22 | @Component 23 | @RequiredArgsConstructor 24 | public class CustomBearerTokenAuthenticationFilter extends OncePerRequestFilter { 25 | 26 | private final TokenService tokenService; 27 | private final InvalidTokenService invalidTokenService; 28 | 29 | @Override 30 | protected void doFilterInternal(@NonNull final HttpServletRequest httpServletRequest, 31 | @NonNull final HttpServletResponse httpServletResponse, 32 | @NonNull final FilterChain filterChain) throws ServletException, IOException { 33 | 34 | log.debug("API Request was secured with Security!"); 35 | 36 | final String authorizationHeader = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION); 37 | 38 | if (Token.isBearerToken(authorizationHeader)) { 39 | 40 | final String jwt = Token.getJwt(authorizationHeader); 41 | 42 | tokenService.verifyAndValidate(jwt); 43 | 44 | final String tokenId = tokenService.getId(jwt); 45 | 46 | invalidTokenService.checkForInvalidityOfToken(tokenId); 47 | 48 | final UsernamePasswordAuthenticationToken authentication = tokenService 49 | .getAuthentication(jwt); 50 | 51 | SecurityContextHolder.getContext().setAuthentication(authentication); 52 | 53 | } 54 | 55 | filterChain.doFilter(httpServletRequest,httpServletResponse); 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/Identity.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model; 2 | 3 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.context.annotation.Scope; 6 | import org.springframework.context.annotation.ScopedProxyMode; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.oauth2.jwt.Jwt; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) 13 | @RequiredArgsConstructor 14 | public class Identity { 15 | 16 | public String getAccessToken() { 17 | return this.getJwt().getTokenValue(); 18 | } 19 | 20 | public String getUserId() { 21 | return this.getJwt().getClaim(TokenClaims.USER_ID.getValue()); 22 | } 23 | 24 | 25 | private Jwt getJwt() { 26 | return ((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/Token.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import org.springframework.util.StringUtils; 6 | 7 | @Getter 8 | @Builder 9 | public class Token { 10 | 11 | private String accessToken; 12 | private Long accessTokenExpiresAt; 13 | private String refreshToken; 14 | 15 | private static final String TOKEN_PREFIX = "Bearer "; 16 | 17 | public static boolean isBearerToken(final String authorizationHeader) { 18 | return StringUtils.hasText(authorizationHeader) && 19 | authorizationHeader.startsWith(TOKEN_PREFIX); 20 | } 21 | 22 | public static String getJwt(final String authorizationHeader) { 23 | return authorizationHeader.replace(TOKEN_PREFIX, ""); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/dto/request/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 9 | @Setter 10 | @Builder 11 | public class LoginRequest { 12 | 13 | @NotBlank 14 | private String email; 15 | 16 | @NotBlank 17 | private String password; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/dto/request/TokenInvalidateRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.dto.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import lombok.*; 5 | 6 | @Getter 7 | @Setter 8 | @Builder 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class TokenInvalidateRequest { 12 | 13 | @NotBlank 14 | private String accessToken; 15 | 16 | @NotBlank 17 | private String refreshToken; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/dto/request/TokenRefreshRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.dto.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import lombok.*; 5 | 6 | @Getter 7 | @Setter 8 | @Builder 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class TokenRefreshRequest { 12 | 13 | @NotBlank 14 | private String refreshToken; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/dto/response/TokenResponse.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.dto.response; 2 | 3 | import lombok.*; 4 | 5 | @Getter 6 | @Setter 7 | @Builder 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class TokenResponse { 11 | 12 | private String accessToken; 13 | private Long accessTokenExpiresAt; 14 | private String refreshToken; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/entity/InvalidTokenEntity.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.entity; 2 | 3 | import com.springboot.springbootsecurity.common.model.entity.BaseEntity; 4 | import jakarta.persistence.*; 5 | import lombok.*; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | @Entity 9 | @Getter 10 | @Setter 11 | @SuperBuilder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @EqualsAndHashCode(callSuper = true) 15 | @Table(name = "INVALID_TOKEN") 16 | public class InvalidTokenEntity extends BaseEntity { 17 | 18 | @Id 19 | @Column(name = "ID") 20 | @GeneratedValue(strategy = GenerationType.UUID) 21 | private String id; 22 | 23 | @Column(name = "TOKEN_ID") 24 | private String tokenId; 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/enums/ConfigurationParameter.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.enums; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public enum ConfigurationParameter { 9 | 10 | ISSUER("ISSUER"), 11 | 12 | AUTH_ACCESS_TOKEN_EXPIRE_MINUTE("30"), 13 | AUTH_REFRESH_TOKEN_EXPIRE_DAY("1"), 14 | AUTH_PUBLIC_KEY(""" 15 | -----BEGIN PUBLIC KEY----- 16 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1HmZ3A379M6Rv9UnMt9R 17 | Wq0a6bpcnoOWJxTi2exwnecW3r1X1PjeUvsDogy7RYjhlxU0G+1r38gPWfUW2FNd 18 | tsa3H+FDhJ6dcNc4uKVYPsiVJukHi4NrvWA8E8dPdLW1lNcijr4PqXjvZTLoS1QX 19 | f30wnNLBNDwdPXTESodi/n87VoSH2ChgLZUVfoS3m/NlUN8Z58gGxRcpUyjl+MmC 20 | hD2cfyWr2xdxKd+UQMrd36LfyoWh0IlONlxo0H5x8JIwlziLbPEAh7dJ9QYM0b5G 21 | msXBAzrILvW5+POSq4u1vNlzSwdLe+AZ6bnCwQVrqvMn/I7JT+4+lY8BsP/gMRQL 22 | awIDAQAB 23 | -----END PUBLIC KEY----- 24 | """), 25 | AUTH_PRIVATE_KEY(""" 26 | -----BEGIN PRIVATE KEY----- 27 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUeZncDfv0zpG/ 28 | 1Scy31FarRrpulyeg5YnFOLZ7HCd5xbevVfU+N5S+wOiDLtFiOGXFTQb7WvfyA9Z 29 | 9RbYU122xrcf4UOEnp1w1zi4pVg+yJUm6QeLg2u9YDwTx090tbWU1yKOvg+peO9l 30 | MuhLVBd/fTCc0sE0PB09dMRKh2L+fztWhIfYKGAtlRV+hLeb82VQ3xnnyAbFFylT 31 | KOX4yYKEPZx/JavbF3Ep35RAyt3fot/KhaHQiU42XGjQfnHwkjCXOIts8QCHt0n1 32 | BgzRvkaaxcEDOsgu9bn485Kri7W82XNLB0t74BnpucLBBWuq8yf8jslP7j6VjwGw 33 | /+AxFAtrAgMBAAECggEARWpw5N7AuQsfvN+DjfA9oPU6/K9BARyWWrBNKMtBQ6Uy 34 | 6JRNdKvV3qBZYIDuUdpVcUmhG5qmipbOxSH4U7ZwwH0NaOHscBBt+WanBlQmj2Ry 35 | riKlr2PBOD6Pghq0j7mp2DWs+ZuIfGKhO5u1Hp8bijA5SJLmQg19tA1I79xpcCFC 36 | flY8WaBKJCTKtH3sisS9IqAtGx3+O2fwzk5oSA6DDuX+45zTLS1vlucwhTUTDE4j 37 | UitYM5CnW9LzJ72ofWZBE29twECXQa+7YYXYniqUOzYZVhy7OiwYT9HH2fDGurmL 38 | F4adtcmVS6CUM6OYSV8z1DXbwLcidTqwiAj9uZ8suQKBgQD2hb/Bimgoh0dT6q9N 39 | pORikLW9L/6q3TuAfNJrrP63dwgooeCcHdk8Wh1zFkNibmfdhHQM5Hu3pQq0oxtG 40 | XiH1WYhluDAjyq6ahvYasgGtdzh279VcicleP9x6B6tmWop+a9kM+Yh2r3Yb5KXr 41 | ZyFWLeoEkCyeXdVr1tds119Q/QKBgQDcpMKXIAiayZmcHU03pNrDGhs7m2B18S6Z 42 | WcXSQRf1d98wbzzfsrbHM9k8gX5VGvZb+rwl95SyevT1LSaGM44On4WXjCUlpUnb 43 | 8+mKONra8yXuWutB1aclPQddj4HAFGikJyBP45e/SXNTBPrK5cm/HULbgd76FxHv 44 | QQZ2EbSOhwKBgA5pHR98LsCHv+So6Fx6khss6GLJxnJIgmztXwOKVk11ONXfOJkH 45 | qaY8glIy7/d2Cr5JOttyE8VVcX3Dtxly8Ts9Y5rGnJHLDE/eKc6/rxdry7IwLOG+ 46 | 8DWBOCst/Zf7HPNs7IA0qgR+F0JkKErNeYZnIrHnl6QeShaGtYsYP+slAoGBANRU 47 | yd5dOWqb73NIz3Jo9w0iJmrqT52wh8OTnMeFVOUogmQ96Drt5O82eiu8AjMsS0Cg 48 | vkdbRoGryefXl2c2XdK8uPbqKyVbNwSwaWJW7GYf77S9UgB89ujjHh9vZtHN0hWG 49 | gZXf07yFlrGh7Scsk0WThy9uf4H0iZHQ5cLhrvwpAoGAAj3uW/aAJDhfHHVgBGhC 50 | la8OzEqY8UbqMYrXKfrmYOzvFYKWd5BgrZvgINhSmjxMeil952gHVsH/td+KUyqe 51 | dxTAgV9WVn8KVz6KoEKJV5CarN0IOl5fZVJz1i0Q5t6VdUXLC6teh4ByrEAlsRgi 52 | h2UWcQCA6KqrmJbR2q6yj+s= 53 | -----END PRIVATE KEY----- 54 | """); 55 | 56 | private final String defaultValue; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/enums/TokenClaims.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.enums; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public enum TokenClaims { 9 | 10 | JWT_ID("jti"), 11 | USER_ID("userId"), 12 | USER_TYPE("userType"), 13 | USER_STATUS("userStatus"), 14 | USER_FIRST_NAME("userFirstName"), 15 | USER_LAST_NAME("userLastName"), 16 | USER_EMAIL("userEmail"), 17 | USER_PHONE_NUMBER("userPhoneNumber"), 18 | STORE_TITLE("storeTitle"), 19 | ISSUED_AT("iat"), 20 | EXPIRES_AT("exp"), 21 | ALGORITHM("alg"), 22 | TYP("typ"); 23 | 24 | private final String value; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/enums/TokenType.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.enums; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public enum TokenType { 9 | 10 | BEARER("Bearer"); 11 | 12 | private final String value; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/enums/UserStatus.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.enums; 2 | 3 | public enum UserStatus { 4 | ACTIVE, 5 | PASSIVE, 6 | SUSPENDED 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/enums/UserType.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.enums; 2 | 3 | public enum UserType { 4 | USER, 5 | ADMIN 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/model/mapper/TokenToTokenResponseMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.auth.model.Token; 4 | import com.springboot.springbootsecurity.auth.model.dto.response.TokenResponse; 5 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | @Mapper 10 | public interface TokenToTokenResponseMapper extends BaseMapper { 11 | 12 | @Override 13 | TokenResponse map(Token source); 14 | 15 | static TokenToTokenResponseMapper initialize() { 16 | return Mappers.getMapper(TokenToTokenResponseMapper.class); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/repository/InvalidTokenRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.repository; 2 | 3 | import com.springboot.springbootsecurity.auth.model.entity.InvalidTokenEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface InvalidTokenRepository extends JpaRepository { 9 | 10 | Optional findByTokenId(final String tokenId); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/security/CustomAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.security; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 5 | import com.springboot.springbootsecurity.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 | @Component 18 | public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { 19 | 20 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 21 | 22 | static { 23 | OBJECT_MAPPER.registerModule(new JavaTimeModule()); 24 | } 25 | 26 | 27 | @Override 28 | public void commence(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, 29 | final AuthenticationException authenticationException) throws IOException { 30 | 31 | httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); 32 | httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); 33 | 34 | final CustomError customError = CustomError.builder() 35 | .header(CustomError.Header.AUTH_ERROR.getName()) 36 | .httpStatus(HttpStatus.UNAUTHORIZED) 37 | .isSuccess(false) 38 | .build(); 39 | 40 | final String responseBody = OBJECT_MAPPER 41 | .writer(DateFormat.getDateInstance()) 42 | .writeValueAsString(customError); 43 | 44 | httpServletResponse.getOutputStream() 45 | .write(responseBody.getBytes()); 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/service/InvalidTokenService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.service; 2 | 3 | import java.util.Set; 4 | 5 | public interface InvalidTokenService { 6 | 7 | void invalidateTokens(final Set tokenIds); 8 | 9 | void checkForInvalidityOfToken(final String tokenId); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/service/TokenService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.service; 2 | 3 | import com.springboot.springbootsecurity.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 | public interface TokenService { 12 | 13 | Token generateToken(final Map claims); 14 | 15 | Token generateToken(final Map claims, final String refreshToken); 16 | 17 | UsernamePasswordAuthenticationToken getAuthentication(final String token); 18 | 19 | void verifyAndValidate(final String jwt); 20 | 21 | void verifyAndValidate(final Set jwts); 22 | 23 | Jws getClaims(final String jwt); 24 | 25 | Claims getPayload(final String jwt); 26 | 27 | String getId(final String jwt); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/service/impl/InvalidTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.exception.TokenAlreadyInvalidatedException; 4 | import com.springboot.springbootsecurity.auth.model.entity.InvalidTokenEntity; 5 | import com.springboot.springbootsecurity.auth.repository.InvalidTokenRepository; 6 | import com.springboot.springbootsecurity.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 | @Service 14 | @RequiredArgsConstructor 15 | public class InvalidTokenServiceImpl implements InvalidTokenService { 16 | 17 | private final InvalidTokenRepository invalidTokenRepository; 18 | 19 | @Override 20 | public void invalidateTokens(Set tokenIds) { 21 | 22 | final Set enocaInvalidTokenEntities = tokenIds.stream() 23 | .map(tokenId -> InvalidTokenEntity.builder() 24 | .tokenId(tokenId) 25 | .build() 26 | ) 27 | .collect(Collectors.toSet()); 28 | 29 | invalidTokenRepository.saveAll(enocaInvalidTokenEntities); 30 | } 31 | 32 | @Override 33 | public void checkForInvalidityOfToken(String tokenId) { 34 | 35 | final boolean isTokenInvalid = invalidTokenRepository.findByTokenId(tokenId).isPresent(); 36 | 37 | if (isTokenInvalid) { 38 | throw new TokenAlreadyInvalidatedException(tokenId); 39 | } 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/service/impl/TokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.auth.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.config.TokenConfigurationParameter; 4 | import com.springboot.springbootsecurity.auth.model.Token; 5 | import com.springboot.springbootsecurity.auth.model.enums.ConfigurationParameter; 6 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 7 | import com.springboot.springbootsecurity.auth.model.enums.TokenType; 8 | import com.springboot.springbootsecurity.auth.model.enums.UserType; 9 | import com.springboot.springbootsecurity.auth.service.InvalidTokenService; 10 | import com.springboot.springbootsecurity.auth.service.TokenService; 11 | import io.jsonwebtoken.Claims; 12 | import io.jsonwebtoken.Jws; 13 | import io.jsonwebtoken.JwsHeader; 14 | import io.jsonwebtoken.Jwts; 15 | import lombok.RequiredArgsConstructor; 16 | import org.apache.commons.lang3.time.DateUtils; 17 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 18 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 19 | import org.springframework.security.oauth2.jwt.Jwt; 20 | import org.springframework.stereotype.Service; 21 | 22 | import java.util.*; 23 | 24 | @Service 25 | @RequiredArgsConstructor 26 | public class TokenServiceImpl implements TokenService { 27 | 28 | private final TokenConfigurationParameter tokenConfigurationParameter; 29 | private final InvalidTokenService invalidTokenService; 30 | 31 | public Token generateToken(final Map claims) { 32 | 33 | final long currentTimeMillis = System.currentTimeMillis(); 34 | 35 | final Date tokenIssuedAt = new Date(currentTimeMillis); 36 | 37 | final Date accessTokenExpiresAt = DateUtils.addMinutes( 38 | new Date(currentTimeMillis), 39 | tokenConfigurationParameter.getAccessTokenExpireMinute() 40 | ); 41 | 42 | final String accessToken = Jwts.builder() 43 | .header() 44 | .type(TokenType.BEARER.getValue()) 45 | .and() 46 | .id(UUID.randomUUID().toString()) 47 | .issuer(ConfigurationParameter.ISSUER.getDefaultValue()) 48 | .issuedAt(tokenIssuedAt) 49 | .expiration(accessTokenExpiresAt) 50 | .signWith(tokenConfigurationParameter.getPrivateKey()) 51 | .claims(claims) 52 | .compact(); 53 | 54 | final Date refreshTokenExpiresAt = DateUtils.addDays( 55 | new Date(currentTimeMillis), 56 | tokenConfigurationParameter.getRefreshTokenExpireDay() 57 | ); 58 | 59 | final String refreshToken = Jwts.builder() 60 | .header() 61 | .type(TokenType.BEARER.getValue()) 62 | .and() 63 | .id(UUID.randomUUID().toString()) 64 | .issuer(tokenConfigurationParameter.getIssuer()) 65 | .issuedAt(tokenIssuedAt) 66 | .expiration(refreshTokenExpiresAt) 67 | .signWith(tokenConfigurationParameter.getPrivateKey()) 68 | .claim(TokenClaims.USER_ID.getValue(), claims.get(TokenClaims.USER_ID.getValue())) 69 | .compact(); 70 | 71 | return Token.builder() 72 | .accessToken(accessToken) 73 | .accessTokenExpiresAt(accessTokenExpiresAt.toInstant().getEpochSecond()) 74 | .refreshToken(refreshToken) 75 | .build(); 76 | 77 | } 78 | 79 | public Token generateToken(final Map claims, final String refreshToken) { 80 | final long currentTimeMillis = System.currentTimeMillis(); 81 | 82 | final String refreshTokenId = this.getId(refreshToken); 83 | 84 | invalidTokenService.checkForInvalidityOfToken(refreshTokenId); 85 | 86 | final Date accessTokenIssuedAt = new Date(currentTimeMillis); 87 | 88 | final Date accessTokenExpiresAt = DateUtils.addMinutes( 89 | new Date(currentTimeMillis), 90 | tokenConfigurationParameter.getAccessTokenExpireMinute() 91 | ); 92 | 93 | final String accessToken = Jwts.builder() 94 | .header() 95 | .type(TokenType.BEARER.getValue()) 96 | .and() 97 | .id(UUID.randomUUID().toString()) 98 | .issuer(tokenConfigurationParameter.getIssuer()) 99 | .issuedAt(accessTokenIssuedAt) 100 | .expiration(accessTokenExpiresAt) 101 | .signWith(tokenConfigurationParameter.getPrivateKey()) 102 | .claims(claims) 103 | .compact(); 104 | 105 | return Token.builder() 106 | .accessToken(accessToken) 107 | .accessTokenExpiresAt(accessTokenExpiresAt.toInstant().getEpochSecond()) 108 | .refreshToken(refreshToken) 109 | .build(); 110 | } 111 | 112 | public UsernamePasswordAuthenticationToken getAuthentication(final String token) { 113 | 114 | final Jws claimsJws = Jwts.parser() 115 | .verifyWith(tokenConfigurationParameter.getPublicKey()) 116 | .build() 117 | .parseSignedClaims(token); 118 | 119 | final JwsHeader jwsHeader = claimsJws.getHeader(); 120 | final Claims payload = claimsJws.getPayload(); 121 | 122 | final Jwt jwt = new org.springframework.security.oauth2.jwt.Jwt( 123 | token, 124 | payload.getIssuedAt().toInstant(), 125 | payload.getExpiration().toInstant(), 126 | Map.of( 127 | TokenClaims.TYP.getValue(), jwsHeader.getType(), 128 | TokenClaims.ALGORITHM.getValue(), jwsHeader.getAlgorithm() 129 | ), 130 | payload 131 | ); 132 | 133 | final UserType userType = UserType.valueOf(payload.get(TokenClaims.USER_TYPE.getValue()).toString()); 134 | 135 | final List authorities = new ArrayList<>(); 136 | authorities.add(new SimpleGrantedAuthority(userType.name())); 137 | 138 | return UsernamePasswordAuthenticationToken 139 | .authenticated(jwt, null, authorities); 140 | 141 | } 142 | 143 | public void verifyAndValidate(final String jwt) { 144 | Jwts.parser() 145 | .verifyWith(tokenConfigurationParameter.getPublicKey()) 146 | .build() 147 | .parseSignedClaims(jwt); 148 | } 149 | 150 | @Override 151 | public void verifyAndValidate(final Set jwts) { 152 | jwts.forEach(this::verifyAndValidate); 153 | } 154 | 155 | public Jws getClaims(final String jwt) { 156 | return Jwts.parser() 157 | .verifyWith(tokenConfigurationParameter.getPublicKey()) 158 | .build() 159 | .parseSignedClaims(jwt); 160 | 161 | } 162 | 163 | public Claims getPayload(final String jwt) { 164 | return Jwts.parser() 165 | .verifyWith(tokenConfigurationParameter.getPublicKey()) 166 | .build() 167 | .parseSignedClaims(jwt) 168 | .getPayload(); 169 | } 170 | 171 | public String getId(final String jwt) { 172 | return Jwts.parser() 173 | .verifyWith(tokenConfigurationParameter.getPublicKey()) 174 | .build() 175 | .parseSignedClaims(jwt) 176 | .getPayload() 177 | .getId(); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/auth/utils/KeyConverter.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @UtilityClass 15 | public class KeyConverter { 16 | 17 | public static PublicKey convertPublicKey(final String publicPemKey) { 18 | 19 | final StringReader keyReader = new StringReader(publicPemKey); 20 | try { 21 | SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo 22 | .getInstance(new PEMParser(keyReader).readObject()); 23 | return new JcaPEMKeyConverter().getPublicKey(publicKeyInfo); 24 | } catch (IOException exception) { 25 | throw new RuntimeException(exception); 26 | } 27 | 28 | } 29 | 30 | public static PrivateKey convertPrivateKey(final String privatePemKey) { 31 | 32 | StringReader keyReader = new StringReader(privatePemKey); 33 | try { 34 | PrivateKeyInfo privateKeyInfo = PrivateKeyInfo 35 | .getInstance(new PEMParser(keyReader).readObject()); 36 | return new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo); 37 | } catch (IOException exception) { 38 | throw new RuntimeException(exception); 39 | } 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.common.exception; 2 | 3 | import com.springboot.springbootsecurity.common.model.CustomError; 4 | import jakarta.validation.ConstraintViolationException; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.access.AccessDeniedException; 9 | import org.springframework.validation.FieldError; 10 | import org.springframework.web.bind.MethodArgumentNotValidException; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.bind.annotation.RestControllerAdvice; 13 | 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | @RestControllerAdvice 20 | public class GlobalExceptionHandler { 21 | 22 | @ExceptionHandler(MethodArgumentNotValidException.class) 23 | protected ResponseEntity handleMethodArgumentNotValid(final MethodArgumentNotValidException ex) { 24 | 25 | Map errors = new HashMap<>(); 26 | 27 | ex.getBindingResult().getAllErrors().forEach( 28 | error -> { 29 | String fieldName = ((FieldError) error).getField(); 30 | String message = error.getDefaultMessage(); 31 | errors.put(fieldName, message); 32 | } 33 | ); 34 | 35 | return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); 36 | 37 | } 38 | 39 | @ExceptionHandler(ConstraintViolationException.class) 40 | protected ResponseEntity handlePathVariableErrors(final ConstraintViolationException constraintViolationException) { 41 | 42 | final List subErrors = new ArrayList<>(); 43 | constraintViolationException.getConstraintViolations() 44 | .forEach(constraintViolation -> 45 | subErrors.add( 46 | CustomError.CustomSubError.builder() 47 | .message(constraintViolation.getMessage()) 48 | .field(StringUtils.substringAfterLast(constraintViolation.getPropertyPath().toString(), ".")) 49 | .value(constraintViolation.getInvalidValue() != null ? constraintViolation.getInvalidValue().toString() : null) 50 | .type(constraintViolation.getInvalidValue().getClass().getSimpleName()) 51 | .build() 52 | ) 53 | ); 54 | 55 | return new ResponseEntity<>(subErrors, HttpStatus.BAD_REQUEST); 56 | 57 | } 58 | 59 | @ExceptionHandler(RuntimeException.class) 60 | protected ResponseEntity handleRuntimeException(final RuntimeException runtimeException) { 61 | return new ResponseEntity<>(runtimeException, HttpStatus.NOT_FOUND); 62 | } 63 | 64 | @ExceptionHandler(AccessDeniedException.class) 65 | protected ResponseEntity handleAccessDeniedException(final AccessDeniedException accessDeniedException) { 66 | return new ResponseEntity<>(accessDeniedException.getMessage(), HttpStatus.FORBIDDEN); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/BaseDomainModel.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 14 | @Setter 15 | @SuperBuilder 16 | @MappedSuperclass 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public abstract class BaseDomainModel { 20 | 21 | @Column(name = "CREATED_AT") 22 | private LocalDateTime createdAt; 23 | 24 | @Column(name = "CREATED_BY") 25 | private String createdBy; 26 | 27 | @Column(name = "UPDATED_AT") 28 | private LocalDateTime updatedAt; 29 | 30 | @Column(name = "UPDATED_BY") 31 | private String updatedBy; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/CustomError.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 13 | @Builder 14 | public class CustomError { 15 | 16 | @Builder.Default 17 | private LocalDateTime time = LocalDateTime.now(); 18 | 19 | private HttpStatus httpStatus; 20 | 21 | private String header; 22 | 23 | @JsonInclude(JsonInclude.Include.NON_NULL) 24 | private String message; 25 | 26 | @Builder.Default 27 | private final Boolean isSuccess = false; 28 | 29 | @JsonInclude(JsonInclude.Include.NON_NULL) 30 | private List subErrors; 31 | 32 | @Getter 33 | @Builder 34 | public static class CustomSubError { 35 | 36 | private String message; 37 | 38 | private String field; 39 | 40 | @JsonInclude(JsonInclude.Include.NON_NULL) 41 | private Object value; 42 | 43 | @JsonInclude(JsonInclude.Include.NON_NULL) 44 | private String type; 45 | 46 | } 47 | 48 | @Getter 49 | @RequiredArgsConstructor 50 | public enum Header { 51 | 52 | API_ERROR("API ERROR"), 53 | 54 | ALREADY_EXIST("ALREADY EXIST"), 55 | 56 | NOT_FOUND("NOT EXIST"), 57 | 58 | VALIDATION_ERROR("VALIDATION ERROR"), 59 | 60 | DATABASE_ERROR("DATABASE ERROR"), 61 | 62 | PROCESS_ERROR("PROCESS ERROR"), 63 | 64 | AUTH_ERROR("AUTH ERROR"); 65 | 66 | 67 | private final String name; 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/CustomPage.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.common.model; 2 | 3 | import lombok.*; 4 | import org.springframework.data.domain.Page; 5 | 6 | import java.util.List; 7 | 8 | @Getter 9 | @Setter 10 | @Builder 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class CustomPage { 14 | private List content; 15 | 16 | private Integer pageNumber; 17 | 18 | private Integer pageSize; 19 | 20 | private Long totalElementCount; 21 | 22 | private Integer totalPageCount; 23 | 24 | 25 | public static CustomPage of(final List domainModels, final Page page) { 26 | return CustomPage.builder() 27 | .content(domainModels) 28 | .pageNumber(page.getNumber() + 1) 29 | .pageSize(page.getSize()) 30 | .totalPageCount(page.getTotalPages()) 31 | .totalElementCount(page.getTotalElements()) 32 | .build(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/CustomPaging.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 11 | @Setter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @SuperBuilder 15 | public class CustomPaging { 16 | 17 | @Min(value = 1, message = "Page number must be bigger than 0") 18 | private Integer pageNumber; 19 | 20 | @Min(value = 1, message = "Page size must be bigger than 0") 21 | private Integer pageSize; 22 | 23 | public Integer getPageNumber() { 24 | return pageNumber - 1; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/dto/request/CustomPagingRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.common.model.dto.request; 2 | 3 | import com.springboot.springbootsecurity.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 | @Getter 12 | @Setter 13 | @SuperBuilder 14 | @NoArgsConstructor 15 | public class CustomPagingRequest { 16 | 17 | private CustomPaging pagination; 18 | 19 | public Pageable toPageable() { 20 | return PageRequest.of( 21 | Math.toIntExact(pagination.getPageNumber()), 22 | Math.toIntExact(pagination.getPageSize()) 23 | ); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/dto/response/CustomPagingResponse.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.common.model.dto.response; 2 | 3 | import com.springboot.springbootsecurity.common.model.CustomPage; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.util.List; 8 | 9 | @Getter 10 | @Builder 11 | public class CustomPagingResponse { 12 | 13 | private List content; 14 | 15 | private Integer pageNumber; 16 | 17 | private Integer pageSize; 18 | 19 | private Long totalElementCount; 20 | 21 | private Integer totalPageCount; 22 | 23 | public static class CustomPagingResponseBuilder { 24 | 25 | public CustomPagingResponseBuilder of(final CustomPage customPage) { 26 | return CustomPagingResponse.builder() 27 | .pageNumber(customPage.getPageNumber()) 28 | .pageSize(customPage.getPageSize()) 29 | .totalElementCount(customPage.getTotalElementCount()) 30 | .totalPageCount(customPage.getTotalPageCount()); 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/dto/response/CustomResponse.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 11 | @Builder 12 | public class CustomResponse { 13 | 14 | @Builder.Default 15 | private LocalDateTime time = LocalDateTime.now(); 16 | 17 | private HttpStatus httpStatus; 18 | 19 | private Boolean isSuccess; 20 | 21 | @JsonInclude(JsonInclude.Include.NON_NULL) 22 | private T response; 23 | 24 | public static final CustomResponse SUCCESS = CustomResponse.builder() 25 | .httpStatus(HttpStatus.OK) 26 | .isSuccess(true) 27 | .build(); 28 | 29 | 30 | public static CustomResponse successOf(final T response) { 31 | return CustomResponse.builder() 32 | .httpStatus(HttpStatus.OK) 33 | .isSuccess(true) 34 | .response(response) 35 | .build(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.common.model.entity; 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 | @Getter 14 | @Setter 15 | @SuperBuilder 16 | @MappedSuperclass 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class BaseEntity { 20 | 21 | @Column(name = "CREATED_AT") 22 | private LocalDateTime createdAt; 23 | 24 | @Column(name = "CREATED_BY") 25 | private String createdBy; 26 | 27 | @Column(name = "UPDATED_AT") 28 | private LocalDateTime updatedAt; 29 | 30 | @Column(name = "UPDATED_BY") 31 | private String updatedBy; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/common/model/mapper/BaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.common.model.mapper; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | 6 | public interface BaseMapper { 7 | 8 | T map(S source); 9 | 10 | List map(Collection sources); 11 | 12 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.controller; 2 | 3 | import com.springboot.springbootsecurity.common.model.CustomPage; 4 | import com.springboot.springbootsecurity.common.model.dto.response.CustomPagingResponse; 5 | import com.springboot.springbootsecurity.common.model.dto.response.CustomResponse; 6 | import com.springboot.springbootsecurity.product.model.Product; 7 | import com.springboot.springbootsecurity.product.model.dto.request.ProductCreateRequest; 8 | import com.springboot.springbootsecurity.product.model.dto.request.ProductPagingRequest; 9 | import com.springboot.springbootsecurity.product.model.dto.request.ProductUpdateRequest; 10 | import com.springboot.springbootsecurity.product.model.dto.response.ProductResponse; 11 | import com.springboot.springbootsecurity.product.model.mapper.CustomPageToCustomPagingResponseMapper; 12 | import com.springboot.springbootsecurity.product.model.mapper.ProductToProductResponseMapper; 13 | import com.springboot.springbootsecurity.product.service.ProductCreateService; 14 | import com.springboot.springbootsecurity.product.service.ProductDeleteService; 15 | import com.springboot.springbootsecurity.product.service.ProductReadService; 16 | import com.springboot.springbootsecurity.product.service.ProductUpdateService; 17 | import jakarta.validation.Valid; 18 | import lombok.RequiredArgsConstructor; 19 | import org.hibernate.validator.constraints.UUID; 20 | import org.springframework.security.access.prepost.PreAuthorize; 21 | import org.springframework.validation.annotation.Validated; 22 | import org.springframework.web.bind.annotation.*; 23 | 24 | @RestController 25 | @RequestMapping("/api/v1/products") 26 | @RequiredArgsConstructor 27 | @Validated 28 | public class ProductController { 29 | 30 | private final ProductCreateService productCreateService; 31 | private final ProductReadService productReadService; 32 | private final ProductUpdateService productUpdateService; 33 | private final ProductDeleteService productDeleteService; 34 | 35 | private final ProductToProductResponseMapper productToProductResponseMapper = ProductToProductResponseMapper.initialize(); 36 | 37 | private final CustomPageToCustomPagingResponseMapper customPageToCustomPagingResponseMapper = 38 | CustomPageToCustomPagingResponseMapper.initialize(); 39 | 40 | @PostMapping 41 | @PreAuthorize("hasAnyAuthority('ADMIN')") 42 | public CustomResponse createProduct(@RequestBody @Valid final ProductCreateRequest productCreateRequest) { 43 | 44 | final Product createdProduct = productCreateService 45 | .createProduct(productCreateRequest); 46 | 47 | return CustomResponse.successOf(createdProduct.getId()); 48 | } 49 | 50 | @GetMapping("/{productId}") 51 | @PreAuthorize("hasAnyAuthority('ADMIN', 'USER')") 52 | public CustomResponse getProductById(@PathVariable @UUID final String productId) { 53 | 54 | final Product product = productReadService.getProductById(productId); 55 | 56 | final ProductResponse productResponse = productToProductResponseMapper.map(product); 57 | 58 | return CustomResponse.successOf(productResponse); 59 | 60 | } 61 | 62 | @GetMapping 63 | @PreAuthorize("hasAnyAuthority('ADMIN', 'USER')") 64 | public CustomResponse> getProducts( 65 | @RequestBody @Valid final ProductPagingRequest productPagingRequest) { 66 | 67 | final CustomPage productPage = productReadService.getProducts(productPagingRequest); 68 | 69 | final CustomPagingResponse productPagingResponse = 70 | customPageToCustomPagingResponseMapper.toPagingResponse(productPage); 71 | 72 | return CustomResponse.successOf(productPagingResponse); 73 | 74 | } 75 | 76 | @PutMapping("/{productId}") 77 | @PreAuthorize("hasAnyAuthority('ADMIN')") 78 | public CustomResponse updatedProductById( 79 | @RequestBody @Valid final ProductUpdateRequest productUpdateRequest, 80 | @PathVariable @UUID final String productId) { 81 | 82 | final Product updatedProduct = productUpdateService.updateProductById(productId, productUpdateRequest); 83 | 84 | final ProductResponse productResponse = productToProductResponseMapper.map(updatedProduct); 85 | 86 | return CustomResponse.successOf(productResponse); 87 | } 88 | 89 | @DeleteMapping("/{productId}") 90 | @PreAuthorize("hasAnyAuthority('ADMIN')") 91 | public CustomResponse deleteProductById(@PathVariable @UUID final String productId) { 92 | 93 | productDeleteService.deleteProductById(productId); 94 | return CustomResponse.SUCCESS; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/exception/ProductAlreadyExistException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class ProductAlreadyExistException extends RuntimeException{ 6 | 7 | @Serial 8 | private static final long serialVersionUID = 6193758692340202558L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | Product already exist! 12 | """; 13 | 14 | public ProductAlreadyExistException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public ProductAlreadyExistException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/exception/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class ProductNotFoundException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = 2329107146606835124L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | Product not found! 12 | """; 13 | 14 | public ProductNotFoundException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public ProductNotFoundException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model; 2 | 3 | import com.springboot.springbootsecurity.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 | @Getter 14 | @Setter 15 | @SuperBuilder 16 | @EqualsAndHashCode(callSuper = true) 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class Product extends BaseDomainModel { 20 | 21 | private String id; 22 | private String name; 23 | private BigDecimal amount; 24 | private BigDecimal unitPrice; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/dto/request/ProductCreateRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 11 | @Setter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Builder 15 | public class ProductCreateRequest { 16 | 17 | @Size( 18 | min = 1, 19 | message = "Product name can't be blank." 20 | ) 21 | private String name; 22 | 23 | @DecimalMin( 24 | value = "0.0001", 25 | message = "Amount must be bigger than 0" 26 | ) 27 | private BigDecimal amount; 28 | 29 | @DecimalMin( 30 | value = "0.0001", 31 | message = "Unit Price must be bigger than 0" 32 | ) 33 | private BigDecimal unitPrice; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/dto/request/ProductPagingRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.dto.request; 2 | 3 | import com.springboot.springbootsecurity.common.model.dto.request.CustomPagingRequest; 4 | import lombok.*; 5 | import lombok.experimental.SuperBuilder; 6 | 7 | @Getter 8 | @Setter 9 | @SuperBuilder 10 | @NoArgsConstructor 11 | public class ProductPagingRequest extends CustomPagingRequest { 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/dto/request/ProductUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 10 | @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | public class ProductUpdateRequest { 15 | 16 | @Size( 17 | min = 1, 18 | message = "Product name can't be blank." 19 | ) 20 | private String name; 21 | 22 | @DecimalMin( 23 | value = "0.0001", 24 | message = "Amount must be bigger than 0" 25 | ) 26 | private BigDecimal amount; 27 | 28 | @DecimalMin( 29 | value = "0.0001", 30 | message = "Unit Price must be bigger than 0" 31 | ) 32 | private BigDecimal unitPrice; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/dto/response/ProductResponse.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | @Getter 12 | @Setter 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class ProductResponse { 17 | 18 | private String id; 19 | private String name; 20 | private BigDecimal amount; 21 | private BigDecimal unitPrice; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/entity/ProductEntity.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.entity; 2 | 3 | import com.springboot.springbootsecurity.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 | @Getter 20 | @Setter 21 | @SuperBuilder 22 | @EqualsAndHashCode(callSuper = true) 23 | @NoArgsConstructor 24 | @AllArgsConstructor 25 | @Entity 26 | @Table(name = "PRODUCT") 27 | public class ProductEntity extends BaseEntity { 28 | 29 | @Id 30 | @GeneratedValue(strategy = GenerationType.UUID) 31 | @Column(name = "ID") 32 | private String id; 33 | 34 | @Column(name = "NAME") 35 | private String name; 36 | 37 | @Column( 38 | name = "AMOUNT", 39 | precision = 24, 40 | scale = 4 41 | ) 42 | private BigDecimal amount; 43 | 44 | @Column( 45 | name = "UNIT_PRICE", 46 | precision = 24, 47 | scale = 4 48 | ) 49 | private BigDecimal unitPrice; 50 | 51 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/mapper/CustomPageToCustomPagingResponseMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.common.model.CustomPage; 4 | import com.springboot.springbootsecurity.common.model.dto.response.CustomPagingResponse; 5 | import com.springboot.springbootsecurity.product.model.Product; 6 | import com.springboot.springbootsecurity.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 | @Mapper 14 | public interface CustomPageToCustomPagingResponseMapper { 15 | 16 | ProductToProductResponseMapper productToProductResponseMapper = Mappers.getMapper(ProductToProductResponseMapper.class); 17 | 18 | default CustomPagingResponse toPagingResponse(CustomPage productPage) { 19 | 20 | if (productPage == null) { 21 | return null; 22 | } 23 | 24 | return CustomPagingResponse.builder() 25 | .content(toProductResponseList(productPage.getContent())) 26 | .totalElementCount(productPage.getTotalElementCount()) 27 | .totalPageCount(productPage.getTotalPageCount()) 28 | .pageNumber(productPage.getPageNumber()) 29 | .pageSize(productPage.getPageSize()) 30 | .build(); 31 | 32 | } 33 | 34 | default List toProductResponseList(List products) { 35 | 36 | if (products == null) { 37 | return null; 38 | } 39 | 40 | return products.stream() 41 | .map(productToProductResponseMapper::map) 42 | .collect(Collectors.toList()); 43 | 44 | } 45 | 46 | static CustomPageToCustomPagingResponseMapper initialize() { 47 | return Mappers.getMapper(CustomPageToCustomPagingResponseMapper.class); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/mapper/ListProductEntityToListProductMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.product.model.Product; 4 | import com.springboot.springbootsecurity.product.model.dto.response.ProductResponse; 5 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @Mapper 13 | public interface ListProductEntityToListProductMapper { 14 | 15 | ProductEntityToProductMapper productEntityToProductMapper = Mappers.getMapper(ProductEntityToProductMapper.class); 16 | 17 | default List toProductList(List productEntities) { 18 | 19 | if (productEntities == null) { 20 | return null; 21 | } 22 | 23 | return productEntities.stream() 24 | .map(productEntityToProductMapper::map) 25 | .collect(Collectors.toList()); 26 | 27 | } 28 | 29 | 30 | static ListProductEntityToListProductMapper initialize() { 31 | return Mappers.getMapper(ListProductEntityToListProductMapper.class); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/mapper/ProductCreateRequestToProductEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 4 | import com.springboot.springbootsecurity.product.model.dto.request.ProductCreateRequest; 5 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Named; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface ProductCreateRequestToProductEntityMapper extends BaseMapper { 12 | 13 | 14 | @Named("mapForSaving") 15 | default ProductEntity mapForSaving(ProductCreateRequest productCreateRequest) { 16 | return ProductEntity.builder() 17 | .amount(productCreateRequest.getAmount()) 18 | .name(productCreateRequest.getName()) 19 | .unitPrice(productCreateRequest.getUnitPrice()) 20 | .build(); 21 | } 22 | 23 | static ProductCreateRequestToProductEntityMapper initialize() { 24 | return Mappers.getMapper(ProductCreateRequestToProductEntityMapper.class); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/mapper/ProductEntityToProductMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.mapper; 2 | 3 | 4 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 5 | import com.springboot.springbootsecurity.product.model.Product; 6 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface ProductEntityToProductMapper extends BaseMapper { 12 | 13 | @Override 14 | Product map(ProductEntity source); 15 | 16 | static ProductEntityToProductMapper initialize() { 17 | return Mappers.getMapper(ProductEntityToProductMapper.class); 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/mapper/ProductToProductEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.mapper; 2 | 3 | 4 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 5 | import com.springboot.springbootsecurity.product.model.Product; 6 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface ProductToProductEntityMapper extends BaseMapper { 12 | 13 | @Override 14 | ProductEntity map(Product source); 15 | 16 | static ProductToProductEntityMapper initialize() { 17 | return Mappers.getMapper(ProductToProductEntityMapper.class); 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/mapper/ProductToProductResponseMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 4 | import com.springboot.springbootsecurity.product.model.Product; 5 | import com.springboot.springbootsecurity.product.model.dto.response.ProductResponse; 6 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface ProductToProductResponseMapper extends BaseMapper { 12 | 13 | @Override 14 | ProductResponse map(Product source); 15 | 16 | static ProductToProductResponseMapper initialize() { 17 | return Mappers.getMapper(ProductToProductResponseMapper.class); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/model/mapper/ProductUpdateRequestToProductEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 4 | import com.springboot.springbootsecurity.product.model.dto.request.ProductUpdateRequest; 5 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Named; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface ProductUpdateRequestToProductEntityMapper extends BaseMapper { 12 | 13 | 14 | @Named("mapForUpdating") 15 | default void mapForUpdating(ProductEntity productEntityToBeUpdate, ProductUpdateRequest productUpdateRequest) { 16 | productEntityToBeUpdate.setName(productUpdateRequest.getName()); 17 | productEntityToBeUpdate.setAmount(productUpdateRequest.getAmount()); 18 | productEntityToBeUpdate.setUnitPrice(productUpdateRequest.getUnitPrice()); 19 | } 20 | 21 | static ProductUpdateRequestToProductEntityMapper initialize() { 22 | return Mappers.getMapper(ProductUpdateRequestToProductEntityMapper.class); 23 | } 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.repository; 2 | 3 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface ProductRepository extends JpaRepository { 7 | 8 | boolean existsProductEntityByName(final String name); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/ProductCreateService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service; 2 | 3 | import com.springboot.springbootsecurity.product.model.Product; 4 | import com.springboot.springbootsecurity.product.model.dto.request.ProductCreateRequest; 5 | 6 | public interface ProductCreateService { 7 | 8 | Product createProduct(final ProductCreateRequest productCreateRequest); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/ProductDeleteService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service; 2 | 3 | public interface ProductDeleteService { 4 | 5 | void deleteProductById(final String productId); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/ProductReadService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service; 2 | 3 | import com.springboot.springbootsecurity.common.model.CustomPage; 4 | import com.springboot.springbootsecurity.product.model.Product; 5 | import com.springboot.springbootsecurity.product.model.dto.request.ProductPagingRequest; 6 | 7 | public interface ProductReadService { 8 | 9 | Product getProductById(final String productId); 10 | 11 | CustomPage getProducts(final ProductPagingRequest productPagingRequest); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/ProductUpdateService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service; 2 | 3 | import com.springboot.springbootsecurity.product.model.Product; 4 | import com.springboot.springbootsecurity.product.model.dto.request.ProductUpdateRequest; 5 | 6 | public interface ProductUpdateService { 7 | 8 | Product updateProductById(final String productId, final ProductUpdateRequest productUpdateRequest); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/impl/ProductCreateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.product.exception.ProductAlreadyExistException; 4 | import com.springboot.springbootsecurity.product.model.Product; 5 | import com.springboot.springbootsecurity.product.model.dto.request.ProductCreateRequest; 6 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 7 | import com.springboot.springbootsecurity.product.model.mapper.ProductCreateRequestToProductEntityMapper; 8 | import com.springboot.springbootsecurity.product.model.mapper.ProductEntityToProductMapper; 9 | import com.springboot.springbootsecurity.product.repository.ProductRepository; 10 | import com.springboot.springbootsecurity.product.service.ProductCreateService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Service; 13 | 14 | @Service 15 | @RequiredArgsConstructor 16 | public class ProductCreateServiceImpl implements ProductCreateService { 17 | 18 | private final ProductRepository productRepository; 19 | 20 | private final ProductCreateRequestToProductEntityMapper productCreateRequestToProductEntityMapper = 21 | ProductCreateRequestToProductEntityMapper.initialize(); 22 | 23 | private final ProductEntityToProductMapper productEntityToProductMapper = ProductEntityToProductMapper.initialize(); 24 | 25 | @Override 26 | public Product createProduct(ProductCreateRequest productCreateRequest) { 27 | 28 | checkUniquenessProductName(productCreateRequest.getName()); 29 | 30 | final ProductEntity productEntityToBeSave = productCreateRequestToProductEntityMapper.mapForSaving(productCreateRequest); 31 | 32 | ProductEntity savedProductEntity = productRepository.save(productEntityToBeSave); 33 | 34 | return productEntityToProductMapper.map(savedProductEntity); 35 | 36 | } 37 | 38 | private void checkUniquenessProductName(final String productName) { 39 | if (productRepository.existsProductEntityByName(productName)) { 40 | throw new ProductAlreadyExistException("There is another product with given name: " + productName); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/impl/ProductDeleteServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.product.exception.ProductNotFoundException; 4 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 5 | import com.springboot.springbootsecurity.product.repository.ProductRepository; 6 | import com.springboot.springbootsecurity.product.service.ProductDeleteService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class ProductDeleteServiceImpl implements ProductDeleteService { 13 | 14 | private final ProductRepository productRepository; 15 | 16 | @Override 17 | public void deleteProductById(String productId) { 18 | 19 | final ProductEntity productEntityToBeDelete = productRepository 20 | .findById(productId) 21 | .orElseThrow(() -> new ProductNotFoundException("With given productID = " + productId)); 22 | 23 | productRepository.delete(productEntityToBeDelete); 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/impl/ProductReadServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.common.model.CustomPage; 4 | import com.springboot.springbootsecurity.product.exception.ProductNotFoundException; 5 | import com.springboot.springbootsecurity.product.model.Product; 6 | import com.springboot.springbootsecurity.product.model.dto.request.ProductPagingRequest; 7 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 8 | import com.springboot.springbootsecurity.product.model.mapper.ListProductEntityToListProductMapper; 9 | import com.springboot.springbootsecurity.product.model.mapper.ProductEntityToProductMapper; 10 | import com.springboot.springbootsecurity.product.repository.ProductRepository; 11 | import com.springboot.springbootsecurity.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 | @Service 19 | @RequiredArgsConstructor 20 | public class ProductReadServiceImpl implements ProductReadService { 21 | 22 | private final ProductRepository productRepository; 23 | 24 | private final ProductEntityToProductMapper productEntityToProductMapper = ProductEntityToProductMapper.initialize(); 25 | 26 | private final ListProductEntityToListProductMapper listProductEntityToListProductMapper = 27 | ListProductEntityToListProductMapper.initialize(); 28 | 29 | @Override 30 | public Product getProductById(String productId) { 31 | 32 | final ProductEntity productEntityFromDB = productRepository 33 | .findById(productId) 34 | .orElseThrow(() -> new ProductNotFoundException("With given productID = " + productId)); 35 | 36 | return productEntityToProductMapper.map(productEntityFromDB); 37 | } 38 | 39 | @Override 40 | public CustomPage getProducts(ProductPagingRequest productPagingRequest) { 41 | 42 | final Page productEntityPage = productRepository.findAll(productPagingRequest.toPageable()); 43 | 44 | if (productEntityPage.getContent().isEmpty()) { 45 | throw new ProductNotFoundException("Couldn't find any Product"); 46 | } 47 | 48 | final List productDomainModels = listProductEntityToListProductMapper 49 | .toProductList(productEntityPage.getContent()); 50 | 51 | return CustomPage.of(productDomainModels, productEntityPage); 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/product/service/impl/ProductUpdateServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.product.exception.ProductAlreadyExistException; 4 | import com.springboot.springbootsecurity.product.exception.ProductNotFoundException; 5 | import com.springboot.springbootsecurity.product.model.Product; 6 | import com.springboot.springbootsecurity.product.model.dto.request.ProductUpdateRequest; 7 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 8 | import com.springboot.springbootsecurity.product.model.mapper.ProductEntityToProductMapper; 9 | import com.springboot.springbootsecurity.product.model.mapper.ProductUpdateRequestToProductEntityMapper; 10 | import com.springboot.springbootsecurity.product.repository.ProductRepository; 11 | import com.springboot.springbootsecurity.product.service.ProductUpdateService; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class ProductUpdateServiceImpl implements ProductUpdateService { 18 | 19 | private final ProductRepository productRepository; 20 | 21 | private final ProductUpdateRequestToProductEntityMapper productUpdateRequestToProductEntityMapper = 22 | ProductUpdateRequestToProductEntityMapper.initialize(); 23 | 24 | private final ProductEntityToProductMapper productEntityToProductMapper = 25 | ProductEntityToProductMapper.initialize(); 26 | 27 | @Override 28 | public Product updateProductById(String productId, ProductUpdateRequest productUpdateRequest) { 29 | 30 | checkProductNameUniqueness(productUpdateRequest.getName()); 31 | 32 | final ProductEntity productEntityToBeUpdate = productRepository 33 | .findById(productId) 34 | .orElseThrow(() -> new ProductNotFoundException("With given productID = " + productId)); 35 | 36 | productUpdateRequestToProductEntityMapper.mapForUpdating(productEntityToBeUpdate, productUpdateRequest); 37 | 38 | ProductEntity updatedProductEntity = productRepository.save(productEntityToBeUpdate); 39 | 40 | return productEntityToProductMapper.map(updatedProductEntity); 41 | 42 | } 43 | 44 | private void checkProductNameUniqueness(final String productName) { 45 | if (productRepository.existsProductEntityByName(productName)) { 46 | throw new ProductAlreadyExistException("With given product name = " + productName); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/controller/UserAuthController.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.controller; 2 | 3 | import com.springboot.springbootsecurity.auth.model.Token; 4 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 5 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 6 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 7 | import com.springboot.springbootsecurity.auth.model.dto.response.TokenResponse; 8 | import com.springboot.springbootsecurity.auth.model.mapper.TokenToTokenResponseMapper; 9 | import com.springboot.springbootsecurity.common.model.dto.response.CustomResponse; 10 | import com.springboot.springbootsecurity.user.model.dto.request.UserRegisterRequest; 11 | import com.springboot.springbootsecurity.user.service.UserLoginService; 12 | import com.springboot.springbootsecurity.user.service.UserLogoutService; 13 | import com.springboot.springbootsecurity.user.service.UserRefreshTokenService; 14 | import com.springboot.springbootsecurity.user.service.UserRegisterService; 15 | import jakarta.validation.Valid; 16 | import lombok.RequiredArgsConstructor; 17 | import org.springframework.security.access.prepost.PreAuthorize; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | 23 | @RestController 24 | @RequestMapping("/api/v1/authentication/user") 25 | @RequiredArgsConstructor 26 | public class UserAuthController { 27 | 28 | private final UserRegisterService userRegisterService; 29 | 30 | private final UserLoginService userLoginService; 31 | 32 | private final UserRefreshTokenService userRefreshTokenService; 33 | 34 | private final UserLogoutService userLogoutService; 35 | 36 | private TokenToTokenResponseMapper tokenToTokenResponseMapper = TokenToTokenResponseMapper.initialize(); 37 | 38 | @PostMapping("/register") 39 | public CustomResponse registerUser(@RequestBody @Valid final UserRegisterRequest userRegisterRequest) { 40 | userRegisterService.registerUser(userRegisterRequest); 41 | return CustomResponse.SUCCESS; 42 | } 43 | 44 | @PostMapping("/login") 45 | public CustomResponse loginUser(@RequestBody @Valid final LoginRequest loginRequest) { 46 | final Token token = userLoginService.login(loginRequest); 47 | final TokenResponse tokenResponse = tokenToTokenResponseMapper.map(token); 48 | return CustomResponse.successOf(tokenResponse); 49 | } 50 | 51 | @PostMapping("/refresh-token") 52 | public CustomResponse refreshToken(@RequestBody @Valid final TokenRefreshRequest tokenRefreshRequest) { 53 | final Token token = userRefreshTokenService.refreshToken(tokenRefreshRequest); 54 | final TokenResponse tokenResponse = tokenToTokenResponseMapper.map(token); 55 | return CustomResponse.successOf(tokenResponse); 56 | } 57 | 58 | @PostMapping("/logout") 59 | @PreAuthorize("hasAnyAuthority('USER')") 60 | public CustomResponse logout(@RequestBody @Valid final TokenInvalidateRequest tokenInvalidateRequest) { 61 | userLogoutService.logout(tokenInvalidateRequest); 62 | return CustomResponse.SUCCESS; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/exception/UserAlreadyExistException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class UserAlreadyExistException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = -884805778962079313L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | User already exist! 12 | """; 13 | 14 | public UserAlreadyExistException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public UserAlreadyExistException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.exception; 2 | 3 | import java.io.Serial; 4 | 5 | public class UserNotFoundException extends RuntimeException { 6 | 7 | @Serial 8 | private static final long serialVersionUID = -3952215105519401565L; 9 | 10 | private static final String DEFAULT_MESSAGE = """ 11 | User not found! 12 | """; 13 | 14 | public UserNotFoundException() { 15 | super(DEFAULT_MESSAGE); 16 | } 17 | 18 | public UserNotFoundException(final String message) { 19 | super(DEFAULT_MESSAGE + " " + message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/model/User.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.model; 2 | 3 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 4 | import com.springboot.springbootsecurity.auth.model.enums.UserType; 5 | import com.springboot.springbootsecurity.common.model.BaseDomainModel; 6 | import lombok.*; 7 | import lombok.experimental.SuperBuilder; 8 | 9 | @Getter 10 | @Setter 11 | @SuperBuilder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @EqualsAndHashCode(callSuper = true) 15 | public class User extends BaseDomainModel { 16 | 17 | private String id; 18 | private String email; 19 | private String password; 20 | private String firstName; 21 | private String lastName; 22 | private String phoneNumber; 23 | 24 | @Builder.Default 25 | private UserType userType = UserType.USER; 26 | 27 | @Builder.Default 28 | private UserStatus userStatus = UserStatus.ACTIVE; 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/model/dto/request/UserRegisterRequest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.model.dto.request; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Size; 6 | import lombok.*; 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | public class UserRegisterRequest { 14 | 15 | @Email(message = "Please enter valid e-mail address") 16 | @Size(min = 7, message = "Minimum e-mail length is 7 characters.") 17 | private String email; 18 | 19 | @Size(min = 8) 20 | private String password; 21 | 22 | @NotBlank(message = "First name can't be blank.") 23 | private String firstName; 24 | 25 | @NotBlank(message = "Last name can't be blank.") 26 | private String lastName; 27 | 28 | @NotBlank(message = "Phone number can't be blank.") 29 | @Size(min = 11, max = 20) 30 | private String phoneNumber; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/model/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.model.entity; 2 | 3 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 4 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 5 | import com.springboot.springbootsecurity.auth.model.enums.UserType; 6 | import com.springboot.springbootsecurity.common.model.entity.BaseEntity; 7 | import jakarta.persistence.*; 8 | import lombok.*; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | @Getter 15 | @Setter 16 | @SuperBuilder 17 | @EqualsAndHashCode(callSuper = true) 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @Entity 21 | @Table(name = "CASE_USER") 22 | public class UserEntity extends BaseEntity { 23 | 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.UUID) 26 | @Column(name = "ID") 27 | private String id; 28 | 29 | @Column(name = "EMAIL") 30 | private String email; 31 | 32 | @Column(name = "PASSWORD") 33 | private String password; 34 | 35 | @Column(name = "FIRST_NAME") 36 | private String firstName; 37 | 38 | @Column(name = "LAST_NAME") 39 | private String lastName; 40 | 41 | @Column( 42 | name = "PHONE_NUMBER", 43 | length = 20 44 | ) 45 | private String phoneNumber; 46 | 47 | @Builder.Default 48 | @Enumerated(EnumType.STRING) 49 | private UserType userType = UserType.USER; 50 | 51 | @Builder.Default 52 | @Enumerated(EnumType.STRING) 53 | private UserStatus userStatus = UserStatus.ACTIVE; 54 | 55 | public Map getClaims() { 56 | 57 | final Map claims = new HashMap<>(); 58 | 59 | claims.put(TokenClaims.USER_ID.getValue(), this.id); 60 | claims.put(TokenClaims.USER_TYPE.getValue(), this.userType); 61 | claims.put(TokenClaims.USER_STATUS.getValue(), this.userStatus); 62 | claims.put(TokenClaims.USER_FIRST_NAME.getValue(), this.firstName); 63 | claims.put(TokenClaims.USER_LAST_NAME.getValue(), this.lastName); 64 | claims.put(TokenClaims.USER_EMAIL.getValue(), this.email); 65 | claims.put(TokenClaims.USER_PHONE_NUMBER.getValue(), this.phoneNumber); 66 | 67 | return claims; 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/model/mapper/UserEntityToUserMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 4 | import com.springboot.springbootsecurity.user.model.User; 5 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.factory.Mappers; 8 | 9 | @Mapper 10 | public interface UserEntityToUserMapper extends BaseMapper { 11 | 12 | @Override 13 | User map(UserEntity source); 14 | 15 | static UserEntityToUserMapper initialize() { 16 | return Mappers.getMapper(UserEntityToUserMapper.class); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/model/mapper/UserRegisterRequestToUserEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.model.mapper; 2 | 3 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 4 | import com.springboot.springbootsecurity.user.model.dto.request.UserRegisterRequest; 5 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Named; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface UserRegisterRequestToUserEntityMapper extends BaseMapper { 12 | 13 | 14 | @Named("mapForSaving") 15 | default UserEntity mapForSaving(UserRegisterRequest userRegisterRequest) { 16 | return UserEntity.builder() 17 | .email(userRegisterRequest.getEmail()) 18 | .firstName(userRegisterRequest.getFirstName()) 19 | .lastName(userRegisterRequest.getLastName()) 20 | .phoneNumber(userRegisterRequest.getPhoneNumber()) 21 | .build(); 22 | } 23 | 24 | static UserRegisterRequestToUserEntityMapper initialize() { 25 | return Mappers.getMapper(UserRegisterRequestToUserEntityMapper.class); 26 | } 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/model/mapper/UserToUserEntityMapper.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.model.mapper; 2 | 3 | 4 | import com.springboot.springbootsecurity.common.model.mapper.BaseMapper; 5 | import com.springboot.springbootsecurity.user.model.User; 6 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.factory.Mappers; 9 | 10 | @Mapper 11 | public interface UserToUserEntityMapper extends BaseMapper { 12 | 13 | @Override 14 | UserEntity map(User source); 15 | 16 | static UserToUserEntityMapper initialize() { 17 | return Mappers.getMapper(UserToUserEntityMapper.class); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.repository; 2 | 3 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface UserRepository extends JpaRepository { 9 | 10 | boolean existsUserEntityByEmail(final String email); 11 | 12 | Optional findUserEntityByEmail(final String email); 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/UserLoginService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service; 2 | 3 | import com.springboot.springbootsecurity.auth.model.Token; 4 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 5 | 6 | public interface UserLoginService { 7 | 8 | Token login(final LoginRequest loginRequest); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/UserLogoutService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service; 2 | 3 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 4 | 5 | public interface UserLogoutService { 6 | 7 | void logout(final TokenInvalidateRequest tokenInvalidateRequest); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/UserRefreshTokenService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service; 2 | 3 | import com.springboot.springbootsecurity.auth.model.Token; 4 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 5 | 6 | public interface UserRefreshTokenService { 7 | 8 | Token refreshToken(final TokenRefreshRequest tokenRefreshRequest); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/UserRegisterService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service; 2 | 3 | 4 | import com.springboot.springbootsecurity.user.model.User; 5 | import com.springboot.springbootsecurity.user.model.dto.request.UserRegisterRequest; 6 | 7 | public interface UserRegisterService { 8 | 9 | User registerUser(final UserRegisterRequest userRegisterRequest); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/impl/UserLoginServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.exception.PasswordNotValidException; 4 | import com.springboot.springbootsecurity.auth.model.Token; 5 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 6 | import com.springboot.springbootsecurity.auth.service.TokenService; 7 | import com.springboot.springbootsecurity.user.exception.UserNotFoundException; 8 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 9 | import com.springboot.springbootsecurity.user.repository.UserRepository; 10 | import com.springboot.springbootsecurity.user.service.UserLoginService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class UserLoginServiceImpl implements UserLoginService { 18 | 19 | private final UserRepository userRepository; 20 | 21 | private final PasswordEncoder passwordEncoder; 22 | 23 | private final TokenService tokenService; 24 | 25 | @Override 26 | public Token login(LoginRequest loginRequest) { 27 | 28 | final UserEntity userEntityFromDB = userRepository 29 | .findUserEntityByEmail(loginRequest.getEmail()) 30 | .orElseThrow( 31 | () -> new UserNotFoundException("Can't find with given email: " 32 | + loginRequest.getEmail()) 33 | ); 34 | 35 | if (Boolean.FALSE.equals(passwordEncoder.matches( 36 | loginRequest.getPassword(), userEntityFromDB.getPassword()))) { 37 | throw new PasswordNotValidException(); 38 | } 39 | 40 | return tokenService.generateToken(userEntityFromDB.getClaims()); 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/impl/UserLogoutServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 4 | import com.springboot.springbootsecurity.auth.service.InvalidTokenService; 5 | import com.springboot.springbootsecurity.auth.service.TokenService; 6 | import com.springboot.springbootsecurity.user.service.UserLogoutService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Set; 11 | 12 | @Service 13 | @RequiredArgsConstructor 14 | public class UserLogoutServiceImpl implements UserLogoutService { 15 | 16 | private final TokenService tokenService; 17 | 18 | private final InvalidTokenService invalidTokenService; 19 | 20 | @Override 21 | public void logout(final TokenInvalidateRequest tokenInvalidateRequest) { 22 | 23 | tokenService.verifyAndValidate( 24 | Set.of( 25 | tokenInvalidateRequest.getAccessToken(), 26 | tokenInvalidateRequest.getRefreshToken() 27 | ) 28 | ); 29 | 30 | final String accessTokenId = tokenService 31 | .getPayload(tokenInvalidateRequest.getAccessToken()) 32 | .getId(); 33 | 34 | invalidTokenService.checkForInvalidityOfToken(accessTokenId); 35 | 36 | 37 | final String refreshTokenId = tokenService 38 | .getPayload(tokenInvalidateRequest.getRefreshToken()) 39 | .getId(); 40 | 41 | invalidTokenService.checkForInvalidityOfToken(refreshTokenId); 42 | 43 | invalidTokenService.invalidateTokens(Set.of(accessTokenId,refreshTokenId)); 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/impl/UserRefreshTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.exception.UserStatusNotValidException; 4 | import com.springboot.springbootsecurity.auth.model.Token; 5 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 6 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 7 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 8 | import com.springboot.springbootsecurity.auth.service.TokenService; 9 | import com.springboot.springbootsecurity.user.exception.UserNotFoundException; 10 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 11 | import com.springboot.springbootsecurity.user.repository.UserRepository; 12 | import com.springboot.springbootsecurity.user.service.UserRefreshTokenService; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | @RequiredArgsConstructor 18 | public class UserRefreshTokenServiceImpl implements UserRefreshTokenService { 19 | 20 | private final UserRepository userRepository; 21 | 22 | private final TokenService tokenService; 23 | 24 | @Override 25 | public Token refreshToken(TokenRefreshRequest tokenRefreshRequest) { 26 | 27 | tokenService.verifyAndValidate(tokenRefreshRequest.getRefreshToken()); 28 | 29 | final String userId = tokenService 30 | .getPayload(tokenRefreshRequest.getRefreshToken()) 31 | .get(TokenClaims.USER_ID.getValue()) 32 | .toString(); 33 | 34 | final UserEntity userEntityFromDB = userRepository 35 | .findById(userId) 36 | .orElseThrow(UserNotFoundException::new); 37 | 38 | this.validateUserStatus(userEntityFromDB); 39 | 40 | return tokenService.generateToken( 41 | userEntityFromDB.getClaims(), 42 | tokenRefreshRequest.getRefreshToken() 43 | ); 44 | 45 | } 46 | 47 | private void validateUserStatus(final UserEntity userEntity) { 48 | if (!(UserStatus.ACTIVE.equals(userEntity.getUserStatus()))) { 49 | throw new UserStatusNotValidException("UserStatus = " + userEntity.getUserStatus()); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/springbootsecurity/user/service/impl/UserRegisterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.user.exception.UserAlreadyExistException; 4 | import com.springboot.springbootsecurity.user.model.User; 5 | import com.springboot.springbootsecurity.user.model.dto.request.UserRegisterRequest; 6 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 7 | import com.springboot.springbootsecurity.user.model.mapper.UserEntityToUserMapper; 8 | import com.springboot.springbootsecurity.user.model.mapper.UserRegisterRequestToUserEntityMapper; 9 | import com.springboot.springbootsecurity.user.repository.UserRepository; 10 | import com.springboot.springbootsecurity.user.service.UserRegisterService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.security.crypto.password.PasswordEncoder; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | @RequiredArgsConstructor 17 | public class UserRegisterServiceImpl implements UserRegisterService { 18 | 19 | private final UserRepository userRepository; 20 | 21 | private final UserRegisterRequestToUserEntityMapper userRegisterRequestToUserEntityMapper = UserRegisterRequestToUserEntityMapper.initialize(); 22 | 23 | private final UserEntityToUserMapper userEntityToUserMapper = UserEntityToUserMapper.initialize(); 24 | 25 | private final PasswordEncoder passwordEncoder; 26 | 27 | @Override 28 | public User registerUser(UserRegisterRequest userRegisterRequest) { 29 | 30 | if (userRepository.existsUserEntityByEmail(userRegisterRequest.getEmail())) { 31 | throw new UserAlreadyExistException("The email is already used for another admin : " + userRegisterRequest.getEmail()); 32 | } 33 | 34 | final UserEntity userEntityToBeSave = userRegisterRequestToUserEntityMapper.mapForSaving(userRegisterRequest); 35 | 36 | userEntityToBeSave.setPassword(passwordEncoder.encode(userRegisterRequest.getPassword())); 37 | 38 | UserEntity savedUserEntity = userRepository.save(userEntityToBeSave); 39 | 40 | return userEntityToUserMapper.map(savedUserEntity); 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 1223 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}/springbootsecurityexample 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 | -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/SpringBootSecurityApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootSecurityApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/admin/service/impl/AdminLoginServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.exception.AdminNotFoundException; 4 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 5 | import com.springboot.springbootsecurity.admin.repository.AdminRepository; 6 | import com.springboot.springbootsecurity.auth.exception.PasswordNotValidException; 7 | import com.springboot.springbootsecurity.auth.model.Token; 8 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 9 | import com.springboot.springbootsecurity.auth.service.TokenService; 10 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 11 | import com.springboot.springbootsecurity.builder.AdminEntityBuilder; 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 AdminLoginServiceImplTest extends AbstractBaseServiceTest { 23 | 24 | @InjectMocks 25 | private AdminLoginServiceImpl adminLoginService; 26 | 27 | @Mock 28 | private AdminRepository adminRepository; 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 AdminEntity adminEntity = new AdminEntityBuilder().withValidFields().build(); 46 | 47 | final Token expectedToken = Token.builder() 48 | .accessToken("mockAccessToken") 49 | .accessTokenExpiresAt(123456789L) 50 | .refreshToken("mockRefreshToken") 51 | .build(); 52 | 53 | // When 54 | when(adminRepository.findAdminEntityByEmail(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 = adminLoginService.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(adminRepository).findAdminEntityByEmail(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(adminRepository.findAdminEntityByEmail(loginRequest.getEmail())) 87 | .thenReturn(Optional.empty()); 88 | 89 | // Then 90 | AdminNotFoundException exception = assertThrows(AdminNotFoundException.class, 91 | () -> adminLoginService.login(loginRequest)); 92 | 93 | assertEquals("Admin not found!\n Can't find with given email: " + loginRequest.getEmail(), exception.getMessage()); 94 | 95 | // Verify 96 | verify(adminRepository).findAdminEntityByEmail(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 AdminEntity adminEntity = AdminEntity.builder() 111 | .email(loginRequest.getEmail()) 112 | .password("encodedPassword") 113 | .build(); 114 | 115 | // When 116 | when(adminRepository.findAdminEntityByEmail(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 | () -> adminLoginService.login(loginRequest)); 125 | 126 | assertNotNull(exception); 127 | 128 | // Verify 129 | verify(adminRepository).findAdminEntityByEmail(loginRequest.getEmail()); 130 | verify(passwordEncoder).matches(loginRequest.getPassword(), adminEntity.getPassword()); 131 | verifyNoInteractions(tokenService); 132 | 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/admin/service/impl/AdminLogoutServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 4 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 5 | import com.springboot.springbootsecurity.auth.service.InvalidTokenService; 6 | import com.springboot.springbootsecurity.auth.service.TokenService; 7 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 8 | import com.springboot.springbootsecurity.builder.AdminEntityBuilder; 9 | import com.springboot.springbootsecurity.builder.TokenBuilder; 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.mockito.Mockito.*; 18 | 19 | class AdminLogoutServiceImplTest extends AbstractBaseServiceTest { 20 | 21 | @InjectMocks 22 | private AdminLogoutServiceImpl adminLogoutService; 23 | 24 | @Mock 25 | private TokenService tokenService; 26 | 27 | @Mock 28 | private InvalidTokenService invalidTokenService; 29 | 30 | @Test 31 | void givenAccessTokenAndRefreshToken_whenLogoutForAdmin_thenReturnLogout() { 32 | 33 | // Given 34 | final AdminEntity mockAdminEntity = new AdminEntityBuilder().withValidFields().build(); 35 | 36 | final Claims mockAccessTokenClaims = TokenBuilder.getValidClaims( 37 | mockAdminEntity.getId(), 38 | mockAdminEntity.getFirstName() 39 | ); 40 | 41 | final Claims mockRefreshTokenClaims = TokenBuilder.getValidClaims( 42 | mockAdminEntity.getId(), 43 | mockAdminEntity.getFirstName() 44 | ); 45 | 46 | final String mockAccessTokenId = mockAccessTokenClaims.getId(); 47 | final String mockRefreshTokenId = mockRefreshTokenClaims.getId(); 48 | 49 | 50 | final String accessToken = "validAccessToken"; 51 | final String refreshToken = "validRefreshToken"; 52 | 53 | final TokenInvalidateRequest tokenInvalidateRequest = TokenInvalidateRequest.builder() 54 | .accessToken(accessToken) 55 | .refreshToken(refreshToken) 56 | .build(); 57 | 58 | // When 59 | doNothing().when(tokenService).verifyAndValidate(Set.of(accessToken, refreshToken)); 60 | when(tokenService.getPayload(accessToken)).thenReturn(mockAccessTokenClaims); 61 | doNothing().when(invalidTokenService).checkForInvalidityOfToken(mockAccessTokenId); 62 | when(tokenService.getPayload(refreshToken)).thenReturn(mockRefreshTokenClaims); 63 | doNothing().when(invalidTokenService).checkForInvalidityOfToken(mockRefreshTokenId); 64 | doNothing().when(invalidTokenService).invalidateTokens(Set.of(mockAccessTokenId, mockRefreshTokenId)); 65 | 66 | // Then 67 | adminLogoutService.logout(tokenInvalidateRequest); 68 | 69 | // Verify 70 | verify(tokenService).verifyAndValidate(Set.of(accessToken, refreshToken)); 71 | verify(tokenService, times(2)).getPayload(anyString()); 72 | verify(invalidTokenService, times(2)).checkForInvalidityOfToken(anyString()); 73 | verify(invalidTokenService).invalidateTokens(anySet()); 74 | 75 | } 76 | 77 | 78 | } -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/admin/service/impl/AdminRefreshTokenServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.exception.AdminNotFoundException; 4 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 5 | import com.springboot.springbootsecurity.admin.repository.AdminRepository; 6 | import com.springboot.springbootsecurity.auth.exception.UserStatusNotValidException; 7 | import com.springboot.springbootsecurity.auth.model.Token; 8 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 9 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 10 | import com.springboot.springbootsecurity.auth.service.TokenService; 11 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 12 | import com.springboot.springbootsecurity.builder.AdminEntityBuilder; 13 | import com.springboot.springbootsecurity.builder.TokenBuilder; 14 | import io.jsonwebtoken.Claims; 15 | import org.junit.jupiter.api.Test; 16 | import org.mockito.InjectMocks; 17 | import org.mockito.Mock; 18 | 19 | import java.util.Optional; 20 | 21 | import static org.junit.jupiter.api.Assertions.*; 22 | import static org.mockito.Mockito.*; 23 | 24 | class AdminRefreshTokenServiceImplTest extends AbstractBaseServiceTest { 25 | 26 | @InjectMocks 27 | private AdminRefreshTokenServiceImpl adminRefreshTokenService; 28 | 29 | @Mock 30 | private AdminRepository adminRepository; 31 | 32 | @Mock 33 | private TokenService tokenService; 34 | 35 | 36 | @Test 37 | void refreshToken_ValidRefreshToken_ReturnsToken() { 38 | 39 | // Given 40 | final String refreshTokenString = "mockRefreshToken"; 41 | final TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 42 | .refreshToken(refreshTokenString) 43 | .build(); 44 | 45 | final AdminEntity mockAdminUserEntity = new AdminEntityBuilder().withValidFields().build(); 46 | 47 | final Claims mockClaims = TokenBuilder.getValidClaims( 48 | mockAdminUserEntity.getId(), 49 | mockAdminUserEntity.getFirstName() 50 | ); 51 | 52 | final Token expectedToken = Token.builder() 53 | .accessToken("mockAccessToken") 54 | .accessTokenExpiresAt(123456789L) 55 | .refreshToken("newMockRefreshToken") 56 | .build(); 57 | 58 | doNothing().when(tokenService).verifyAndValidate(refreshTokenString); 59 | when(tokenService.getPayload(refreshTokenString)).thenReturn(mockClaims); 60 | when(adminRepository.findById(anyString())).thenReturn(Optional.of(mockAdminUserEntity)); 61 | when(tokenService.generateToken(mockAdminUserEntity.getClaims(), refreshTokenString)).thenReturn(expectedToken); 62 | 63 | // When 64 | Token actualToken = adminRefreshTokenService.refreshToken(tokenRefreshRequest); 65 | 66 | // Then 67 | assertNotNull(actualToken); 68 | assertEquals(expectedToken.getAccessToken(), actualToken.getAccessToken()); 69 | assertEquals(expectedToken.getAccessTokenExpiresAt(), actualToken.getAccessTokenExpiresAt()); 70 | assertEquals(expectedToken.getRefreshToken(), actualToken.getRefreshToken()); 71 | 72 | // Verify 73 | verify(tokenService).verifyAndValidate(refreshTokenString); 74 | verify(tokenService).getPayload(refreshTokenString); 75 | verify(adminRepository).findById(anyString()); 76 | verify(tokenService).generateToken(mockAdminUserEntity.getClaims(), refreshTokenString); 77 | 78 | } 79 | 80 | @Test 81 | void refreshToken_InvalidRefreshToken_ThrowsException() { 82 | 83 | // Given 84 | final String refreshTokenString = "invalidRefreshToken"; 85 | final TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 86 | .refreshToken(refreshTokenString) 87 | .build(); 88 | 89 | // When 90 | doThrow(RuntimeException.class).when(tokenService).verifyAndValidate(refreshTokenString); 91 | 92 | // Then 93 | assertThrows(RuntimeException.class, 94 | () -> adminRefreshTokenService.refreshToken(tokenRefreshRequest)); 95 | 96 | // Verify 97 | verify(tokenService).verifyAndValidate(refreshTokenString); 98 | verifyNoInteractions(adminRepository); 99 | 100 | } 101 | 102 | @Test 103 | void refreshToken_AdminNotFound_ThrowsException() { 104 | 105 | // Given 106 | final String refreshTokenString = "validRefreshToken"; 107 | final TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 108 | .refreshToken(refreshTokenString) 109 | .build(); 110 | 111 | final Claims mockClaims = TokenBuilder.getValidClaims("nonExistentAdminId", "John"); 112 | 113 | // When 114 | doNothing().when(tokenService).verifyAndValidate(refreshTokenString); 115 | when(tokenService.getPayload(refreshTokenString)).thenReturn(mockClaims); 116 | when(adminRepository.findById("nonExistentAdminId")).thenReturn(Optional.empty()); 117 | 118 | // Then 119 | AdminNotFoundException exception = assertThrows(AdminNotFoundException.class, 120 | () -> adminRefreshTokenService.refreshToken(tokenRefreshRequest)); 121 | 122 | assertEquals(""" 123 | Admin not found! 124 | """, exception.getMessage()); 125 | 126 | // Verify 127 | verify(tokenService).verifyAndValidate(refreshTokenString); 128 | verify(tokenService).getPayload(refreshTokenString); 129 | verify(adminRepository).findById("nonExistentAdminId"); 130 | 131 | } 132 | 133 | @Test 134 | void refreshToken_InactiveAdmin_ThrowsException() { 135 | 136 | // Given 137 | String refreshTokenString = "validRefreshToken"; 138 | TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 139 | .refreshToken(refreshTokenString) 140 | .build(); 141 | 142 | AdminEntity inactiveAdmin = new AdminEntityBuilder().withValidFields().withUserStatus(UserStatus.PASSIVE).build(); 143 | 144 | Claims mockClaims = TokenBuilder.getValidClaims(inactiveAdmin.getId(), inactiveAdmin.getFirstName()); 145 | 146 | // When 147 | doNothing().when(tokenService).verifyAndValidate(refreshTokenString); 148 | when(tokenService.getPayload(refreshTokenString)).thenReturn(mockClaims); 149 | when(adminRepository.findById(inactiveAdmin.getId())).thenReturn(Optional.of(inactiveAdmin)); 150 | 151 | // Then 152 | UserStatusNotValidException exception = assertThrows(UserStatusNotValidException.class, 153 | () -> adminRefreshTokenService.refreshToken(tokenRefreshRequest)); 154 | 155 | assertEquals("User status is not valid!\n UserStatus = PASSIVE", exception.getMessage()); 156 | 157 | // Verify 158 | verify(tokenService).verifyAndValidate(refreshTokenString); 159 | verify(tokenService).getPayload(refreshTokenString); 160 | verify(adminRepository).findById(inactiveAdmin.getId()); 161 | 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/admin/service/impl/AdminRegisterServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.admin.service.impl; 2 | 3 | import com.springboot.springbootsecurity.admin.exception.AdminAlreadyExistException; 4 | import com.springboot.springbootsecurity.admin.model.Admin; 5 | import com.springboot.springbootsecurity.admin.model.dto.request.AdminRegisterRequest; 6 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 7 | import com.springboot.springbootsecurity.admin.model.mapper.AdminEntityToAdminMapper; 8 | import com.springboot.springbootsecurity.admin.model.mapper.AdminRegisterRequestToAdminEntityMapper; 9 | import com.springboot.springbootsecurity.admin.repository.AdminRepository; 10 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.InjectMocks; 13 | import org.mockito.Mock; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | 16 | import static org.junit.Assert.assertThrows; 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.mockito.ArgumentMatchers.any; 19 | import static org.mockito.Mockito.*; 20 | 21 | class AdminRegisterServiceImplTest extends AbstractBaseServiceTest { 22 | 23 | @InjectMocks 24 | private AdminRegisterServiceImpl adminRegisterService; 25 | 26 | @Mock 27 | private AdminRepository adminRepository; 28 | 29 | @Mock 30 | private PasswordEncoder passwordEncoder; 31 | 32 | private AdminRegisterRequestToAdminEntityMapper adminRegisterRequestToAdminEntityMapper = AdminRegisterRequestToAdminEntityMapper.initialize(); 33 | 34 | private AdminEntityToAdminMapper adminEntityToAdminMapper = AdminEntityToAdminMapper.initialize(); 35 | 36 | 37 | @Test 38 | void givenAdminRegisterRequest_whenRegisterAdmin_thenReturnAdmin() { 39 | 40 | // Given 41 | final AdminRegisterRequest request = AdminRegisterRequest.builder() 42 | .email("test@example.com") 43 | .password("password123") 44 | .firstName("John") 45 | .lastName("Doe") 46 | .phoneNumber("1234567890") 47 | .build(); 48 | 49 | final String encodedPassword = "encodedPassword"; 50 | 51 | final AdminEntity adminEntity = adminRegisterRequestToAdminEntityMapper.mapForSaving(request); 52 | 53 | final Admin expected = adminEntityToAdminMapper.map(adminEntity); 54 | 55 | // When 56 | when(adminRepository.existsAdminEntityByEmail(request.getEmail())).thenReturn(false); 57 | when(passwordEncoder.encode(request.getPassword())).thenReturn(encodedPassword); 58 | when(adminRepository.save(any(AdminEntity.class))).thenReturn(adminEntity); 59 | 60 | // Then 61 | Admin result = adminRegisterService.registerAdmin(request); 62 | 63 | assertEquals(expected.getId(), result.getId()); 64 | assertEquals(expected.getEmail(), result.getEmail()); 65 | assertEquals(expected.getPhoneNumber(), result.getPhoneNumber()); 66 | assertEquals(expected.getFirstName(), result.getFirstName()); 67 | assertEquals(expected.getLastName(), result.getLastName()); 68 | 69 | // Verify 70 | verify(adminRepository).save(any(AdminEntity.class)); 71 | 72 | } 73 | 74 | @Test 75 | void givenAdminRegisterRequest_whenEmailAlreadyExists_thenThrowAdminAlreadyExistException() { 76 | 77 | // Given 78 | final AdminRegisterRequest request = AdminRegisterRequest.builder() 79 | .email("existing@example.com") 80 | .password("password123") 81 | .firstName("Jane") 82 | .lastName("Doe") 83 | .phoneNumber("9876543210") 84 | .build(); 85 | 86 | // When 87 | when(adminRepository.existsAdminEntityByEmail(request.getEmail())).thenReturn(true); 88 | 89 | // Then 90 | assertThrows(AdminAlreadyExistException.class, () -> adminRegisterService.registerAdmin(request)); 91 | 92 | // Verify 93 | verify(adminRepository, never()).save(any(AdminEntity.class)); 94 | 95 | } 96 | 97 | 98 | } -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/base/AbstractBaseServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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/test/java/com/springboot/springbootsecurity/base/AbstractRestControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.base; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.springboot.springbootsecurity.auth.config.TokenConfigurationParameter; 5 | import com.springboot.springbootsecurity.auth.model.Token; 6 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 7 | import com.springboot.springbootsecurity.builder.AdminEntityBuilder; 8 | import com.springboot.springbootsecurity.builder.UserEntityBuilder; 9 | import io.jsonwebtoken.JwtBuilder; 10 | import io.jsonwebtoken.Jwts; 11 | import org.apache.commons.lang3.time.DateUtils; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.extension.ExtendWith; 14 | import org.mockito.Mock; 15 | import org.mockito.junit.jupiter.MockitoExtension; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 18 | import org.springframework.boot.test.context.SpringBootTest; 19 | import org.springframework.security.oauth2.core.OAuth2AccessToken; 20 | import org.springframework.test.web.servlet.MockMvc; 21 | 22 | import java.util.Date; 23 | import java.util.Map; 24 | import java.util.UUID; 25 | 26 | @SpringBootTest 27 | @AutoConfigureMockMvc 28 | public class AbstractRestControllerTest extends AbstractTestContainerConfiguration { 29 | 30 | @Autowired 31 | protected MockMvc mockMvc; 32 | 33 | @Autowired 34 | protected ObjectMapper objectMapper; 35 | 36 | protected Token mockAdminToken; 37 | 38 | protected Token mockUserToken; 39 | 40 | @Mock 41 | private TokenConfigurationParameter tokenConfiguration; 42 | 43 | 44 | @BeforeEach 45 | public void initializeAuth() { 46 | 47 | this.tokenConfiguration = new TokenConfigurationParameter(); 48 | this.mockAdminToken = this.generate(new AdminEntityBuilder().withValidFields().build().getClaims()); 49 | this.mockUserToken = this.generate(new UserEntityBuilder().withValidFields().build().getClaims()); 50 | 51 | } 52 | 53 | private Token generate(Map claims) { 54 | 55 | final long currentTimeMillis = System.currentTimeMillis(); 56 | 57 | final Date tokenIssuedAt = new Date(currentTimeMillis); 58 | 59 | final Date accessTokenExpiresAt = DateUtils.addMinutes(new Date(currentTimeMillis), tokenConfiguration.getAccessTokenExpireMinute()); 60 | 61 | final String accessToken = Jwts.builder() 62 | .header() 63 | .add(TokenClaims.TYP.getValue(), OAuth2AccessToken.TokenType.BEARER.getValue()) 64 | .and() 65 | .id(UUID.randomUUID().toString()) 66 | .issuer(tokenConfiguration.getIssuer()) 67 | .issuedAt(tokenIssuedAt) 68 | .expiration(accessTokenExpiresAt) 69 | .signWith(tokenConfiguration.getPrivateKey()) 70 | .claims(claims) 71 | .compact(); 72 | 73 | final Date refreshTokenExpiresAt = DateUtils.addDays(new Date(currentTimeMillis), tokenConfiguration.getRefreshTokenExpireDay()); 74 | 75 | final JwtBuilder refreshTokenBuilder = Jwts.builder(); 76 | 77 | final String refreshToken = refreshTokenBuilder 78 | .header() 79 | .add(TokenClaims.TYP.getValue(), OAuth2AccessToken.TokenType.BEARER.getValue()) 80 | .and() 81 | .id(UUID.randomUUID().toString()) 82 | .issuer(tokenConfiguration.getIssuer()) 83 | .issuedAt(tokenIssuedAt) 84 | .expiration(refreshTokenExpiresAt) 85 | .signWith(tokenConfiguration.getPrivateKey()) 86 | .claim(TokenClaims.USER_ID.getValue(), claims.get(TokenClaims.USER_ID.getValue())) 87 | .compact(); 88 | 89 | return Token.builder() 90 | .accessToken(accessToken) 91 | .accessTokenExpiresAt(accessTokenExpiresAt.toInstant().getEpochSecond()) 92 | .refreshToken(refreshToken) 93 | .build(); 94 | 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/base/AbstractTestContainerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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 | 29 | -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/builder/AdminEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.builder; 2 | 3 | import com.springboot.springbootsecurity.admin.model.entity.AdminEntity; 4 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 5 | import com.springboot.springbootsecurity.auth.model.enums.UserType; 6 | 7 | import java.util.UUID; 8 | 9 | public class AdminEntityBuilder extends BaseBuilder { 10 | 11 | public AdminEntityBuilder() { 12 | super(AdminEntity.class); 13 | } 14 | 15 | 16 | public AdminEntityBuilder withValidFields() { 17 | return this 18 | .withId(UUID.randomUUID().toString()) 19 | .withEmail("adminexample@example.com") 20 | .withPassword("adminpassword") 21 | .withFirstName("John") 22 | .withLastName("Doe") 23 | .withPhoneNumber("1234567890") 24 | .withUserType(UserType.ADMIN) 25 | .withUserStatus(UserStatus.ACTIVE); 26 | } 27 | 28 | public AdminEntityBuilder withId(String id) { 29 | data.setId(id); 30 | return this; 31 | } 32 | 33 | public AdminEntityBuilder withEmail(String email) { 34 | data.setEmail(email); 35 | return this; 36 | } 37 | 38 | public AdminEntityBuilder withPassword(String password) { 39 | data.setPassword(password); 40 | return this; 41 | } 42 | 43 | public AdminEntityBuilder withFirstName(String firstName) { 44 | data.setFirstName(firstName); 45 | return this; 46 | } 47 | 48 | public AdminEntityBuilder withLastName(String lastName) { 49 | data.setLastName(lastName); 50 | return this; 51 | } 52 | 53 | public AdminEntityBuilder withPhoneNumber(String phoneNumber) { 54 | data.setPhoneNumber(phoneNumber); 55 | return this; 56 | } 57 | 58 | public AdminEntityBuilder withUserType(UserType userType) { 59 | data.setUserType(userType); 60 | return this; 61 | } 62 | 63 | public AdminEntityBuilder withUserStatus(UserStatus userStatus) { 64 | data.setUserStatus(userStatus); 65 | return this; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/builder/BaseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.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/springboot/springbootsecurity/builder/TokenBuilder.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.builder; 2 | 3 | import com.springboot.springbootsecurity.auth.model.enums.TokenClaims; 4 | import io.jsonwebtoken.Claims; 5 | import io.jsonwebtoken.Jwts; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.UUID; 10 | 11 | public class TokenBuilder { 12 | 13 | public static Claims getValidClaims(String userId, String firstName) { 14 | Map mockClaimsMap = new HashMap<>(); 15 | mockClaimsMap.put(TokenClaims.JWT_ID.getValue(), UUID.randomUUID().toString()); 16 | mockClaimsMap.put(TokenClaims.USER_ID.getValue(), userId); 17 | mockClaimsMap.put(TokenClaims.USER_FIRST_NAME.getValue(), firstName); 18 | return Jwts.claims(mockClaimsMap); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/builder/UserEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.builder; 2 | 3 | 4 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 5 | import com.springboot.springbootsecurity.auth.model.enums.UserType; 6 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 7 | 8 | import java.util.UUID; 9 | 10 | public class UserEntityBuilder extends BaseBuilder { 11 | 12 | public UserEntityBuilder() { 13 | super(UserEntity.class); 14 | } 15 | 16 | public UserEntityBuilder withValidFields() { 17 | return this 18 | .withId(UUID.randomUUID().toString()) 19 | .withEmail("userexample@example.com") 20 | .withPassword("userpassword") 21 | .withFirstName("John") 22 | .withLastName("Doe") 23 | .withPhoneNumber("1234567890") 24 | .withUserType(UserType.USER) 25 | .withUserStatus(UserStatus.ACTIVE); 26 | } 27 | 28 | public UserEntityBuilder withId(String id) { 29 | data.setId(id); 30 | return this; 31 | } 32 | 33 | public UserEntityBuilder withEmail(String email) { 34 | data.setEmail(email); 35 | return this; 36 | } 37 | 38 | public UserEntityBuilder withPassword(String password) { 39 | data.setPassword(password); 40 | return this; 41 | } 42 | 43 | public UserEntityBuilder withFirstName(String firstName) { 44 | data.setFirstName(firstName); 45 | return this; 46 | } 47 | 48 | public UserEntityBuilder withLastName(String lastName) { 49 | data.setLastName(lastName); 50 | return this; 51 | } 52 | 53 | public UserEntityBuilder withPhoneNumber(String phoneNumber) { 54 | data.setPhoneNumber(phoneNumber); 55 | return this; 56 | } 57 | 58 | public UserEntityBuilder withUserType(UserType userType) { 59 | data.setUserType(userType); 60 | return this; 61 | } 62 | 63 | public UserEntityBuilder withUserStatus(UserStatus userStatus) { 64 | data.setUserStatus(userStatus); 65 | return this; 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/product/service/impl/ProductCreateServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 4 | import com.springboot.springbootsecurity.product.exception.ProductAlreadyExistException; 5 | import com.springboot.springbootsecurity.product.model.Product; 6 | import com.springboot.springbootsecurity.product.model.dto.request.ProductCreateRequest; 7 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 8 | import com.springboot.springbootsecurity.product.model.mapper.ProductCreateRequestToProductEntityMapper; 9 | import com.springboot.springbootsecurity.product.model.mapper.ProductEntityToProductMapper; 10 | import com.springboot.springbootsecurity.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/test/java/com/springboot/springbootsecurity/product/service/impl/ProductDeleteServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 4 | import com.springboot.springbootsecurity.product.exception.ProductNotFoundException; 5 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 6 | import com.springboot.springbootsecurity.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/springboot/springbootsecurity/product/service/impl/ProductReadServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 4 | import com.springboot.springbootsecurity.common.model.CustomPage; 5 | import com.springboot.springbootsecurity.common.model.CustomPaging; 6 | import com.springboot.springbootsecurity.product.exception.ProductNotFoundException; 7 | import com.springboot.springbootsecurity.product.model.Product; 8 | import com.springboot.springbootsecurity.product.model.dto.request.ProductPagingRequest; 9 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 10 | import com.springboot.springbootsecurity.product.model.mapper.ListProductEntityToListProductMapper; 11 | import com.springboot.springbootsecurity.product.model.mapper.ProductEntityToProductMapper; 12 | import com.springboot.springbootsecurity.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/springboot/springbootsecurity/product/service/impl/ProductUpdateServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.product.service.impl; 2 | 3 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 4 | import com.springboot.springbootsecurity.product.exception.ProductAlreadyExistException; 5 | import com.springboot.springbootsecurity.product.exception.ProductNotFoundException; 6 | import com.springboot.springbootsecurity.product.model.Product; 7 | import com.springboot.springbootsecurity.product.model.dto.request.ProductUpdateRequest; 8 | import com.springboot.springbootsecurity.product.model.entity.ProductEntity; 9 | import com.springboot.springbootsecurity.product.model.mapper.ProductEntityToProductMapper; 10 | import com.springboot.springbootsecurity.product.model.mapper.ProductUpdateRequestToProductEntityMapper; 11 | import com.springboot.springbootsecurity.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 | } -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/user/service/impl/UserLoginServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.exception.PasswordNotValidException; 4 | import com.springboot.springbootsecurity.auth.model.Token; 5 | import com.springboot.springbootsecurity.auth.model.dto.request.LoginRequest; 6 | import com.springboot.springbootsecurity.auth.service.TokenService; 7 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 8 | import com.springboot.springbootsecurity.builder.UserEntityBuilder; 9 | import com.springboot.springbootsecurity.user.exception.UserNotFoundException; 10 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 11 | import com.springboot.springbootsecurity.user.repository.UserRepository; 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 UserLoginServiceImplTest extends AbstractBaseServiceTest { 23 | 24 | @InjectMocks 25 | private UserLoginServiceImpl userLoginService; 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 | LoginRequest loginRequest = LoginRequest.builder() 41 | .email("test@example.com") 42 | .password("password123") 43 | .build(); 44 | 45 | UserEntity userEntity = new UserEntityBuilder().withValidFields().build(); 46 | 47 | 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(userEntity)); 56 | 57 | when(passwordEncoder.matches(loginRequest.getPassword(), userEntity.getPassword())) 58 | .thenReturn(true); 59 | 60 | when(tokenService.generateToken(userEntity.getClaims())).thenReturn(expectedToken); 61 | 62 | Token actualToken = userLoginService.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(), userEntity.getPassword()); 72 | verify(tokenService).generateToken(userEntity.getClaims()); 73 | 74 | } 75 | 76 | @Test 77 | void login_InvalidEmail_ThrowsAdminNotFoundException() { 78 | 79 | // Given 80 | 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 | () -> userLoginService.login(loginRequest)); 92 | 93 | assertEquals("User not found!\n Can't find with given email: " + 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 | LoginRequest loginRequest = LoginRequest.builder() 106 | .email("test@example.com") 107 | .password("invalidPassword") 108 | .build(); 109 | 110 | UserEntity userEntity = new UserEntityBuilder() 111 | .withEmail(loginRequest.getEmail()) 112 | .withPassword("encodedPassword") 113 | .build(); 114 | 115 | // When 116 | when(userRepository.findUserEntityByEmail(loginRequest.getEmail())) 117 | .thenReturn(Optional.of(userEntity)); 118 | 119 | when(passwordEncoder.matches(loginRequest.getPassword(), userEntity.getPassword())) 120 | .thenReturn(false); 121 | 122 | // Then 123 | PasswordNotValidException exception = assertThrows(PasswordNotValidException.class, 124 | () -> userLoginService.login(loginRequest)); 125 | 126 | assertNotNull(exception); 127 | 128 | // Verify 129 | verify(userRepository).findUserEntityByEmail(loginRequest.getEmail()); 130 | verify(passwordEncoder).matches(loginRequest.getPassword(), userEntity.getPassword()); 131 | verifyNoInteractions(tokenService); 132 | 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/user/service/impl/UserLogoutServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenInvalidateRequest; 4 | import com.springboot.springbootsecurity.auth.service.InvalidTokenService; 5 | import com.springboot.springbootsecurity.auth.service.TokenService; 6 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 7 | import com.springboot.springbootsecurity.builder.TokenBuilder; 8 | import com.springboot.springbootsecurity.builder.UserEntityBuilder; 9 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 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.mockito.ArgumentMatchers.anySet; 18 | import static org.mockito.ArgumentMatchers.anyString; 19 | import static org.mockito.Mockito.*; 20 | 21 | class UserLogoutServiceImplTest extends AbstractBaseServiceTest { 22 | 23 | @InjectMocks 24 | private UserLogoutServiceImpl userLogoutService; 25 | 26 | @Mock 27 | private TokenService tokenService; 28 | 29 | @Mock 30 | private InvalidTokenService invalidTokenService; 31 | 32 | @Test 33 | void givenAccessTokenAndRefreshToken_whenLogoutForUser_thenReturnLogout() { 34 | 35 | UserEntity mockUserEntity = new UserEntityBuilder().withValidFields().build(); 36 | 37 | Claims mockAccessTokenClaims = TokenBuilder.getValidClaims( 38 | mockUserEntity.getId(), 39 | mockUserEntity.getFirstName() 40 | ); 41 | 42 | Claims mockRefreshTokenClaims = TokenBuilder.getValidClaims( 43 | mockUserEntity.getId(), 44 | mockUserEntity.getFirstName() 45 | ); 46 | 47 | String mockAccessTokenId = mockAccessTokenClaims.getId(); 48 | String mockRefreshTokenId = mockRefreshTokenClaims.getId(); 49 | 50 | // Given 51 | String accessToken = "validAccessToken"; 52 | String refreshToken = "validRefreshToken"; 53 | 54 | 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 | userLogoutService.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/test/java/com/springboot/springbootsecurity/user/service/impl/UserRefreshTokenServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.auth.exception.UserStatusNotValidException; 4 | import com.springboot.springbootsecurity.auth.model.Token; 5 | import com.springboot.springbootsecurity.auth.model.dto.request.TokenRefreshRequest; 6 | import com.springboot.springbootsecurity.auth.model.enums.UserStatus; 7 | import com.springboot.springbootsecurity.auth.service.TokenService; 8 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 9 | import com.springboot.springbootsecurity.builder.TokenBuilder; 10 | import com.springboot.springbootsecurity.builder.UserEntityBuilder; 11 | import com.springboot.springbootsecurity.user.exception.UserNotFoundException; 12 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 13 | import com.springboot.springbootsecurity.user.repository.UserRepository; 14 | import io.jsonwebtoken.Claims; 15 | import org.junit.jupiter.api.Test; 16 | import org.mockito.InjectMocks; 17 | import org.mockito.Mock; 18 | 19 | import java.util.Optional; 20 | 21 | import static org.junit.jupiter.api.Assertions.*; 22 | import static org.mockito.ArgumentMatchers.anyString; 23 | import static org.mockito.Mockito.*; 24 | 25 | class UserRefreshTokenServiceImplTest extends AbstractBaseServiceTest { 26 | 27 | @InjectMocks 28 | private UserRefreshTokenServiceImpl userRefreshTokenService; 29 | 30 | @Mock 31 | private UserRepository userRepository; 32 | 33 | @Mock 34 | private TokenService tokenService; 35 | 36 | 37 | @Test 38 | void refreshToken_ValidRefreshToken_ReturnsToken() { 39 | 40 | // Given 41 | String refreshTokenString = "mockRefreshToken"; 42 | TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 43 | .refreshToken(refreshTokenString) 44 | .build(); 45 | 46 | UserEntity mockUserEntity = new UserEntityBuilder().withValidFields().build(); 47 | 48 | Claims mockClaims = TokenBuilder.getValidClaims( 49 | mockUserEntity.getId(), 50 | mockUserEntity.getFirstName() 51 | ); 52 | 53 | Token expectedToken = Token.builder() 54 | .accessToken("mockAccessToken") 55 | .accessTokenExpiresAt(123456789L) 56 | .refreshToken("newMockRefreshToken") 57 | .build(); 58 | 59 | doNothing().when(tokenService).verifyAndValidate(refreshTokenString); 60 | when(tokenService.getPayload(refreshTokenString)).thenReturn(mockClaims); 61 | when(userRepository.findById(anyString())).thenReturn(Optional.of(mockUserEntity)); 62 | when(tokenService.generateToken(mockUserEntity.getClaims(), refreshTokenString)).thenReturn(expectedToken); 63 | 64 | // When 65 | Token actualToken = userRefreshTokenService.refreshToken(tokenRefreshRequest); 66 | 67 | // Then 68 | assertNotNull(actualToken); 69 | assertEquals(expectedToken.getAccessToken(), actualToken.getAccessToken()); 70 | assertEquals(expectedToken.getAccessTokenExpiresAt(), actualToken.getAccessTokenExpiresAt()); 71 | assertEquals(expectedToken.getRefreshToken(), actualToken.getRefreshToken()); 72 | 73 | // Verify 74 | verify(tokenService).verifyAndValidate(refreshTokenString); 75 | verify(tokenService).getPayload(refreshTokenString); 76 | verify(userRepository).findById(anyString()); 77 | verify(tokenService).generateToken(mockUserEntity.getClaims(), refreshTokenString); 78 | 79 | } 80 | 81 | @Test 82 | void refreshToken_InvalidRefreshToken_ThrowsException() { 83 | 84 | // Given 85 | String refreshTokenString = "invalidRefreshToken"; 86 | TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 87 | .refreshToken(refreshTokenString) 88 | .build(); 89 | 90 | // Mock the behavior of verifyAndValidate to throw an exception 91 | doThrow(RuntimeException.class).when(tokenService).verifyAndValidate(refreshTokenString); 92 | 93 | // When, Then & Verify 94 | assertThrows(RuntimeException.class, 95 | () -> userRefreshTokenService.refreshToken(tokenRefreshRequest)); 96 | 97 | // Verify that verifyAndValidate method was called with the expected argument 98 | verify(tokenService).verifyAndValidate(refreshTokenString); 99 | 100 | // Ensure no other interactions occurred 101 | verifyNoInteractions(userRepository); 102 | 103 | } 104 | 105 | @Test 106 | void refreshToken_UserNotFound_ThrowsException() { 107 | // Given 108 | String refreshTokenString = "validRefreshToken"; 109 | TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 110 | .refreshToken(refreshTokenString) 111 | .build(); 112 | 113 | Claims mockClaims = TokenBuilder.getValidClaims("nonExistentUserId", "John"); 114 | 115 | doNothing().when(tokenService).verifyAndValidate(refreshTokenString); 116 | when(tokenService.getPayload(refreshTokenString)).thenReturn(mockClaims); 117 | when(userRepository.findById("nonExistentUserId")).thenReturn(Optional.empty()); 118 | 119 | // When, Then & Verify 120 | UserNotFoundException exception = assertThrows(UserNotFoundException.class, 121 | () -> userRefreshTokenService.refreshToken(tokenRefreshRequest)); 122 | 123 | assertEquals(""" 124 | User not found! 125 | """, exception.getMessage()); 126 | 127 | verify(tokenService).verifyAndValidate(refreshTokenString); 128 | verify(tokenService).getPayload(refreshTokenString); 129 | verify(userRepository).findById("nonExistentUserId"); 130 | 131 | } 132 | 133 | @Test 134 | void refreshToken_InactiveAdmin_ThrowsException() { 135 | 136 | // Given 137 | String refreshTokenString = "validRefreshToken"; 138 | TokenRefreshRequest tokenRefreshRequest = TokenRefreshRequest.builder() 139 | .refreshToken(refreshTokenString) 140 | .build(); 141 | 142 | UserEntity inactiveUser = new UserEntityBuilder().withValidFields().withUserStatus(UserStatus.PASSIVE).build(); 143 | 144 | Claims mockClaims = TokenBuilder.getValidClaims(inactiveUser.getId(), inactiveUser.getFirstName()); 145 | 146 | doNothing().when(tokenService).verifyAndValidate(refreshTokenString); 147 | when(tokenService.getPayload(refreshTokenString)).thenReturn(mockClaims); 148 | when(userRepository.findById(inactiveUser.getId())).thenReturn(Optional.of(inactiveUser)); 149 | 150 | // When, Then & Verify 151 | UserStatusNotValidException exception = assertThrows(UserStatusNotValidException.class, 152 | () -> userRefreshTokenService.refreshToken(tokenRefreshRequest)); 153 | 154 | assertEquals("User status is not valid!\n UserStatus = PASSIVE", exception.getMessage()); 155 | 156 | verify(tokenService).verifyAndValidate(refreshTokenString); 157 | verify(tokenService).getPayload(refreshTokenString); 158 | verify(userRepository).findById(inactiveUser.getId()); 159 | 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /src/test/java/com/springboot/springbootsecurity/user/service/impl/UserRegisterServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.springboot.springbootsecurity.user.service.impl; 2 | 3 | import com.springboot.springbootsecurity.base.AbstractBaseServiceTest; 4 | import com.springboot.springbootsecurity.user.exception.UserAlreadyExistException; 5 | import com.springboot.springbootsecurity.user.model.User; 6 | import com.springboot.springbootsecurity.user.model.dto.request.UserRegisterRequest; 7 | import com.springboot.springbootsecurity.user.model.entity.UserEntity; 8 | import com.springboot.springbootsecurity.user.model.mapper.UserEntityToUserMapper; 9 | import com.springboot.springbootsecurity.user.model.mapper.UserRegisterRequestToUserEntityMapper; 10 | import com.springboot.springbootsecurity.user.repository.UserRepository; 11 | import org.junit.jupiter.api.Test; 12 | import org.mockito.InjectMocks; 13 | import org.mockito.Mock; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | 16 | import static org.junit.Assert.assertThrows; 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.mockito.ArgumentMatchers.any; 19 | import static org.mockito.Mockito.*; 20 | 21 | class UserRegisterServiceImplTest extends AbstractBaseServiceTest { 22 | 23 | @InjectMocks 24 | private UserRegisterServiceImpl userRegisterService; 25 | 26 | @Mock 27 | private UserRepository userRepository; 28 | 29 | @Mock 30 | private PasswordEncoder passwordEncoder; 31 | 32 | private UserRegisterRequestToUserEntityMapper userRegisterRequestToUserEntityMapper = UserRegisterRequestToUserEntityMapper.initialize(); 33 | 34 | private UserEntityToUserMapper userEntityToUserMapper = UserEntityToUserMapper.initialize(); 35 | 36 | 37 | @Test 38 | void givenUserRegisterRequest_whenRegisterUser_thenReturnUser() { 39 | 40 | // Given 41 | UserRegisterRequest request = UserRegisterRequest.builder() 42 | .email("test@example.com") 43 | .password("password123") 44 | .firstName("John") 45 | .lastName("Doe") 46 | .phoneNumber("1234567890") 47 | .build(); 48 | 49 | String encodedPassword = "encodedPassword"; 50 | 51 | UserEntity userEntity = userRegisterRequestToUserEntityMapper.mapForSaving(request); 52 | 53 | User expected = userEntityToUserMapper.map(userEntity); 54 | 55 | // When 56 | when(userRepository.existsUserEntityByEmail(request.getEmail())).thenReturn(false); 57 | when(passwordEncoder.encode(request.getPassword())).thenReturn(encodedPassword); 58 | when(userRepository.save(any(UserEntity.class))).thenReturn(userEntity); 59 | 60 | // Then 61 | User result = userRegisterService.registerUser(request); 62 | 63 | assertEquals(expected.getId(), result.getId()); 64 | assertEquals(expected.getEmail(), result.getEmail()); 65 | assertEquals(expected.getPhoneNumber(), result.getPhoneNumber()); 66 | assertEquals(expected.getFirstName(), result.getFirstName()); 67 | assertEquals(expected.getLastName(), result.getLastName()); 68 | 69 | // Verify 70 | verify(userRepository).save(any(UserEntity.class)); 71 | 72 | } 73 | 74 | @Test 75 | void givenUserRegisterRequest_whenEmailAlreadyExists_thenThrowUserAlreadyExistException() { 76 | 77 | // Given 78 | UserRegisterRequest request = UserRegisterRequest.builder() 79 | .email("test@example.com") 80 | .password("password123") 81 | .firstName("John") 82 | .lastName("Doe") 83 | .phoneNumber("1234567890") 84 | .build(); 85 | 86 | // When 87 | when(userRepository.existsUserEntityByEmail(request.getEmail())).thenReturn(true); 88 | 89 | // Then 90 | assertThrows(UserAlreadyExistException.class, () -> userRegisterService.registerUser(request)); 91 | 92 | // Verify 93 | verify(userRepository, never()).save(any(UserEntity.class)); 94 | 95 | } 96 | 97 | } --------------------------------------------------------------------------------