├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── docker-compose.yml ├── mvnw ├── mvnw.cmd ├── nb-configuration.xml ├── pom.xml └── src └── main ├── java └── io │ └── bootify │ └── java_spring_boot │ ├── JavaSpringBootApplication.java │ ├── config │ ├── DomainConfig.java │ ├── JacksonConfig.java │ ├── RoleInitializer.java │ └── SecurityConfig.java │ ├── controller │ └── AuthController.java │ ├── dto │ ├── SignInRequest.java │ ├── SignInResponse.java │ ├── SignUpRequest.java │ └── SignUpResponse.java │ ├── entity │ ├── Role.java │ └── User.java │ ├── repository │ ├── RoleRepository.java │ └── UserRepository.java │ ├── resource │ ├── HomeResource.java │ ├── ImageClassificationResource.java │ └── UserResource.java │ ├── security │ ├── CustomUserDetailsService.java │ └── jwt │ │ ├── JwtAuthEntryPoint.java │ │ ├── JwtAuthFilter.java │ │ └── JwtTokenProvider.java │ └── service │ ├── AuthService.java │ ├── UserService.java │ └── ai │ └── ImageClassificationService.java └── resources └── application.yml /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | application-local.yml 7 | *.log 8 | 9 | ## IntelliJ IDEA 10 | .idea 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ## VS Code 16 | .vscode/ 17 | 18 | ## STS 19 | .apt_generated 20 | .classpath 21 | .factorypath 22 | .project 23 | .settings 24 | .springBeans 25 | .sts4-cache 26 | 27 | ## NetBeans 28 | /nbproject/private/ 29 | /nbbuild/ 30 | /dist/ 31 | /nbdist/ 32 | /.nb-gradle/ 33 | build/ 34 | !**/src/main/**/build/ 35 | !**/src/test/**/build/ 36 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Thompsoni 4 | 5 | Support the Dev: 6 | BTC: bc1qpftdtjggrq8dpa6x6x7dnqvgv7ttc2x2m8rgvy 7 | ETH / POL / BNB: 0xdCC9f5281B8bb40B11A792C280aA2cdd434C34AF 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice, donation info and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Spring Boot 3 JWT Authentication Maven Boilerplate 2 | 3 | This repository provides a Java Spring Boot 3.3.5 Maven boilerplate, featuring a JWT authentication, role-based access control, PostgreSQL integration and Docker setup. 4 | The project also includes basic AI image classification using the Deep Java Library (DJL), making it ideal for rapid development of secure, AI-enabled web applications. 5 | 6 | ## Quick Start 7 | - Configure Lombok on your IDE (IntelliJ) 8 | - docker-compose up 9 | - mvn clean install 10 | - mvn clean package / Build the project from IDE 11 | - mvn spring-boot:run 12 | 13 | ## Features 14 | - JWT Authentication: Secure role-based access with JSON Web Tokens 15 | - PostgreSQL Integration: Efficient database management using PostgreSQL 16 | - Docker Support: Containerized setup for easy deployment and scaling 17 | - Maven and Lombok: Simplify project dependencies and reduce boilerplate code 18 | - Sample Endpoints: User auth and image classification test endpoints 19 | 20 | ## Tech Stack 21 | - Java 21 22 | - Spring Boot 3.3.5 23 | - Maven for dependency management 24 | - Lombok for reducing boilerplate Java code 25 | - PostgreSQL as the database 26 | - Docker for containerization 27 | - JSON Web Token (JWT) for secure authentication 28 | - DJL (Deep Java Library) for AI integration 29 | 30 | ## Configuration 31 | Update application.yml with your database configuration and JWT secret key. 32 | The key should be HS512. 33 | 34 | config/SecurityConfig contains a whitelist for endpoints. 35 | 36 | Test the setup by calling the endpoints. 37 | 38 | ``` 39 | spring: 40 | datasource: 41 | url: jdbc:postgresql://localhost:5432/your_db_name 42 | username: your_db_user 43 | password: your_db_password 44 | ``` 45 | 46 | ``` 47 | jwt: 48 | secret: your_jwt_secret_key 49 | ``` 50 | 51 | ## API Endpoints 52 | 53 | ### POST /auth/signup 54 | ``` 55 | { 56 | "firstName": "Satoshi", 57 | "lastName": "Nakamoto", 58 | "email": "satoshi.nakamoto@gmail.com", 59 | "password": "mysecretpw" 60 | } 61 | ``` 62 | 63 | ### POST /auth/signin 64 | ``` 65 | Include the token you received as Authorization Bearer token 66 | ``` 67 | 68 | ### GET /classify-image 69 | ``` 70 | Add url param imageUrl=https://urltoimg.jpg 71 | ``` 72 | 73 | ## JWT Authentication with Spring Security Overview 74 | This project uses Spring Security to configure JWT-based authentication, which ensures secure access to protected resources. 75 | 76 | ### SecurityFilterChain 77 | This configuration customizes Spring Security’s default behavior: 78 | - Disable Basic Authentication: Disables default form-based login. 79 | - Disable CSRF: Since this is a stateless REST API, CSRF is disabled. 80 | - Whitelist Endpoints: Configures public endpoints that do not require authentication, specified in WHITE_LIST_URL (e.g., /auth/** and /user/**). 81 | - Session Management: Sets the session policy to STATELESS to avoid creating sessions for each request, suitable for JWT. 82 | - Add JWT Filter: Adds a custom JwtAuthFilter to the filter chain before the UsernamePasswordAuthenticationFilter. This filter intercepts requests, validates the JWT token, and sets the user authentication context if valid. 83 | 84 | ### JwtAuthFilter 85 | This filter intercepts each request to: 86 | - Extract the JWT from the request headers. 87 | - Validate the token and, if valid, retrieve user details. 88 | - Set the Authentication object in the security context, enabling access to authenticated endpoints. 89 | 90 | ### AuthenticationProvider 91 | Configures authentication using DaoAuthenticationProvider, which integrates with UserDetailsService for loading user information and uses BCrypt for password encoding. 92 | AuthenticationEntryPoint: When configured, this entry point handles unauthorized access attempts by returning an appropriate HTTP error response (e.g., 401 Unauthorized). 93 | 94 | ## Development 95 | 96 | When starting the application `docker compose up` is called and the app will connect to the contained services. 97 | [Docker](https://www.docker.com/get-started/) must be available on the current system. 98 | 99 | During development it is recommended to use the profile `local`. In IntelliJ `-Dspring.profiles.active=local` can be 100 | added in the VM options of the Run Configuration after enabling this property in "Modify options". Create your own 101 | `application-local.yml` file to override settings for development. 102 | 103 | Lombok must be supported by your IDE. For IntelliJ install the Lombok plugin and enable annotation processing - 104 | [learn more](https://bootify.io/next-steps/spring-boot-with-lombok.html). 105 | 106 | After starting the application it is accessible under `localhost:8080`. 107 | 108 | ## Build 109 | 110 | The application can be built using the following command: 111 | 112 | ``` 113 | mvn clean package 114 | ``` 115 | 116 | Start your application with the following command - here with the profile `production`: 117 | 118 | ``` 119 | java -Dspring.profiles.active=production -jar ./target/java-spring-boot-0.0.1-SNAPSHOT.jar 120 | ``` 121 | 122 | If required, a Docker image can be created with the Spring Boot plugin. Add `SPRING_PROFILES_ACTIVE=production` as 123 | environment variable when running the container. 124 | 125 | ``` 126 | mvn spring-boot:build-image -Dspring-boot.build-image.imageName=io.bootify/java-spring-boot 127 | ``` 128 | 129 | ## License 130 | This project is licensed under the MIT License. See the LICENSE file for details. 131 | 132 | ## Keywords and SEO Tags 133 | - Java Spring Boot 3.3.5 boilerplate 134 | - JWT authentication with Spring Boot 135 | - Java Spring Boot 3 JWT boilerplate 136 | - Spring Boot and Docker setup 137 | - Java AI image classification 138 | - Spring Boot PostgreSQL integration 139 | - Java AI and JWT boilerplate project 140 | - Secure Java backend with JWT 141 | - Java 21 Spring Boot 3 JWT boilerplate 142 | 143 | ## Support the Dev: 144 | - BTC: bc1qpftdtjggrq8dpa6x6x7dnqvgv7ttc2x2m8rgvy 145 | - ETH / POL / BNB: 0xdCC9f5281B8bb40B11A792C280aA2cdd434C34AF 146 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: postgres:17.0 4 | environment: 5 | - POSTGRES_DB=java-spring-boot 6 | - POSTGRES_USER=postgres 7 | - POSTGRES_PASSWORD=P4ssword! 8 | ports: 9 | - 5433:5432 10 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /nb-configuration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 16 | all 17 | 18 | 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.5 9 | 10 | 11 | io.bootify 12 | java-spring-boot 13 | 0.0.1-SNAPSHOT 14 | java-spring-boot 15 | 16 | 17 | 21 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-security 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-validation 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-jpa 36 | 37 | 38 | org.postgresql 39 | postgresql 40 | runtime 41 | 42 | 43 | io.github.wimdeblauwe 44 | error-handling-spring-boot-starter 45 | 4.5.0 46 | 47 | 48 | org.springdoc 49 | springdoc-openapi-starter-webmvc-ui 50 | 2.6.0 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | 1.18.34 56 | provided 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-devtools 61 | runtime 62 | true 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-docker-compose 67 | runtime 68 | true 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-starter-test 73 | test 74 | 75 | 76 | 77 | ai.djl 78 | api 79 | 0.30.0 80 | 81 | 82 | ai.djl 83 | basicdataset 84 | 0.30.0 85 | 86 | 87 | ai.djl 88 | model-zoo 89 | 0.30.0 90 | 91 | 92 | ai.djl.timeseries 93 | timeseries 94 | 0.30.0 95 | 96 | 97 | ai.djl.huggingface 98 | tokenizers 99 | 0.30.0 100 | 101 | 102 | ai.djl.audio 103 | audio 104 | 0.30.0 105 | 106 | 107 | 108 | ai.djl.mxnet 109 | mxnet-model-zoo 110 | 0.30.0 111 | 112 | 113 | 114 | ai.djl.pytorch 115 | pytorch-model-zoo 116 | 0.30.0 117 | 118 | 119 | 120 | ai.djl.tensorflow 121 | tensorflow-model-zoo 122 | 0.30.0 123 | 124 | 125 | 126 | ai.djl.onnxruntime 127 | onnxruntime-engine 128 | 0.30.0 129 | 130 | 131 | ai.djl 132 | examples 133 | 0.6.0 134 | 135 | 136 | 137 | 138 | io.jsonwebtoken 139 | jjwt-impl 140 | 0.12.3 141 | runtime 142 | 143 | 144 | 145 | 146 | io.jsonwebtoken 147 | jjwt-api 148 | 0.12.3 149 | 150 | 151 | 152 | 153 | io.jsonwebtoken 154 | jjwt-jackson 155 | 0.12.3 156 | runtime 157 | 158 | 159 | 160 | 161 | 162 | 163 | org.springframework.boot 164 | spring-boot-maven-plugin 165 | 166 | 167 | local 168 | 169 | 170 | 171 | org.projectlombok 172 | lombok 173 | 174 | 175 | 176 | 177 | 178 | org.apache.maven.plugins 179 | maven-compiler-plugin 180 | 3.13.0 181 | 182 | 183 | 184 | org.projectlombok 185 | lombok 186 | 1.18.34 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/JavaSpringBootApplication.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.domain.EntityScan; 6 | 7 | @SpringBootApplication 8 | @EntityScan(basePackages = "io.bootify.java_spring_boot.entity") 9 | public class JavaSpringBootApplication { 10 | 11 | public static void main(final String[] args) { 12 | SpringApplication.run(JavaSpringBootApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/config/DomainConfig.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.config; 2 | 3 | import org.springframework.boot.autoconfigure.domain.EntityScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 6 | import org.springframework.transaction.annotation.EnableTransactionManagement; 7 | 8 | 9 | @Configuration 10 | @EntityScan("io.bootify.java_spring_boot.entity") 11 | @EnableJpaRepositories("io.bootify.java_spring_boot.repository") 12 | @EnableTransactionManagement 13 | public class DomainConfig { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/config/JacksonConfig.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.config; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationFeature; 4 | import com.fasterxml.jackson.databind.SerializationFeature; 5 | import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class JacksonConfig { 11 | 12 | @Bean 13 | public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { 14 | return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder 15 | .featuresToDisable( 16 | DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 17 | DeserializationFeature.ACCEPT_FLOAT_AS_INT, 18 | SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/config/RoleInitializer.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.config; 2 | 3 | import io.bootify.java_spring_boot.entity.Role; 4 | import io.bootify.java_spring_boot.repository.RoleRepository; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | @Configuration 11 | public class RoleInitializer { 12 | 13 | @Bean 14 | @Transactional 15 | public CommandLineRunner initRoles(RoleRepository roleRepository) { 16 | return args -> { 17 | addRoleIfNotFound("ROLE_ADMIN", roleRepository); 18 | addRoleIfNotFound("ROLE_MODERATOR", roleRepository); 19 | addRoleIfNotFound("ROLE_USER", roleRepository); 20 | }; 21 | } 22 | 23 | private void addRoleIfNotFound(String roleName, RoleRepository roleRepository) { 24 | if (!roleRepository.existsByName(roleName)) { 25 | Role role = new Role(roleName); 26 | roleRepository.save(role); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.config; 2 | 3 | import io.bootify.java_spring_boot.security.jwt.JwtAuthFilter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.authentication.AuthenticationProvider; 8 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 12 | import org.springframework.security.web.SecurityFilterChain; 13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 | 15 | import org.springframework.security.authentication.AuthenticationManager; 16 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 17 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 18 | import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; 19 | import org.springframework.security.core.userdetails.UserDetailsService; 20 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 21 | import org.springframework.security.crypto.password.PasswordEncoder; 22 | 23 | @Configuration 24 | @EnableWebSecurity 25 | @EnableMethodSecurity 26 | @RequiredArgsConstructor 27 | public class SecurityConfig { 28 | 29 | private final JwtAuthFilter jwtAuthFilter; 30 | private final UserDetailsService userDetailsService; 31 | 32 | private static final String[] WHITE_LIST_URL = { 33 | "/user/**", 34 | "/auth/**" 35 | }; 36 | 37 | @Bean 38 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 39 | return http 40 | .httpBasic(AbstractHttpConfigurer::disable) //disable default login 41 | .csrf(AbstractHttpConfigurer::disable) 42 | .authorizeHttpRequests(req -> 43 | req.requestMatchers(WHITE_LIST_URL).permitAll() 44 | .anyRequest().authenticated() 45 | ) 46 | .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) 47 | .authenticationProvider(authenticationProvider()) 48 | .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) 49 | .build(); 50 | } 51 | 52 | @Bean 53 | public PasswordEncoder passwordEncoder() { 54 | return new BCryptPasswordEncoder(); 55 | } 56 | 57 | @Bean 58 | public AuthenticationProvider authenticationProvider() { 59 | DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); 60 | authProvider.setUserDetailsService(userDetailsService); 61 | authProvider.setPasswordEncoder(passwordEncoder()); 62 | return authProvider; 63 | } 64 | 65 | @Bean 66 | public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { 67 | return config.getAuthenticationManager(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.controller; 2 | 3 | import io.bootify.java_spring_boot.dto.SignInRequest; 4 | import io.bootify.java_spring_boot.dto.SignInResponse; 5 | import io.bootify.java_spring_boot.dto.SignUpResponse; 6 | import io.bootify.java_spring_boot.repository.UserRepository; 7 | import io.bootify.java_spring_boot.security.CustomUserDetailsService; 8 | import io.bootify.java_spring_boot.security.jwt.JwtTokenProvider; 9 | import io.bootify.java_spring_boot.service.AuthService; 10 | import io.bootify.java_spring_boot.service.UserService; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.authentication.AuthenticationManager; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.security.crypto.password.PasswordEncoder; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import io.bootify.java_spring_boot.dto.SignUpRequest; 20 | import lombok.RequiredArgsConstructor; 21 | import java.util.ArrayList; 22 | 23 | @RestController 24 | @RequestMapping("/auth") 25 | @RequiredArgsConstructor 26 | public class AuthController { 27 | 28 | private final CustomUserDetailsService userDetailsService; 29 | private final UserRepository userRepository; 30 | private final UserService userService; 31 | private final AuthService authService; 32 | 33 | @Autowired 34 | private AuthenticationManager authenticationManager; 35 | 36 | @Autowired 37 | private final JwtTokenProvider jwtTokenProvider; 38 | 39 | @Autowired 40 | private PasswordEncoder passwordEncoder; 41 | 42 | @PostMapping("/signup") 43 | public ResponseEntity signup(@RequestBody SignUpRequest request) { 44 | userService.registerUser(request); 45 | 46 | Authentication authentication = new UsernamePasswordAuthenticationToken( 47 | request.getEmail(), null, new ArrayList<>()); 48 | 49 | String jwtToken = jwtTokenProvider.generateToken(authentication); 50 | return ResponseEntity.ok(new SignUpResponse(jwtToken)); 51 | } 52 | 53 | @PostMapping("/signin") 54 | public ResponseEntity signin(@RequestBody SignInRequest request) { 55 | return ResponseEntity.ok(new SignInResponse( 56 | authService.authUser(request) 57 | )); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/dto/SignInRequest.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.dto; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @Builder 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class SignInRequest { 11 | private String email; 12 | private String password; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/dto/SignInResponse.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.dto; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @Builder 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class SignInResponse { 11 | private String token; 12 | } -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/dto/SignUpRequest.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.dto; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @Builder 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class SignUpRequest { 11 | private String firstName; 12 | private String lastName; 13 | private String email; 14 | private String password; 15 | } -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/dto/SignUpResponse.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.dto; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @Builder 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | public class SignUpResponse { 11 | 12 | private String token; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/entity/Role.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import org.springframework.security.core.GrantedAuthority; 9 | 10 | @Getter 11 | @Setter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Entity 15 | @Table(name = "roles") 16 | public class Role implements GrantedAuthority { 17 | 18 | public Role(String name) { 19 | this.name = name; 20 | } 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | private String name; 26 | 27 | @Override 28 | public String getAuthority() { 29 | return getName(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/entity/User.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.entity; 2 | 3 | import jakarta.persistence.*; 4 | import jakarta.validation.constraints.Size; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | import java.util.Arrays; 11 | import java.util.Set; 12 | import java.util.HashSet; 13 | 14 | @Setter 15 | @Getter 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Entity 19 | @Table(name = "users") 20 | public class User { 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | 26 | private String name; 27 | 28 | @Size(min = 4, max = 255, message = "Minimum username length: 4 characters") 29 | @Column(nullable = false, unique = true) 30 | private String username; 31 | 32 | @Column(nullable = false, unique = true) 33 | private String email; 34 | 35 | @Size(min = 8, max = 255, message = "Minimum password length: 8 characters") 36 | @Column(nullable = false) 37 | private String password; 38 | 39 | @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 40 | @JoinTable(name = "users_roles", 41 | joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), 42 | inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") 43 | ) 44 | private Set roles; 45 | 46 | public void addRoles(Role... rolesToAdd) { 47 | if (this.roles == null) { 48 | this.roles = new HashSet<>(); 49 | } 50 | 51 | this.roles.addAll(Arrays.asList(rolesToAdd)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.repository; 2 | 3 | import io.bootify.java_spring_boot.entity.Role; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface RoleRepository extends JpaRepository { 9 | 10 | Role findByName(String name); 11 | boolean existsByName(String name); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.repository; 2 | 3 | import io.bootify.java_spring_boot.entity.User; 4 | import java.util.Optional; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | @Repository 9 | public interface UserRepository extends JpaRepository { 10 | 11 | Optional findByUsernameOrEmail(String username, String email); 12 | 13 | boolean existsByUsernameOrEmail(String username, String email); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/resource/HomeResource.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.resource; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | 7 | @RestController 8 | public class HomeResource { 9 | 10 | @GetMapping("/") 11 | public String index() { 12 | return "\"Hello World!\""; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/resource/ImageClassificationResource.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.resource; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import ai.djl.ModelException; 9 | import ai.djl.translate.TranslateException; 10 | import io.bootify.java_spring_boot.service.ai.ImageClassificationService; 11 | 12 | import java.io.IOException; 13 | 14 | @RestController 15 | public class ImageClassificationResource { 16 | 17 | @Autowired 18 | private ImageClassificationService imageClassificationService; 19 | 20 | @GetMapping("/classify-image") 21 | public String classifyImage(@RequestParam String imageUrl) { 22 | try { 23 | return imageClassificationService.classifyImage(imageUrl); 24 | } catch (ModelException | TranslateException | IOException e) { 25 | e.printStackTrace(); 26 | return "Error: " + e.getMessage(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/resource/UserResource.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.resource; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class UserResource { 8 | 9 | @GetMapping("/user") 10 | public String index() { 11 | return "\"user!\""; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/security/CustomUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.security; 2 | 3 | import io.bootify.java_spring_boot.entity.Role; 4 | import io.bootify.java_spring_boot.entity.User; 5 | import io.bootify.java_spring_boot.repository.UserRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.security.core.GrantedAuthority; 8 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.util.Set; 15 | import java.util.stream.Collectors; 16 | 17 | @Service 18 | @RequiredArgsConstructor 19 | public class CustomUserDetailsService implements UserDetailsService { 20 | 21 | private final UserRepository userRepository; 22 | 23 | @Override 24 | public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException { 25 | User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail) 26 | .orElseThrow(() -> new UsernameNotFoundException("User not exists by Username or Email")); 27 | 28 | Set authorities = user.getRoles().stream() 29 | .map((role) -> new SimpleGrantedAuthority(role.getAuthority())) 30 | .collect(Collectors.toSet()); 31 | 32 | return new org.springframework.security.core.userdetails.User( 33 | usernameOrEmail, 34 | user.getPassword(), 35 | authorities 36 | ); 37 | } 38 | 39 | public User addUser(User user) { 40 | if (userRepository.existsByUsernameOrEmail(user.getUsername(), user.getEmail())) { 41 | throw new IllegalArgumentException("User with this username or email already exists"); 42 | } 43 | 44 | Role userRole = new Role("ROLE_USER"); 45 | user.addRoles(userRole); 46 | 47 | return userRepository.save(user); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/security/jwt/JwtAuthEntryPoint.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.security.jwt; 2 | 3 | import jakarta.servlet.ServletException; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import java.io.IOException; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.web.AuthenticationEntryPoint; 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * AuthenticationEntryPoint is an interface provided by Spring Security, specifically for handling unauthorized access attempts. 13 | * How JwtAuthenticationEntryPoint Works in the Application: 14 | * JwtAuthenticationEntryPoint is usually referenced in the security configuration file (like SecurityConfig) to specify how unauthorized requests are handled. 15 | * When an unauthenticated request hits a protected endpoint, Spring Security intercepts it, triggers JwtAuthenticationEntryPoint, and the commence method executes. 16 | * This lets your API return a consistent 401 Unauthorized response for failed authentications, instead of allowing the request to proceed or failing silently. 17 | * @author Omistaja 18 | */ 19 | @Component 20 | public class JwtAuthEntryPoint implements AuthenticationEntryPoint { 21 | 22 | @Override 23 | public void commence(HttpServletRequest request, 24 | HttpServletResponse response, 25 | AuthenticationException authException 26 | ) throws IOException, ServletException { 27 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/security/jwt/JwtAuthFilter.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.security.jwt; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.context.SecurityContextHolder; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.util.StringUtils; 15 | import org.springframework.web.filter.OncePerRequestFilter; 16 | 17 | import java.io.IOException; 18 | 19 | @Component 20 | @RequiredArgsConstructor 21 | public class JwtAuthFilter extends OncePerRequestFilter { 22 | 23 | private final JwtTokenProvider jwtTokenProvider; 24 | private final UserDetailsService userDetailsService; 25 | 26 | @Override 27 | protected void doFilterInternal( 28 | HttpServletRequest request, 29 | HttpServletResponse response, 30 | FilterChain filterChain 31 | ) throws ServletException, IOException { 32 | String token = getTokenFromRequest(request); 33 | 34 | // Validate Token 35 | if(StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { 36 | String username = jwtTokenProvider.getUsername(token); 37 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 38 | 39 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( 40 | userDetails, 41 | null, 42 | userDetails.getAuthorities() 43 | ); 44 | 45 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 46 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 47 | } 48 | 49 | filterChain.doFilter(request, response); 50 | } 51 | 52 | private String getTokenFromRequest(HttpServletRequest request){ 53 | String bearerToken = request.getHeader("Authorization"); 54 | 55 | if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { 56 | return bearerToken.substring(7, bearerToken.length()); 57 | } 58 | 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/security/jwt/JwtTokenProvider.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.security.jwt; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.io.Decoders; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.crypto.SecretKey; 12 | import java.security.Key; 13 | import java.util.Date; 14 | 15 | @Component 16 | public class JwtTokenProvider { 17 | 18 | @Value("${jwt.secret}") 19 | private String jwtSecret; 20 | 21 | @Value("${jwt.expiration}") 22 | private long jwtExpirationDate; 23 | 24 | public String generateToken(Authentication authentication){ 25 | String username = authentication.getName(); 26 | Date currentDate = new Date(); 27 | Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate); 28 | String token = Jwts.builder() 29 | .subject(username) 30 | .issuedAt(new Date()) 31 | .expiration(expireDate) 32 | .signWith(key()) 33 | .compact(); 34 | 35 | return token; 36 | } 37 | 38 | private Key key(){ 39 | return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); 40 | } 41 | 42 | public String getUsername(String token){ 43 | return Jwts.parser() 44 | .verifyWith((SecretKey)key()) 45 | .build() 46 | .parseSignedClaims(token) 47 | .getPayload() 48 | .getSubject(); 49 | } 50 | 51 | public Claims getClaims(String token){ 52 | return Jwts.parser() 53 | .verifyWith((SecretKey)key()) 54 | .build() 55 | .parseSignedClaims(token) 56 | .getPayload(); 57 | } 58 | 59 | public boolean validateToken(String token){ 60 | Jwts.parser() 61 | .verifyWith((SecretKey)key()) 62 | .build() 63 | .parse(token); 64 | return true; 65 | } 66 | 67 | private Claims extractAllClaims(String token) { 68 | return Jwts.parser().verifyWith((SecretKey)key()) 69 | .build() 70 | .parseClaimsJws(token) 71 | .getBody(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.service; 2 | 3 | import io.bootify.java_spring_boot.dto.SignInRequest; 4 | import io.bootify.java_spring_boot.repository.UserRepository; 5 | import io.bootify.java_spring_boot.security.jwt.JwtTokenProvider; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.stereotype.Service; 13 | 14 | @Service 15 | @RequiredArgsConstructor 16 | public class AuthService { 17 | 18 | @Autowired 19 | private AuthenticationManager authenticationManager; 20 | 21 | @Autowired 22 | private final JwtTokenProvider jwtTokenProvider; 23 | 24 | private final UserRepository userRepository; 25 | private final PasswordEncoder passwordEncoder; 26 | 27 | public String authUser(SignInRequest request) { 28 | Authentication authentication = authenticationManager.authenticate( 29 | new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); 30 | 31 | return jwtTokenProvider.generateToken(authentication); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/service/UserService.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.service; 2 | 3 | import io.bootify.java_spring_boot.dto.SignUpRequest; 4 | import io.bootify.java_spring_boot.entity.User; 5 | import io.bootify.java_spring_boot.repository.UserRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class UserService { 13 | 14 | private final UserRepository userRepository; 15 | private final PasswordEncoder passwordEncoder; 16 | 17 | public void registerUser(SignUpRequest request) { 18 | if (userRepository.existsByUsernameOrEmail(request.getEmail(), request.getEmail())) { 19 | throw new IllegalArgumentException("User with this username or email already exists"); 20 | } 21 | 22 | User newUser = new User(); 23 | newUser.setUsername(request.getEmail()); 24 | newUser.setEmail(request.getEmail()); 25 | newUser.setPassword(passwordEncoder.encode(request.getPassword())); 26 | 27 | userRepository.save(newUser); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/bootify/java_spring_boot/service/ai/ImageClassificationService.java: -------------------------------------------------------------------------------- 1 | package io.bootify.java_spring_boot.service.ai; 2 | 3 | import ai.djl.ModelException; 4 | import ai.djl.inference.Predictor; 5 | import ai.djl.modality.Classifications; 6 | import ai.djl.modality.cv.Image; 7 | import ai.djl.modality.cv.ImageFactory; 8 | import ai.djl.translate.TranslateException; 9 | import ai.djl.repository.zoo.Criteria; 10 | import ai.djl.repository.zoo.ModelZoo; 11 | import ai.djl.repository.zoo.ZooModel; 12 | 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.io.IOException; 16 | 17 | @Service 18 | public class ImageClassificationService { 19 | 20 | public String classifyImage(String imageUrl) throws ModelException, TranslateException, IOException { 21 | Criteria criteria = Criteria.builder() 22 | .setTypes(Image.class, Classifications.class) 23 | .optArtifactId("resnet") 24 | .build(); 25 | 26 | try (ZooModel model = ModelZoo.loadModel(criteria); 27 | Predictor predictor = model.newPredictor() 28 | ) { 29 | Image img = ImageFactory.getInstance().fromUrl(imageUrl); 30 | Classifications classifications = predictor.predict(img); 31 | return classifications.toString(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | devtools: 3 | restart: 4 | enabled: true # Enable automatic restart (default is true) 5 | poll-interval: 1000ms # Poll interval for changes (default is 1s) 6 | livereload: 7 | enabled: true # Enable live reload (default is true) 8 | datasource: 9 | url: ${JDBC_DATABASE_URL:jdbc:postgresql://localhost:5432/java-spring-boot} 10 | username: ${JDBC_DATABASE_USERNAME:postgres} 11 | password: ${JDBC_DATABASE_PASSWORD:mypassword} 12 | hikari: 13 | connection-timeout: 30000 14 | maximum-pool-size: 10 15 | jpa: 16 | hibernate: 17 | ddl-auto: update 18 | open-in-view: false 19 | properties: 20 | hibernate: 21 | jdbc: 22 | lob: 23 | non_contextual_creation: true 24 | id: 25 | new_generator_mappings: true 26 | docker: 27 | compose: 28 | lifecycle-management: start-only 29 | jwt: 30 | secret: skG91dM0QEPyhAkNWRqNwUMwW7in+o2reC6w7O12MSePeu7mSYOcej+R9NMUoFe1upxNmS669+7OQYsMBcZYhQ== # HS512 31 | expiration: 3600000 # 1 hour in milliseconds 32 | error: 33 | handling: 34 | http-status-in-json-response: true 35 | exception-logging: NO_LOGGING 36 | full-stacktrace-http-statuses: 5xx 37 | log-levels: 38 | 5xx: ERROR 39 | springdoc: 40 | pathsToMatch: / 41 | logging: 42 | level: 43 | org.springframework: ERROR 44 | server: 45 | error: 46 | include-stacktrace: never 47 | --------------------------------------------------------------------------------