diff --git a/spring-security-jwt/getting-started/SecureApplication/.gitignore b/spring-security-jwt/getting-started/SecureApplication/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/spring-security-jwt/getting-started/SecureApplication/.mvn/wrapper/maven-wrapper.jar b/spring-security-jwt/getting-started/SecureApplication/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..c1dd12f17 Binary files /dev/null and b/spring-security-jwt/getting-started/SecureApplication/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-security-jwt/getting-started/SecureApplication/.mvn/wrapper/maven-wrapper.properties b/spring-security-jwt/getting-started/SecureApplication/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..b74bf7fcd --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/spring-security-jwt/getting-started/SecureApplication/mvnw b/spring-security-jwt/getting-started/SecureApplication/mvnw new file mode 100644 index 000000000..8a8fb2282 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-security-jwt/getting-started/SecureApplication/mvnw.cmd b/spring-security-jwt/getting-started/SecureApplication/mvnw.cmd new file mode 100644 index 000000000..1d8ab018e --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/spring-security-jwt/getting-started/SecureApplication/pom.xml b/spring-security-jwt/getting-started/SecureApplication/pom.xml new file mode 100644 index 000000000..accd7cfac --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/pom.xml @@ -0,0 +1,149 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.5 + + + com.reflectoring + jwt-security + 0.0.1-SNAPSHOT + security + Spring JWT Security sample project + + 11 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + io.jsonwebtoken + jjwt-api + 0.11.1 + + + io.jsonwebtoken + jjwt-impl + 0.11.1 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.1 + runtime + + + + + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + org.projectlombok + lombok + 1.18.20 + provided + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + org.mapstruct + mapstruct + 1.4.2.Final + + + org.hsqldb + hsqldb + 2.4.0 + runtime + + + org.zalando + problem-spring-web + 0.27.0 + + + + + org.hamcrest + hamcrest-library + 2.2 + test + + + org.springframework.data + spring-data-commons + + + org.springframework.data + spring-data-jpa + + + org.apache.commons + commons-lang3 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + 11 + + + org.projectlombok + lombok + 1.18.20 + + + org.mapstruct + mapstruct-processor + 1.4.2.Final + + + + + + + + diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/JWTApplication.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/JWTApplication.java new file mode 100644 index 000000000..675729326 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/JWTApplication.java @@ -0,0 +1,16 @@ +package com.reflectoring.security; + +import com.reflectoring.security.jwt.JWTCreator; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JWTApplication { + + public static void main(String[] args) { + //System.out.println("Create JWT Token: " + JWTCreator.createJwt()); + //System.out.println("Parse JWT: " + JWTCreator.parseJwt(JWTCreator.createJwt())); + SpringApplication.run(JWTApplication.class, args); + } + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/JwtAuthenticationEntryPoint.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/JwtAuthenticationEntryPoint.java new file mode 100644 index 000000000..fbe7ac10a --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/JwtAuthenticationEntryPoint.java @@ -0,0 +1,35 @@ +package com.reflectoring.security.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Component +@Slf4j +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + Exception exception = (Exception) request.getAttribute("exception"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType(APPLICATION_JSON_VALUE); + log.error("Authentication Exception: {} ", exception, exception); + Map data = new HashMap<>(); + data.put("message", exception != null ? exception.getMessage() : authException.getCause().toString()); + OutputStream out = response.getOutputStream(); + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(out, data); + out.flush(); + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/JwtProperties.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/JwtProperties.java new file mode 100644 index 000000000..6e7083973 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/JwtProperties.java @@ -0,0 +1,15 @@ +package com.reflectoring.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@ConfigurationProperties(prefix = "jwt") +@Configuration +@Data +public class JwtProperties { + + private String secretKey; + private long validity; + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/OpenApiConfig.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/OpenApiConfig.java new file mode 100644 index 000000000..ea4925775 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/OpenApiConfig.java @@ -0,0 +1,37 @@ +package com.reflectoring.security.config; + + +import io.swagger.v3.oas.annotations.ExternalDocumentation; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.info.License; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.security.SecurityScheme; + +@OpenAPIDefinition( + info = @Info( + title = "Library application", + description = "Get all library books", + version = "1.0.0", + license = @License( + name = "Apache 2.0", + url = "http://www.apache.org/licenses/LICENSE-2.0" + )), + security = { + @SecurityRequirement( + name = "bearerAuth" + ) + } + ) +@SecurityScheme( + name = "bearerAuth", + description = "JWT Authorization", + scheme = "bearer", + type = SecuritySchemeType.HTTP, + bearerFormat = "JWT", + in = SecuritySchemeIn.HEADER +) +public class OpenApiConfig { +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/PlainTextPasswordEncoder.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/PlainTextPasswordEncoder.java new file mode 100644 index 000000000..22b2b22d6 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/PlainTextPasswordEncoder.java @@ -0,0 +1,19 @@ +package com.reflectoring.security.config; + +import org.springframework.security.crypto.password.PasswordEncoder; + +public class PlainTextPasswordEncoder implements PasswordEncoder { + @Override + public String encode(CharSequence rawPassword) { + return rawPassword.toString(); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return rawPassword.toString().equals(encodedPassword); + } + + public static PasswordEncoder getInstance() { + return new PlainTextPasswordEncoder(); + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/SecurityConfiguration.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/SecurityConfiguration.java new file mode 100644 index 000000000..5132df2ce --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/SecurityConfiguration.java @@ -0,0 +1,73 @@ +package com.reflectoring.security.config; + +import com.reflectoring.security.filter.JwtFilter; +import com.reflectoring.security.service.AuthUserDetailsService; +import org.apache.commons.lang3.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + private final JwtFilter jwtFilter; + + private final AuthUserDetailsService authUserDetailsService; + + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + @Autowired + public SecurityConfiguration(JwtFilter jwtFilter, + AuthUserDetailsService authUserDetailsService, + JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint) { + + this.jwtFilter = jwtFilter; + this.authUserDetailsService = authUserDetailsService; + this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint; + } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + final DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setUserDetailsService(authUserDetailsService); + daoAuthenticationProvider.setPasswordEncoder(PlainTextPasswordEncoder.getInstance()); + return daoAuthenticationProvider; + } + + @Bean + public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception { + return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class) + .authenticationProvider(authenticationProvider()) + .build(); + } + + @Bean + public SecurityFilterChain configure (HttpSecurity http) throws Exception { + return http.csrf().disable() + .authorizeRequests() + .antMatchers("/token/*").permitAll() + .anyRequest().authenticated().and() + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exception -> exception.authenticationEntryPoint(jwtAuthenticationEntryPoint)).build(); + } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return web -> web.ignoring().antMatchers(ArrayUtils.addAll(buildExemptedRoutes())); + } + + private String[] buildExemptedRoutes() { + return new String[] {"/swagger-ui/**","/v3/api-docs/**"}; + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/UserProperties.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/UserProperties.java new file mode 100644 index 000000000..47d90292b --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/config/UserProperties.java @@ -0,0 +1,15 @@ +package com.reflectoring.security.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@ConfigurationProperties("spring.security.user") +@Configuration +@Data +public class UserProperties { + + private String name; + private String password; + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/exception/CommonException.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/exception/CommonException.java new file mode 100644 index 000000000..a24fdfd12 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/exception/CommonException.java @@ -0,0 +1,32 @@ +package com.reflectoring.security.exception; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.StatusType; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; +import static org.zalando.problem.Status.*; + +@JsonInclude(NON_EMPTY) +@JsonIgnoreProperties({"stackTrace", "type", "title", "message", "localizedMessage", "parameters"}) +public class CommonException extends AbstractThrowableProblem { + + private CommonException(StatusType status, String detail) { + super(null, null, status, detail, null, null, null); + } + + public static CommonException unauthorized() { + return new CommonException(UNAUTHORIZED, "Unauthorised or Bad Credentials"); + } + + public static CommonException forbidden() { + return new CommonException(FORBIDDEN, "Forbidden"); + } + + public static CommonException headerError() { + return new CommonException(FORBIDDEN, "Missing Header"); + } + + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/filter/JwtFilter.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/filter/JwtFilter.java new file mode 100644 index 000000000..d74e999f8 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/filter/JwtFilter.java @@ -0,0 +1,83 @@ +package com.reflectoring.security.filter; + +import com.reflectoring.security.jwt.JwtHelper; +import com.reflectoring.security.service.AuthUserDetailsService; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Objects; + + +@Component +@Slf4j +public class JwtFilter extends OncePerRequestFilter { + + public static final String AUTHORIZATION = "Authorization"; + + private final AuthUserDetailsService userDetailsService; + + private final JwtHelper jwtHelper; + + public JwtFilter(AuthUserDetailsService userDetailsService, JwtHelper jwtHelper) { + this.userDetailsService = userDetailsService; + this.jwtHelper = jwtHelper; + } + + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + log.info("Inside JWT filter"); + try { + final String authorizationHeader = request.getHeader(AUTHORIZATION); + System.out.println("Print Auth header: " + authorizationHeader); + String jwt = null; + String username = null; + if (Objects.nonNull(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) { + jwt = authorizationHeader.substring(7); + System.out.println("JWT Tokwn ONLY: " + jwt); + username = jwtHelper.extractUsername(jwt); + } + + System.out.println("Security Context: " + SecurityContextHolder.getContext().getAuthentication()); + if (Objects.nonNull(username) && SecurityContextHolder.getContext().getAuthentication() == null) { + System.out.println("Context username:" + username); + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); + System.out.println("Context user details: " + userDetails); + boolean isTokenValidated = jwtHelper.validateToken(jwt, userDetails); + System.out.println("Is token validated: " + isTokenValidated); + if (isTokenValidated) { + System.out.println("UerDetails authorities: " + userDetails.getAuthorities()); + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + } else { + throw new BadCredentialsException("Bearer token not set correctly"); + } + } catch (ExpiredJwtException jwtException) { + request.setAttribute("exception", jwtException); + } catch (BadCredentialsException | UnsupportedJwtException | MalformedJwtException e) { + log.error("Filter exception: {}", e.getMessage()); + request.setAttribute("exception", e); + } + filterChain.doFilter(request, response); + + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/jwt/JWTCreator.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/jwt/JWTCreator.java new file mode 100644 index 000000000..9476e89f2 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/jwt/JWTCreator.java @@ -0,0 +1,48 @@ +package com.reflectoring.security.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.sql.Date; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import java.util.Map; + +public class JWTCreator { + + public static String createJwt() { + // Recommended to be stored in Secret + String secret = "5JzoMbk6E5qIqHSuBTgeQCARtUsxAkBiHwdjXOSW8kWdXzYmP3X51C0"; + Key hmacKey = new SecretKeySpec(Base64.getDecoder().decode(secret), + SignatureAlgorithm.HS256.getJcaName()); + return Jwts.builder() + .claim("id", "abc123") + .claim("role", "admin") + /*.addClaims(Map.of("id", "abc123", + "role", "admin"))*/ + .setIssuer("TestApplication") + .setIssuedAt(java.util.Date.from(Instant.now())) + .setExpiration(Date.from(Instant.now().plus(10, ChronoUnit.MINUTES))) + .signWith(hmacKey) + .compact(); + } + + public static Jws parseJwt(String jwtString) { + // Recommended to be stored in Secret + String secret = "5JzoMbk6E5qIqHSuBTgeQCARtUsxAkBiHwdjXOSW8kWdXzYmP3X51C0"; + Key hmacKey = new SecretKeySpec(Base64.getDecoder().decode(secret), + SignatureAlgorithm.HS256.getJcaName()); + + Jws jwt = Jwts.parserBuilder() + .setSigningKey(hmacKey) + .build() + .parseClaimsJws(jwtString); + + return jwt; + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/jwt/JwtHelper.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/jwt/JwtHelper.java new file mode 100644 index 000000000..319b211a3 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/jwt/JwtHelper.java @@ -0,0 +1,74 @@ +package com.reflectoring.security.jwt; + +import com.reflectoring.security.config.JwtProperties; +import io.jsonwebtoken.*; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.time.Instant; +import java.util.*; +import java.util.function.Function; + +@Component +public class JwtHelper { + + private final JwtProperties jwtProperties; + + + public JwtHelper(JwtProperties jwtProperties) { + this.jwtProperties = jwtProperties; + } + + private Jws extractClaims(String bearerToken) { + return Jwts.parserBuilder().setSigningKey(jwtProperties.getSecretKey()) + .build().parseClaimsJws(bearerToken); + } + + public T extractClaimBody(String bearerToken, Function claimsResolver) { + Jws jwsClaims = extractClaims(bearerToken); + System.out.println("Claims Bosy: " + jwsClaims.getBody()); + return claimsResolver.apply(jwsClaims.getBody()); + } + + public T extractClaimHeader(String bearerToken, Function claimsResolver) { + Jws jwsClaims = extractClaims(bearerToken); + return claimsResolver.apply(jwsClaims.getHeader()); + } + + public Date extractExpiry(String bearerToken) { + return extractClaimBody(bearerToken, Claims::getExpiration); + } + + public String extractUsername(String bearerToken) { + return extractClaimBody(bearerToken, Claims::getSubject); + } + + private Boolean isTokenExpired(String bearerToken) { + System.out.println("Is before: " + extractExpiry(bearerToken).before(new Date())); + return extractExpiry(bearerToken).before(new Date()); + } + + public String createToken(Map claims, String subject) { + Date expiryDate = Date.from(Instant.ofEpochMilli(System.currentTimeMillis() + jwtProperties.getValidity())); + Key hmacKey = new SecretKeySpec(Base64.getDecoder().decode(jwtProperties.getSecretKey()), + SignatureAlgorithm.HS256.getJcaName()); + return Jwts.builder() + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(expiryDate) + .signWith(hmacKey) + .compact(); + } + + public boolean validateToken(String token, UserDetails userDetails) { + final String userName = extractUsername(token); + System.out.println("Username from token: " + userName); + return userName.equals(userDetails.getUsername()) && !isTokenExpired(token); + } + + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapper/BookMapper.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapper/BookMapper.java new file mode 100644 index 000000000..7c20fdf3c --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapper/BookMapper.java @@ -0,0 +1,23 @@ +package com.reflectoring.security.mapper; + +import com.reflectoring.security.mapstruct.AuthorDto; +import com.reflectoring.security.mapstruct.BookDto; +import com.reflectoring.security.persistence.Author; +import com.reflectoring.security.persistence.Book; +import org.mapstruct.Mapper; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface BookMapper { + BookDto bookToBookDto(Book book); + + List bookToBookDto(List book); + + AuthorDto authorToAuthorDto(Author author); + + Book bookDtoToBook(BookDto bookDto); + + Author authorDtoToAuthor(AuthorDto authorDto); +} + diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapstruct/AuthorDto.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapstruct/AuthorDto.java new file mode 100644 index 000000000..eb10202fb --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapstruct/AuthorDto.java @@ -0,0 +1,50 @@ +package com.reflectoring.security.mapstruct; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class AuthorDto { + + @JsonProperty("id") + private long id; + + @JsonProperty("name") + private String name; + + @JsonProperty("dob") + private String dob; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDob() { + return dob; + } + + public void setDob(String dob) { + this.dob = dob; + } + + @Override + public String toString() { + return "AuthorDto{" + + "id=" + id + + ", name='" + name + '\'' + + ", dob='" + dob + '\'' + + '}'; + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapstruct/BookDto.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapstruct/BookDto.java new file mode 100644 index 000000000..d0430da1a --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/mapstruct/BookDto.java @@ -0,0 +1,87 @@ +package com.reflectoring.security.mapstruct; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.NoArgsConstructor; + +import java.util.Set; + +@NoArgsConstructor +public class BookDto { + @JsonProperty("bookId") + private long id; + + @JsonProperty("bookName") + private String name; + + @JsonProperty("publisher") + private String publisher; + + @JsonProperty("publicationYear") + private String publicationYear; + + @JsonProperty("genre") + private String genre; + + @JsonProperty("authors") + private Set authors; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPublisher() { + return publisher; + } + + public void setPublisher(String publisher) { + this.publisher = publisher; + } + + public String getPublicationYear() { + return publicationYear; + } + + public void setPublicationYear(String publicationYear) { + this.publicationYear = publicationYear; + } + + public String getGenre() { + return genre; + } + + public void setGenre(String genre) { + this.genre = genre; + } + + public Set getAuthors() { + return authors; + } + + public void setAuthors(Set authors) { + this.authors = authors; + } + + @Override + public String toString() { + return "BookDto{" + + "id=" + id + + ", name='" + name + '\'' + + ", publisher='" + publisher + '\'' + + ", publicationYear='" + publicationYear + '\'' + + ", genre=" + genre + + ", authors=" + authors + + '}'; + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/model/TokenRequest.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/model/TokenRequest.java new file mode 100644 index 000000000..a5f61815e --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/model/TokenRequest.java @@ -0,0 +1,15 @@ +package com.reflectoring.security.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TokenRequest { + private String username; + private String password; +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/model/TokenResponse.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/model/TokenResponse.java new file mode 100644 index 000000000..c80b1cf6c --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/model/TokenResponse.java @@ -0,0 +1,16 @@ +package com.reflectoring.security.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TokenResponse { + + private String token; + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/persistence/Author.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/persistence/Author.java new file mode 100644 index 000000000..8934de43f --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/persistence/Author.java @@ -0,0 +1,61 @@ +package com.reflectoring.security.persistence; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Set; + +@Entity +public class Author implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + private String name; + + private String dob; + + @ManyToMany(mappedBy = "authors") + private Set books; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + public void setBooks(Set books) { + this.books = books; + } + + public String getDob() { + return dob; + } + + public void setDob(String dob) { + this.dob = dob; + } + + @Override + public String toString() { + return "Author{" + + "id=" + id + + ", name='" + name + '\'' + + ", dob='" + dob + '\'' + + '}'; + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/persistence/Book.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/persistence/Book.java new file mode 100644 index 000000000..499e8b4c4 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/persistence/Book.java @@ -0,0 +1,86 @@ +package com.reflectoring.security.persistence; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Set; + +@Entity +@Table(name = "BOOK") +public class Book implements Serializable { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + private String name; + + private String publisher; + + private String publicationYear; + + private String genre; + + @ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY) + @JoinTable(name = "author_book", + joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"), + inverseJoinColumns = @JoinColumn(name = "author_id", referencedColumnName = "id")) + private Set authors; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getAuthors() { + return authors; + } + + public void setAuthors(Set authors) { + this.authors = authors; + } + + public String getPublisher() { + return publisher; + } + + public void setPublisher(String publisher) { + this.publisher = publisher; + } + + public String getPublicationYear() { + return publicationYear; + } + + public void setPublicationYear(String publicationYear) { + this.publicationYear = publicationYear; + } + + public String getGenre() { + return genre; + } + + public void setGenre(String genre) { + this.genre = genre; + } + + @Override + public String toString() { + return "Book{" + + "id=" + id + + ", name='" + name + '\'' + + ", publisher='" + publisher + '\'' + + ", publicationYear='" + publicationYear + '\'' + + ", genre=" + genre + + '}'; + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/AuthorRepository.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/AuthorRepository.java new file mode 100644 index 000000000..111adefc8 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/AuthorRepository.java @@ -0,0 +1,9 @@ +package com.reflectoring.security.repository; + +import com.reflectoring.security.persistence.Author; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AuthorRepository extends JpaRepository { +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/BookRepository.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/BookRepository.java new file mode 100644 index 000000000..32714e89d --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/BookRepository.java @@ -0,0 +1,18 @@ +package com.reflectoring.security.repository; + +import com.reflectoring.security.persistence.Book; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface BookRepository extends JpaRepository { + + + List findByGenre(String genre); + + @PostAuthorize("returnObject.size() > 0") + List findAll(); +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/DatabaseComponent.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/DatabaseComponent.java new file mode 100644 index 000000000..dcc72cc69 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/repository/DatabaseComponent.java @@ -0,0 +1,92 @@ +package com.reflectoring.security.repository; + +import com.reflectoring.security.persistence.Author; +import com.reflectoring.security.persistence.Book; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.Set; + +@Component +public class DatabaseComponent implements CommandLineRunner { + + private final BookRepository bookRepository; + + @Autowired + public DatabaseComponent(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + + @Override + public void run(String... args) throws Exception { + + Book book = new Book(); + book.setName("The Kite Runner"); + book.setPublisher("Riverhead books"); + book.setPublicationYear("2003"); + book.setGenre("Fiction"); + Author author = new Author(); + author.setName("Khaled Hosseini"); + author.setDob("04/03/1965"); + book.setAuthors(Set.of(author)); + bookRepository.save(book); + + book = new Book(); + book.setName("Exiles"); + book.setPublisher("Pan Macmillan"); + book.setPublicationYear("2022"); + book.setGenre("Fiction"); + author = new Author(); + author.setName("Jane Harper"); + author.setDob("01/06/1980"); + book.setAuthors(Set.of(author)); + bookRepository.save(book); + + book = new Book(); + book.setName("A Game of Thrones"); + book.setPublisher("Bantam Spectra"); + book.setPublicationYear("1996"); + book.setGenre("Fantasy"); + author = new Author(); + author.setName("R.R.Martin"); + author.setDob("20/09/1948"); + book.setAuthors(Set.of(author)); + bookRepository.save(book); + + book = new Book(); + book.setName("American Gods"); + book.setPublisher("Headline"); + book.setPublicationYear("2001"); + book.setGenre("Fantasy"); + author = new Author(); + author.setName("Neil Gaiman"); + author.setDob("10/11/1960"); + book.setAuthors(Set.of(author)); + bookRepository.save(book); + + book = new Book(); + book.setName("The Passenger"); + book.setPublisher("Knopf"); + book.setPublicationYear("2022"); + book.setGenre("Mystery"); + author = new Author(); + author.setName("Cormac McCarthy"); + author.setDob("20/07/1933"); + book.setAuthors(Set.of(author)); + bookRepository.save(book); + + book = new Book(); + book.setName("Gone Girl"); + book.setPublisher("Crown Publishing Group"); + book.setPublicationYear("2012"); + book.setGenre("Mystery"); + author = new Author(); + author.setName("Gillian Flynn"); + author.setDob("24/02/1971"); + book.setAuthors(Set.of(author)); + bookRepository.save(book); + + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/AuthUserDetailsService.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/AuthUserDetailsService.java new file mode 100644 index 000000000..44aa42292 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/AuthUserDetailsService.java @@ -0,0 +1,34 @@ +package com.reflectoring.security.service; + +import com.reflectoring.security.config.UserProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; + +@Service +public class AuthUserDetailsService implements UserDetailsService { + + private final UserProperties userProperties; + + @Autowired + public AuthUserDetailsService(UserProperties userProperties) { + this.userProperties = userProperties; + } + + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + if (StringUtils.isEmpty(username) || !username.equals(userProperties.getName())) { + throw new UsernameNotFoundException(String.format("User not found, or unauthorized %s", username)); + } + + return new User(userProperties.getName(), userProperties.getPassword(), new ArrayList<>()); + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/BookService.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/BookService.java new file mode 100644 index 000000000..b2e77b6ff --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/BookService.java @@ -0,0 +1,36 @@ +package com.reflectoring.security.service; + +import com.reflectoring.security.mapper.BookMapper; +import com.reflectoring.security.mapstruct.BookDto; +import com.reflectoring.security.persistence.Book; +import com.reflectoring.security.repository.BookRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class BookService { + + private static final Logger log = LoggerFactory.getLogger(BookService.class); + + private final BookRepository bookRepository; + + private final BookMapper bookMapper; + + public BookService(BookRepository bookRepository, BookMapper bookMapper) { + this.bookRepository = bookRepository; + this.bookMapper = bookMapper; + } + + public List getBook(String genre) { + List books = bookRepository.findByGenre(genre); + return bookMapper.bookToBookDto(books); + } + + public List getAllBooks() { + List books = bookRepository.findAll(); + return bookMapper.bookToBookDto(books); + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/TokenService.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/TokenService.java new file mode 100644 index 000000000..b1e981ac1 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/service/TokenService.java @@ -0,0 +1,41 @@ +package com.reflectoring.security.service; + +import com.reflectoring.security.jwt.JwtHelper; +import com.reflectoring.security.model.TokenRequest; +import com.reflectoring.security.model.TokenResponse; +import org.apache.catalina.User; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@Service +public class TokenService { + + private final AuthenticationManager authenticationManager; + + private final AuthUserDetailsService userDetailsService; + + private final JwtHelper jwtHelper; + + public TokenService(AuthenticationManager authenticationManager, + AuthUserDetailsService userDetailsService, + JwtHelper jwtHelper) { + this.authenticationManager = authenticationManager; + this.userDetailsService = userDetailsService; + this.jwtHelper = jwtHelper; + } + + + public TokenResponse generateToken(TokenRequest tokenRequest) { + this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(tokenRequest.getUsername(), tokenRequest.getPassword())); + final UserDetails userDetails = userDetailsService.loadUserByUsername(tokenRequest.getUsername()); + String token = jwtHelper.createToken(Collections.emptyMap(), userDetails.getUsername()); + return TokenResponse.builder() + .token(token) + .build(); + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/BookController.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/BookController.java new file mode 100644 index 000000000..a340a83c5 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/BookController.java @@ -0,0 +1,34 @@ +package com.reflectoring.security.web; + +import com.reflectoring.security.mapstruct.BookDto; +import com.reflectoring.security.service.BookService; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@Tag(name = "Library Controller", description = "Get library books") +public class BookController { + + private static final Logger log = LoggerFactory.getLogger(BookController.class); + + private final BookService bookService; + + public BookController(BookService bookService) { + this.bookService = bookService; + } + + @GetMapping("/library/books/all") + public ResponseEntity> getAllBooks() { + return ResponseEntity.ok().body(bookService.getAllBooks()); + } + + + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/GlobalExceptionHandler.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/GlobalExceptionHandler.java new file mode 100644 index 000000000..e39fc78fc --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/GlobalExceptionHandler.java @@ -0,0 +1,17 @@ +package com.reflectoring.security.web; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler({BadCredentialsException.class}) + public ResponseEntity handleBadCredentialsException(BadCredentialsException exception) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .body(exception.getMessage()); + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/TokenController.java b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/TokenController.java new file mode 100644 index 000000000..e3bc9dcce --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/web/TokenController.java @@ -0,0 +1,27 @@ +package com.reflectoring.security.web; + +import com.reflectoring.security.model.TokenRequest; +import com.reflectoring.security.model.TokenResponse; +import com.reflectoring.security.service.TokenService; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Tag(name = "Create Token", description = "Create Token") +public class TokenController { + + private final TokenService tokenService; + + public TokenController(TokenService tokenService) { + this.tokenService = tokenService; + } + + @PostMapping("/token/create") + public TokenResponse createToken(@RequestBody TokenRequest tokenRequest) { + return tokenService.generateToken(tokenRequest); + } + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/resources/application.yml b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/application.yml new file mode 100644 index 000000000..eb310817f --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/application.yml @@ -0,0 +1,25 @@ +server: + port: 8083 + +spring: + security: + user: + name: libUser + password: libPassword + datasource: + driver-class-name: org.hsqldb.jdbc.JDBCDriver + url: jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1 + username: sa + password: + +jwt: + secretKey: 5JzoMbk6E5qIqHSuBTgeQCARtUsxAkBiHwdjXOSW8kWdXzYmP3X51C0 + validity: 60000 + +springdoc: + swagger-ui: + path: /swagger-ui + +logging: + level: + org.springframework: DEBUG \ No newline at end of file diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/homePage.html b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/homePage.html new file mode 100644 index 000000000..e1b3b52ab --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/homePage.html @@ -0,0 +1,14 @@ + + + + + Title + + +

HomePage

+
+ Logout
+

SUCCESS

+
+ + \ No newline at end of file diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/invalidSession.html b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/invalidSession.html new file mode 100644 index 000000000..bb31c14d6 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/invalidSession.html @@ -0,0 +1,10 @@ + + + + + Title + + +

Invalid Session

+ + \ No newline at end of file diff --git a/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/login.html b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/login.html new file mode 100644 index 000000000..96cf0e71d --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/main/resources/templates/login.html @@ -0,0 +1,23 @@ + + + + Please Log In + + +

Please Log In

+
+ Invalid username and password.
+
+ You have been logged out.
+
+
+ +
+
+ +
+ +
+ + + diff --git a/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/SecurityApplicationTests.java b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/SecurityApplicationTests.java new file mode 100644 index 000000000..f5a983ac9 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/SecurityApplicationTests.java @@ -0,0 +1,13 @@ +package com.reflectoring.security; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SecurityApplicationTests { + + /*@Test + void contextLoads() { + }*/ + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/jwt/JsonCreatorTest.java b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/jwt/JsonCreatorTest.java new file mode 100644 index 000000000..9af1d8ad4 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/jwt/JsonCreatorTest.java @@ -0,0 +1,30 @@ +package com.reflectoring.security.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static com.reflectoring.security.jwt.JWTCreator.parseJwt; +import static org.junit.jupiter.api.Assertions.*; + +public class JsonCreatorTest { + + @Test + public void testParseJwtClaims() { + String jwtToken = JWTCreator.createJwt(); + assertNotNull(jwtToken); + Jws claims = JWTCreator.parseJwt(jwtToken); + assertNotNull(claims); + Assertions.assertAll( + () -> assertNotNull(claims.getSignature()), + () -> assertNotNull(claims.getHeader()), + () -> assertNotNull(claims.getBody()), + () -> assertEquals(claims.getHeader().getAlgorithm(), "HS256"), + () -> assertEquals(claims.getBody().get("id"), "abc123"), + () -> assertEquals(claims.getBody().get("role"), "admin"), + () -> assertEquals(claims.getBody().getIssuer(), "TestApplication") + ); + } + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/web/BookControllerTest.java b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/web/BookControllerTest.java new file mode 100644 index 000000000..6591b31b5 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/web/BookControllerTest.java @@ -0,0 +1,70 @@ +package com.reflectoring.security.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.reflectoring.security.model.TokenRequest; +import com.reflectoring.security.model.TokenResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlGroup; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_METHOD; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@SqlGroup({ + @Sql(value = "classpath:init/first.sql", executionPhase = BEFORE_TEST_METHOD), + @Sql(value = "classpath:init/second.sql", executionPhase = BEFORE_TEST_METHOD) +}) + +public class BookControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void failsAsBearerTokenNotSet() throws Exception { + mockMvc.perform(get("/library/books/all")) + .andDo(print()) + .andExpect(status().isUnauthorized()); + } + + @Test + void testWithValidBearerToken() throws Exception { + TokenRequest request = TokenRequest.builder() + .username("libUser") + .password("libPassword") + .build(); + MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/token/create") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(request))) + .andExpect(status().isOk()).andReturn(); + String resultStr = mvcResult.getResponse().getContentAsString(); + TokenResponse token = new ObjectMapper().readValue(resultStr, TokenResponse.class); + mockMvc.perform(get("/library/books/all") + .header("Authorization", "Bearer " + token.getToken())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(5))); + } + + @Test + void testWithInvalidBearerToken() throws Exception { + mockMvc.perform(get("/library/books/all") + .header("Authorization", "Bearer 123")) + .andDo(print()) + .andExpect(status().isUnauthorized()); + } + +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/web/TokenControllerTest.java b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/web/TokenControllerTest.java new file mode 100644 index 000000000..16aff3d4a --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/test/java/com/reflectoring/security/web/TokenControllerTest.java @@ -0,0 +1,44 @@ +package com.reflectoring.security.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.reflectoring.security.model.TokenRequest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class TokenControllerTest { + @Autowired + private MockMvc mvc; + + @Test + public void shouldNotAllowAccessToUnauthenticatedUsers() throws Exception { + TokenRequest request = TokenRequest.builder() + .username("testUser") + .password("testPassword") + .build(); + mvc.perform(MockMvcRequestBuilders.post("/token/create") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(request))) + .andExpect(status().isUnauthorized()); + } + + @Test + public void shouldGenerateAuthToken() throws Exception { + TokenRequest request = TokenRequest.builder() + .username("libUser") + .password("libPassword") + .build(); + mvc.perform(MockMvcRequestBuilders.post("/token/create") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(request))) + .andExpect(status().isOk()); + } +} diff --git a/spring-security-jwt/getting-started/SecureApplication/src/test/resources/application.yml b/spring-security-jwt/getting-started/SecureApplication/src/test/resources/application.yml new file mode 100644 index 000000000..0355df6d7 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/test/resources/application.yml @@ -0,0 +1,22 @@ +server: + port: 8083 + +spring: + security: + user: + name: libUser + password: libPassword + datasource: + driver-class-name: org.hsqldb.jdbc.JDBCDriver + url: jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1 + username: sa + password: + +jwt: + secretKey: 5JzoMbk6E5qIqHSuBTgeQCARtUsxAkBiHwdjXOSW8kWdXzYmP3X51C0 + validity: 600000 + + +logging: + level: + org.springframework: DEBUG \ No newline at end of file diff --git a/spring-security-jwt/getting-started/SecureApplication/src/test/resources/init/first.sql b/spring-security-jwt/getting-started/SecureApplication/src/test/resources/init/first.sql new file mode 100644 index 000000000..f82b525a3 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/test/resources/init/first.sql @@ -0,0 +1,3 @@ +TRUNCATE TABLE AUTHOR_BOOK RESTART IDENTITY; +TRUNCATE TABLE BOOK RESTART IDENTITY; +TRUNCATE TABLE AUTHOR RESTART IDENTITY; \ No newline at end of file diff --git a/spring-security-jwt/getting-started/SecureApplication/src/test/resources/init/second.sql b/spring-security-jwt/getting-started/SecureApplication/src/test/resources/init/second.sql new file mode 100644 index 000000000..c1b663218 --- /dev/null +++ b/spring-security-jwt/getting-started/SecureApplication/src/test/resources/init/second.sql @@ -0,0 +1,5 @@ +INSERT INTO BOOK (id, name, publisher, publication_year, genre) VALUES (1, 'The Kite Runner', 'Riverhead books', '2003', 'Fiction'); +INSERT INTO BOOK (id, name, publisher, publication_year, genre) VALUES (2, 'Exiles', 'Pan Macmillan', '2022', 'Fiction'); +INSERT INTO BOOK (id, name, publisher, publication_year, genre) VALUES (3, 'A Game of Thrones', 'Bantam Spectra', '1996', 'Fiction'); +INSERT INTO BOOK (id, name, publisher, publication_year, genre) VALUES (4, 'American Gods', 'Headline', '2001', 'Fantasy'); +INSERT INTO BOOK (id, name, publisher, publication_year, genre) VALUES (5, 'The Passenger', 'Knopf', '2022', 'Mystery'); \ No newline at end of file