├── .gitignore ├── README.md ├── build.gradle ├── docker-compose.yml ├── flights-api ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── api │ │ │ ├── AccessRule.java │ │ │ ├── AccessRuleAuthorizationManager.java │ │ │ ├── AccessRuleRepository.java │ │ │ ├── Flight.java │ │ │ ├── FlightController.java │ │ │ ├── FlightRepository.java │ │ │ ├── FlightsApiApplication.java │ │ │ ├── SecurityConfiguration.java │ │ │ └── UserController.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── example │ └── api │ ├── FlightControllerTests.java │ └── FlightsApiApplicationTests.java ├── flights-web ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── web │ │ │ ├── CsrfCookieWebFilter.java │ │ │ ├── FlightsWebApplication.java │ │ │ └── SecurityConfiguration.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── example │ └── web │ └── FlightsWebApplicationTests.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jump-to ├── look-at ├── run-native-api-linux ├── settings.gradle ├── spa ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── build.gradle ├── karma.conf.js ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── controller-panel │ │ │ ├── controller-panel-routing.module.ts │ │ │ ├── controller-panel.component.css │ │ │ ├── controller-panel.component.html │ │ │ ├── controller-panel.component.ts │ │ │ └── controller-panel.module.ts │ │ ├── flights │ │ │ ├── flights-routing.module.ts │ │ │ ├── flights.component.css │ │ │ ├── flights.component.html │ │ │ ├── flights.component.ts │ │ │ └── flights.module.ts │ │ ├── home │ │ │ ├── home-routing.module.ts │ │ │ ├── home.component.css │ │ │ ├── home.component.html │ │ │ ├── home.component.ts │ │ │ └── home.module.ts │ │ ├── interceptors │ │ │ └── gateway.interceptor.ts │ │ ├── layout │ │ │ ├── layout-routing.module.ts │ │ │ ├── layout.component.css │ │ │ ├── layout.component.html │ │ │ ├── layout.component.ts │ │ │ └── layout.module.ts │ │ ├── services │ │ │ ├── flights.service.ts │ │ │ └── user.service.ts │ │ ├── shared │ │ │ ├── flight-status.pipe.ts │ │ │ └── shared.module.ts │ │ └── user-info │ │ │ ├── user-info-routing.module.ts │ │ │ ├── user-info.component.css │ │ │ ├── user-info.component.html │ │ │ ├── user-info.component.ts │ │ │ └── user-info.module.ts │ ├── assets │ │ ├── .gitkeep │ │ └── img │ │ │ ├── github.png │ │ │ ├── logo.png │ │ │ └── spring-logo.png │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json └── sso ├── .gitignore ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── example │ │ └── sso │ │ ├── SecurityConfiguration.java │ │ └── SsoApplication.java └── resources │ └── application.yml └── test └── java └── com └── example └── sso └── SsoApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spring Security 5.5 From Taxi to Takeoff 2 | == 3 | 4 | This repository is for the SpringOne 2021 presentation titled "Spring Security 5.5 From Taxi to Takeoff". 5 | It contains the following four applications: 6 | 7 | * [spa](/spa) - An Angular-based Single Page Application 8 | * [flights-web](/flights-web) - A Spring-powered OAuth 2.0 client application 9 | * [flights-api](/flights-api) - A REST API secured with Spring Security OAuth 2.0 Resource Server 10 | * [sso](/sso) - A Spring-powered OAuth 2.0 Authorization Server 11 | 12 | The final state is a single-page application that authenticates the user with OpenID Connect 1.0 and collaborates with a REST API using OAuth 2.0 bearer tokens. It brings together the following concepts: 13 | 14 | * The `spa` is served as static content from the `/static` directory of `flights-web` 15 | * The `sso` application is configured as an OpenID Connect 1.0 provider that mints signed JWTs for an OAuth 2.0 client 16 | * The `flights-api` application is simplified to act as a resource server that verifies signed JWTs for authentication 17 | * The `flights-web` application acts as an OAuth 2.0 client, performs token relay with Spring Cloud Gateway, and implements the [backend for frontend (bff)](https://www.ietf.org/id/draft-bertocci-oauth2-tmi-bff-01.html) pattern to store access tokens on the server 18 | * The `spa` authenticates with `flights-web` using a standard session cookie (`SESSIONID`), and additionally uses a cookie/header pair for csrf protection (`XSRF-TOKEN`, `X-XSRF-TOKEN`) 19 | 20 | Getting Started 21 | -- 22 | 23 | First, start the authorization server, with the following command: 24 | 25 | ```shell 26 | ./gradlew :sso:bootRun 27 | ``` 28 | 29 | Next, start the REST API like so: 30 | 31 | ```shell 32 | ./gradlew :flights-api:bootRun 33 | ``` 34 | 35 | You will need the [Angular CLI](https://angular.io/cli) installed. 36 | Then, start the SPA and OAuth 2.0 Client application using the following command: 37 | 38 | ```shell 39 | ./gradlew :flights-web:bootRun 40 | ``` 41 | 42 | Finally, navigate to http://127.0.0.1:8000 43 | 44 | NOTE: Ensure you have added `127.0.0.1 auth-server` to your `/etc/hosts` file, which is used to keep the authorization server on a separate host to distinguish cookies from other apps running on `localhost`. 45 | 46 | Running Natively 47 | -- 48 | 49 | To run the application's natively, you can use [spring-native](https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#getting-started-native-image) to build the images locally, or pull the pre-built images from Docker Hub. A docker-compose.yml file is provided to run using the pre-built images. 50 | 51 | ```shell 52 | docker-compose up 53 | ``` 54 | 55 | Following Along 56 | -- 57 | 58 | To follow along with the presentation, start with the `main` branch: 59 | 60 | ```shell 61 | git checkout main 62 | ``` 63 | 64 | Each checkpoint along the way contains a specific commit message you can use to quickly hop around in the presentation. For example, to switch to *Step 1 - Secure by default*, do the following: 65 | 66 | ```shell 67 | ./look-at 'Step 1' 68 | ``` 69 | 70 | This will safely attempt to switch to a particular commit, but you will be in 'detached HEAD' state. To reset to a particular point such as *Step 12 - Secure BFF application* ,`git checkout main` again, and do the following: 71 | 72 | ```shell 73 | ./jump-to 'Step 12' 74 | ``` 75 | 76 | This will hard-reset to the specified commit and discard changes in your working directory. 77 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'org.example' 6 | version '1.0-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' 14 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' 15 | } 16 | 17 | test { 18 | useJUnitPlatform() 19 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | sso: 4 | container_name: springone-2021-sso 5 | image: sjohnr/springone-2021-sso 6 | hostname: auth-server 7 | ports: 8 | - 9000:9000 9 | healthcheck: 10 | test: 'exit 0' 11 | interval: 1s 12 | retries: 15 13 | flights-api: 14 | container_name: springone-2021-flights-api 15 | image: sjohnr/springone-2021-flights-api 16 | hostname: flights-api 17 | ports: 18 | - 8090:8090 19 | depends_on: 20 | sso: 21 | condition: service_healthy 22 | flights-web: 23 | container_name: springone-2021-flights-web 24 | image: sjohnr/springone-2021-flights-web 25 | ports: 26 | - 8000:8000 27 | environment: 28 | FLIGHTS_API_HOST: flights-api 29 | depends_on: 30 | sso: 31 | condition: service_healthy 32 | -------------------------------------------------------------------------------- /flights-api/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /flights-api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.6.3' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | id 'org.springframework.experimental.aot' version '0.10.3' 6 | id 'org.graalvm.buildtools.native' version '0.9.3' 7 | id 'org.hibernate.orm' 8 | } 9 | 10 | group = 'com.example' 11 | version = '0.0.1-SNAPSHOT' 12 | sourceCompatibility = '11' 13 | 14 | repositories { 15 | mavenCentral() 16 | maven { url 'https://repo.spring.io/release' } 17 | } 18 | 19 | dependencies { 20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 21 | implementation 'org.springframework.boot:spring-boot-starter-web' 22 | implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' 23 | runtimeOnly 'com.h2database:h2' 24 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 25 | testImplementation 'org.springframework.security:spring-security-test' 26 | } 27 | 28 | test { 29 | useJUnitPlatform() 30 | } 31 | 32 | bootBuildImage { 33 | builder = 'paketobuildpacks/builder:tiny' 34 | environment = ['BP_NATIVE_IMAGE': 'true', 'BP_NATIVE_IMAGE_BUILD_ARGUMENTS': '--enable-url-protocols=http'] 35 | } 36 | 37 | nativeBuild { 38 | classpath processAotResources.outputs, compileAotJava.outputs 39 | } 40 | 41 | nativeTest { 42 | classpath processAotTestResources.outputs, compileAotTestJava.outputs 43 | } 44 | 45 | hibernate { 46 | enhance { 47 | enableLazyInitialization = true 48 | enableDirtyTracking = true 49 | enableAssociationManagement = true 50 | enableExtendedEnhancement = false 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /flights-api/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/flights-api/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /flights-api/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /flights-api/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /flights-api/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /flights-api/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flight-data' 2 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/AccessRule.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Id; 6 | 7 | @Entity(name="rules") 8 | public class AccessRule { 9 | @Id 10 | private String pattern; 11 | 12 | @Column 13 | private String authority; 14 | 15 | protected AccessRule() {} 16 | 17 | public AccessRule(String pattern, String authority) { 18 | this.pattern = pattern; 19 | this.authority = authority; 20 | } 21 | 22 | public String getPattern() { 23 | return pattern; 24 | } 25 | 26 | public void setPattern(String pattern) { 27 | this.pattern = pattern; 28 | } 29 | 30 | public String getAuthority() { 31 | return authority; 32 | } 33 | 34 | public void setAuthority(String authority) { 35 | this.authority = authority; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/AccessRuleAuthorizationManager.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import org.springframework.boot.context.event.ApplicationReadyEvent; 6 | import org.springframework.context.event.EventListener; 7 | import org.springframework.security.authorization.AuthorityAuthorizationManager; 8 | import org.springframework.security.authorization.AuthorizationDecision; 9 | import org.springframework.security.authorization.AuthorizationManager; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.web.access.intercept.RequestAuthorizationContext; 12 | import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager; 13 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 14 | import org.springframework.stereotype.Component; 15 | 16 | import static org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager.Builder; 17 | import static org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager.builder; 18 | 19 | @Component 20 | public class AccessRuleAuthorizationManager implements AuthorizationManager { 21 | 22 | private final AccessRuleRepository rules; 23 | private RequestMatcherDelegatingAuthorizationManager delegate; 24 | 25 | public AccessRuleAuthorizationManager(AccessRuleRepository rules) { 26 | this.rules = rules; 27 | } 28 | 29 | @Override 30 | public AuthorizationDecision check(Supplier authentication, RequestAuthorizationContext object) { 31 | return this.delegate.check(authentication, object.getRequest()); 32 | } 33 | 34 | @EventListener 35 | void applyRules(ApplicationReadyEvent event) { 36 | Builder builder = builder(); 37 | for (AccessRule rule : this.rules.findAll()) { 38 | builder.add( 39 | new AntPathRequestMatcher(rule.getPattern()), 40 | AuthorityAuthorizationManager.hasAuthority(rule.getAuthority()) 41 | ); 42 | } 43 | this.delegate = builder.build(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/AccessRuleRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface AccessRuleRepository extends CrudRepository { 8 | } 9 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/Flight.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.Id; 5 | 6 | @Entity 7 | public class Flight { 8 | @Id 9 | private String flightNumber; 10 | private String pilotId; 11 | private Status status = Status.BOARD; 12 | 13 | public Flight() { 14 | } 15 | 16 | public Flight(String flightNumber, String pilotId, Status status) { 17 | this.flightNumber = flightNumber; 18 | this.pilotId = pilotId; 19 | this.status = status; 20 | } 21 | 22 | public String getFlightNumber() { 23 | return flightNumber; 24 | } 25 | 26 | public void setFlightNumber(String flightNumber) { 27 | this.flightNumber = flightNumber; 28 | } 29 | 30 | public String getPilotId() { 31 | return pilotId; 32 | } 33 | 34 | public void setPilotId(String pilotId) { 35 | this.pilotId = pilotId; 36 | } 37 | 38 | public Status getStatus() { 39 | return status; 40 | } 41 | 42 | public void setStatus(Status status) { 43 | this.status = status; 44 | } 45 | 46 | public enum Status { 47 | BOARD, TAXI, TAKE_OFF 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/FlightController.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import java.util.List; 4 | 5 | import javax.transaction.Transactional; 6 | 7 | import org.springframework.security.access.prepost.PostAuthorize; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PutMapping; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | @RestController 16 | @RequestMapping("/flights") 17 | public class FlightController { 18 | private final FlightRepository flightRepository; 19 | 20 | public FlightController(FlightRepository flightRepository) { 21 | this.flightRepository = flightRepository; 22 | } 23 | 24 | @GetMapping("/all") 25 | public List getAllFlights() { 26 | return this.flightRepository.findAll(); 27 | } 28 | 29 | @GetMapping 30 | public List getFlights(Authentication authentication) { 31 | return this.flightRepository.findByPilotId(authentication.getName()); 32 | } 33 | 34 | @PutMapping("/{flightNumber}/taxi") 35 | @PostAuthorize("returnObject.pilotId == authentication.name") 36 | @Transactional 37 | public Flight taxi(@PathVariable String flightNumber) { 38 | Flight flight = this.flightRepository.findByFlightNumber(flightNumber); 39 | flight.setStatus(Flight.Status.TAXI); 40 | return flight; 41 | } 42 | 43 | @PutMapping("/{flightNumber}/take-off") 44 | @Transactional 45 | public Flight takeOff(@PathVariable String flightNumber) { 46 | Flight flight = this.flightRepository.findByFlightNumber(flightNumber); 47 | flight.setStatus(Flight.Status.TAKE_OFF); 48 | return flight; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/FlightRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | @Repository 9 | public interface FlightRepository extends JpaRepository { 10 | Flight findByFlightNumber(String flightNumber); 11 | List findByPilotId(String pilotId); 12 | } 13 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/FlightsApiApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import org.springframework.boot.CommandLineRunner; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.nativex.hint.AccessBits; 7 | import org.springframework.nativex.hint.TypeHint; 8 | import org.springframework.nativex.hint.TypeHints; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.transaction.annotation.EnableTransactionManagement; 11 | 12 | @SpringBootApplication 13 | @EnableTransactionManagement(order = -1) 14 | @TypeHints({ 15 | @TypeHint(typeNames = "org.springframework.security.access.expression.method.MethodSecurityExpressionRoot", access = AccessBits.ALL), 16 | @TypeHint(typeNames = "org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken", access = AccessBits.ALL) 17 | }) 18 | public class FlightsApiApplication { 19 | 20 | public static void main(String[] args) { 21 | SpringApplication.run(FlightsApiApplication.class, args); 22 | } 23 | 24 | @Component 25 | static class FlightDataInitializer implements CommandLineRunner { 26 | 27 | private final FlightRepository flightRepository; 28 | private final AccessRuleRepository rules; 29 | 30 | FlightDataInitializer(FlightRepository flightRepository, AccessRuleRepository rules) { 31 | this.flightRepository = flightRepository; 32 | this.rules = rules; 33 | } 34 | 35 | @Override 36 | public void run(String... args) { 37 | this.flightRepository.save(new Flight("101", "josh", Flight.Status.TAXI)); 38 | this.flightRepository.save(new Flight("102", "marcus", Flight.Status.BOARD)); 39 | this.flightRepository.save(new Flight("103", "steve", Flight.Status.BOARD)); 40 | this.rules.save(new AccessRule("/flights/all", "flights:all")); 41 | this.rules.save(new AccessRule("/flights/*/take-off", "flights:approve")); 42 | this.rules.save(new AccessRule("/flights", "flights:read")); 43 | this.rules.save(new AccessRule("/**", "flights:write")); 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import java.util.Set; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; 10 | import org.springframework.security.core.authority.AuthorityUtils; 11 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; 12 | import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; 13 | import org.springframework.security.web.SecurityFilterChain; 14 | 15 | @Configuration 16 | @EnableGlobalMethodSecurity(prePostEnabled = true) 17 | public class SecurityConfiguration { 18 | 19 | @Bean 20 | public SecurityFilterChain securityFilterChain(HttpSecurity http, AccessRuleAuthorizationManager access) throws Exception { 21 | // @formatter:off 22 | http 23 | .authorizeHttpRequests((authz) -> authz.anyRequest().access(access)) 24 | .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); 25 | // @formatter:on 26 | return http.build(); 27 | } 28 | 29 | @Bean 30 | public JwtAuthenticationConverter jwtAuthenticationConverter() { 31 | JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); 32 | authoritiesConverter.setAuthorityPrefix(""); 33 | 34 | JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter(); 35 | authenticationConverter.setJwtGrantedAuthoritiesConverter((jwt) -> { 36 | if (!"josh".equals(jwt.getSubject())) { 37 | return authoritiesConverter.convert(jwt); 38 | } 39 | Set authorities = AuthorityUtils.authorityListToSet(authoritiesConverter.convert(jwt)); 40 | if (authorities.contains("flights:write")) { 41 | authorities.add("flights:approve"); 42 | } 43 | if (authorities.contains("flights:read")) { 44 | authorities.add("flights:all"); 45 | } 46 | return AuthorityUtils.createAuthorityList(authorities.toArray(String[]::new)); 47 | }); 48 | 49 | return authenticationConverter; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /flights-api/src/main/java/com/example/api/UserController.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import java.security.Principal; 4 | 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RequestMapping("/user") 11 | public class UserController { 12 | 13 | @GetMapping("/info") 14 | public Principal getInfo(Principal principal) { 15 | return principal; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /flights-api/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8090 3 | 4 | spring: 5 | security: 6 | oauth2: 7 | resourceserver: 8 | jwt: 9 | jwk-set-uri: http://auth-server:9000/oauth2/jwks 10 | issuer-uri: http://auth-server:9000 11 | 12 | logging: 13 | level: 14 | org.springframework.security: TRACE 15 | org.springframework.security.access: INFO 16 | -------------------------------------------------------------------------------- /flights-api/src/test/java/com/example/api/FlightControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 9 | import org.springframework.boot.test.mock.mockito.MockBean; 10 | import org.springframework.security.test.context.support.WithMockUser; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | 13 | import static org.mockito.ArgumentMatchers.anyString; 14 | import static org.mockito.Mockito.when; 15 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 18 | 19 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 20 | @AutoConfigureMockMvc 21 | public class FlightControllerTests { 22 | private static final String FLIGHT_102 = "102"; 23 | private static final String FLIGHT_103 = "103"; 24 | 25 | @Autowired 26 | MockMvc mockMvc; 27 | 28 | @MockBean 29 | FlightRepository flightRepository; 30 | 31 | @Test 32 | @WithMockUser(username = "marcus", authorities = "flights:write") 33 | void taxiWhenPilotIdEqualToAuthenticationNameThenOk() throws Exception { 34 | when(this.flightRepository.findByFlightNumber(anyString())) 35 | .thenReturn(new Flight(FLIGHT_102, "marcus", Flight.Status.BOARD)); 36 | 37 | this.mockMvc.perform(put("/flights/{flightNumber}/taxi", FLIGHT_102) 38 | .with(csrf().asHeader())) 39 | .andExpect(status().isOk()); 40 | } 41 | 42 | @Test 43 | @WithMockUser(username = "marcus", authorities = "flights:write") 44 | void taxiWhenPilotIdNotEqualToAuthenticationNameThenForbidden() throws Exception { 45 | when(this.flightRepository.findByFlightNumber(anyString())) 46 | .thenReturn(new Flight(FLIGHT_103, "steve", Flight.Status.BOARD)); 47 | 48 | this.mockMvc.perform(put("/flights/{flightNumber}/taxi", FLIGHT_103) 49 | .with(csrf().asHeader())) 50 | .andExpect(status().isForbidden()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /flights-api/src/test/java/com/example/api/FlightsApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class FlightsApiApplicationTests { 9 | 10 | @Test 11 | void contextLoads() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /flights-web/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /flights-web/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.6.3' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | } 6 | 7 | group = 'com.example' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '11' 10 | 11 | repositories { 12 | mavenCentral() 13 | maven { url 'https://repo.spring.io/milestone' } 14 | } 15 | 16 | ext { 17 | set('springCloudVersion', "2021.0.0-RC1") 18 | } 19 | 20 | dependencies { 21 | implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' 22 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 23 | implementation 'org.springframework.cloud:spring-cloud-starter-gateway' 24 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 25 | testImplementation 'io.projectreactor:reactor-test' 26 | testImplementation 'org.springframework.security:spring-security-test' 27 | } 28 | 29 | dependencyManagement { 30 | imports { 31 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 32 | } 33 | } 34 | 35 | test { 36 | useJUnitPlatform() 37 | } 38 | 39 | task mergeSpa(type: Copy) { 40 | from "$rootDir/spa/dist/spa" 41 | into "$buildDir/resources/main/static" 42 | 43 | dependsOn(':spa:prepareSpaResources') 44 | } 45 | 46 | compileJava.dependsOn mergeSpa 47 | -------------------------------------------------------------------------------- /flights-web/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/flights-web/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /flights-web/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /flights-web/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /flights-web/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /flights-web/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gateway' 2 | -------------------------------------------------------------------------------- /flights-web/src/main/java/com/example/web/CsrfCookieWebFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.web; 2 | 3 | import java.time.Duration; 4 | 5 | import reactor.core.publisher.Mono; 6 | 7 | import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; 8 | import org.springframework.http.ResponseCookie; 9 | import org.springframework.security.web.server.csrf.CsrfToken; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.server.ServerWebExchange; 12 | import org.springframework.web.server.WebFilter; 13 | import org.springframework.web.server.WebFilterChain; 14 | 15 | @Component 16 | public class CsrfCookieWebFilter implements WebFilter { 17 | 18 | @Override 19 | public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { 20 | String key = CsrfToken.class.getName(); 21 | Mono csrfToken = null != exchange.getAttribute(key) ? exchange.getAttribute(key) : Mono.empty(); 22 | return csrfToken.doOnSuccess(token -> { 23 | ResponseCookie cookie = ResponseCookie.from("XSRF-TOKEN", token.getToken()).maxAge(Duration.ofHours(1)) 24 | .httpOnly(false).path("/").sameSite(WebFluxProperties.SameSite.LAX.attribute()).build(); 25 | exchange.getResponse().getCookies().add("XSRF-TOKEN", cookie); 26 | }).then(chain.filter(exchange)); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /flights-web/src/main/java/com/example/web/FlightsWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class FlightsWebApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(FlightsWebApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /flights-web/src/main/java/com/example/web/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.web; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.Customizer; 6 | import org.springframework.security.config.web.server.ServerHttpSecurity; 7 | import org.springframework.security.web.server.SecurityWebFilterChain; 8 | import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository; 9 | 10 | @Configuration 11 | public class SecurityConfiguration { 12 | 13 | @Bean 14 | SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { 15 | // @formatter:off 16 | http 17 | .authorizeExchange((authorize) -> authorize 18 | .anyExchange().authenticated() 19 | ) 20 | .oauth2Login(Customizer.withDefaults()) 21 | .csrf((csrf) -> csrf 22 | .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) 23 | ); 24 | // @formatter:on 25 | return http.build(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /flights-web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | 4 | logging: 5 | level: 6 | org.springframework.security: TRACE 7 | 8 | spring: 9 | security: 10 | oauth2: 11 | client: 12 | registration: 13 | air-traffic-control-client: 14 | provider: spring 15 | client-id: air-traffic-control 16 | client-secret: secret 17 | scope: openid,flights:read,flights:write 18 | client-name: Spring 19 | provider: 20 | spring: 21 | issuer-uri: http://auth-server:9000 22 | cloud: 23 | gateway: 24 | routes: 25 | - id: resource 26 | uri: http://${FLIGHTS_API_HOST:localhost}:8090 27 | predicates: 28 | - Path=/flights/**, /user/** 29 | filters: 30 | - TokenRelay= 31 | - id: default 32 | uri: forward:/index.html 33 | predicates: 34 | - Path=/app/** 35 | -------------------------------------------------------------------------------- /flights-web/src/test/java/com/example/web/FlightsWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class FlightsWebApplicationTests { 9 | 10 | @Test 11 | void contextLoads() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /jump-to: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git log --pretty=oneline main | grep "$1 -" | awk '{print $1}' | xargs git reset --hard -------------------------------------------------------------------------------- /look-at: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git log --pretty=oneline main | grep "$1 -" | awk '{print $1}' | xargs git checkout -------------------------------------------------------------------------------- /run-native-api-linux: -------------------------------------------------------------------------------- 1 | docker run --rm -p 8090:8090 flights-api:0.0.1-SNAPSHOT 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { url 'https://repo.spring.io/release' } 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | resolutionStrategy { 8 | eachPlugin { 9 | if (requested.id.id == 'org.hibernate.orm') { 10 | useModule('org.hibernate:hibernate-gradle-plugin:5.4.32.Final') 11 | } 12 | } 13 | } 14 | } 15 | 16 | rootProject.name = 'springone-2021' 17 | 18 | include ":flights-api" 19 | include ":sso" 20 | include ":flights-web" 21 | include ":spa" 22 | -------------------------------------------------------------------------------- /spa/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /spa/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /spa/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | yarn-error.log 40 | testem.log 41 | /typings 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /spa/README.md: -------------------------------------------------------------------------------- 1 | # AirTrafficControlUiSpa 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.1. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /spa/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "spa": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:class": { 10 | "skipTests": true 11 | }, 12 | "@schematics/angular:component": { 13 | "skipTests": true 14 | }, 15 | "@schematics/angular:directive": { 16 | "skipTests": true 17 | }, 18 | "@schematics/angular:guard": { 19 | "skipTests": true 20 | }, 21 | "@schematics/angular:interceptor": { 22 | "skipTests": true 23 | }, 24 | "@schematics/angular:pipe": { 25 | "skipTests": true 26 | }, 27 | "@schematics/angular:service": { 28 | "skipTests": true 29 | }, 30 | "@schematics/angular:application": { 31 | "strict": true 32 | } 33 | }, 34 | "root": "", 35 | "sourceRoot": "src", 36 | "prefix": "app", 37 | "architect": { 38 | "build": { 39 | "builder": "@angular-devkit/build-angular:browser", 40 | "options": { 41 | "outputPath": "dist/spa", 42 | "index": "src/index.html", 43 | "main": "src/main.ts", 44 | "polyfills": "src/polyfills.ts", 45 | "tsConfig": "tsconfig.app.json", 46 | "assets": [ 47 | "src/favicon.ico", 48 | "src/assets" 49 | ], 50 | "styles": [ 51 | "node_modules/bootstrap/dist/css/bootstrap.min.css", 52 | "src/styles.css" 53 | ], 54 | "scripts": [] 55 | }, 56 | "configurations": { 57 | "production": { 58 | "budgets": [ 59 | { 60 | "type": "initial", 61 | "maximumWarning": "500kb", 62 | "maximumError": "1mb" 63 | }, 64 | { 65 | "type": "anyComponentStyle", 66 | "maximumWarning": "2kb", 67 | "maximumError": "4kb" 68 | } 69 | ], 70 | "fileReplacements": [ 71 | { 72 | "replace": "src/environments/environment.ts", 73 | "with": "src/environments/environment.prod.ts" 74 | } 75 | ], 76 | "outputHashing": "all" 77 | }, 78 | "development": { 79 | "buildOptimizer": false, 80 | "optimization": false, 81 | "vendorChunk": true, 82 | "extractLicenses": false, 83 | "sourceMap": true, 84 | "namedChunks": true 85 | } 86 | }, 87 | "defaultConfiguration": "production" 88 | }, 89 | "serve": { 90 | "builder": "@angular-devkit/build-angular:dev-server", 91 | "configurations": { 92 | "production": { 93 | "browserTarget": "spa:build:production" 94 | }, 95 | "development": { 96 | "browserTarget": "spa:build:development" 97 | } 98 | }, 99 | "defaultConfiguration": "development" 100 | }, 101 | "extract-i18n": { 102 | "builder": "@angular-devkit/build-angular:extract-i18n", 103 | "options": { 104 | "browserTarget": "spa:build" 105 | } 106 | }, 107 | "test": { 108 | "builder": "@angular-devkit/build-angular:karma", 109 | "options": { 110 | "main": "src/test.ts", 111 | "polyfills": "src/polyfills.ts", 112 | "tsConfig": "tsconfig.spec.json", 113 | "karmaConfig": "karma.conf.js", 114 | "assets": [ 115 | "src/favicon.ico", 116 | "src/assets" 117 | ], 118 | "styles": [ 119 | "src/styles.css" 120 | ], 121 | "scripts": [] 122 | } 123 | } 124 | } 125 | } 126 | }, 127 | "defaultProject": "spa" 128 | } 129 | -------------------------------------------------------------------------------- /spa/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id "com.github.node-gradle.node" version "3.0.1" 4 | } 5 | 6 | node { 7 | version = '14.17.5' 8 | npmVersion = '6.14.14' 9 | download = true 10 | } 11 | 12 | task prepareSpaResources { 13 | dependsOn npm_run_build 14 | doLast { 15 | print 'Prepared SPA resources' 16 | } 17 | } -------------------------------------------------------------------------------- /spa/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/spa'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /spa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spa", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "~12.2.0", 14 | "@angular/common": "~12.2.0", 15 | "@angular/compiler": "~12.2.0", 16 | "@angular/core": "~12.2.0", 17 | "@angular/forms": "~12.2.0", 18 | "@angular/localize": "~12.2.0", 19 | "@angular/platform-browser": "~12.2.0", 20 | "@angular/platform-browser-dynamic": "~12.2.0", 21 | "@angular/router": "~12.2.0", 22 | "bootstrap": "^4.5.0", 23 | "rxjs": "~6.6.0", 24 | "tslib": "^2.3.0", 25 | "zone.js": "~0.11.4" 26 | }, 27 | "devDependencies": { 28 | "@angular-devkit/build-angular": "~12.2.1", 29 | "@angular/cli": "~12.2.1", 30 | "@angular/compiler-cli": "~12.2.0", 31 | "@types/jasmine": "~3.8.0", 32 | "@types/node": "^12.11.1", 33 | "jasmine-core": "~3.8.0", 34 | "karma": "~6.3.0", 35 | "karma-chrome-launcher": "~3.1.0", 36 | "karma-coverage": "~2.0.3", 37 | "karma-jasmine": "~4.0.0", 38 | "karma-jasmine-html-reporter": "~1.7.0", 39 | "typescript": "~4.3.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spa/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: 'app', 7 | loadChildren: () => import('./layout/layout.module').then(m => m.LayoutModule) 8 | }, 9 | { 10 | path: '**', 11 | redirectTo: '/app/flights' 12 | } 13 | ]; 14 | 15 | @NgModule({ 16 | imports: [RouterModule.forRoot(routes, { useHash: false })], 17 | exports: [RouterModule] 18 | }) 19 | export class AppRoutingModule { } 20 | -------------------------------------------------------------------------------- /spa/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/app/app.component.css -------------------------------------------------------------------------------- /spa/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spa/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'spa'; 10 | } 11 | -------------------------------------------------------------------------------- /spa/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ReactiveFormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 5 | 6 | import { AppRoutingModule } from './app-routing.module'; 7 | import { AppComponent } from './app.component'; 8 | import { GatewayInterceptor } from './interceptors/gateway.interceptor'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent 13 | ], 14 | imports: [ 15 | BrowserModule, 16 | ReactiveFormsModule, 17 | HttpClientModule, 18 | HttpClientXsrfModule, 19 | AppRoutingModule, 20 | ], 21 | providers: [ 22 | { provide: HTTP_INTERCEPTORS, useClass: GatewayInterceptor, multi: true } 23 | ], 24 | bootstrap: [AppComponent] 25 | }) 26 | export class AppModule { } 27 | -------------------------------------------------------------------------------- /spa/src/app/controller-panel/controller-panel-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { ControllerPanelComponent } from './controller-panel.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: ControllerPanelComponent, 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class ControllerPanelRoutingModule { } 17 | -------------------------------------------------------------------------------- /spa/src/app/controller-panel/controller-panel.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/app/controller-panel/controller-panel.component.css -------------------------------------------------------------------------------- /spa/src/app/controller-panel/controller-panel.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Controller Panel

4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 30 | 31 | 32 |
PilotFlight NumberStatusAction
{{ flight.pilotId }}{{ flight.flightNumber }}{{ flight.status | flightStatus }} 21 |
22 |
23 | 26 |
27 |
None
28 |
29 |
33 |
34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /spa/src/app/controller-panel/controller-panel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { Flight, FlightsService } from '../services/flights.service'; 4 | 5 | @Component({ 6 | selector: 'app-controller-panel', 7 | templateUrl: './controller-panel.component.html', 8 | styleUrls: ['./controller-panel.component.css'] 9 | }) 10 | export class ControllerPanelComponent implements OnInit { 11 | 12 | flights$: Observable; 13 | 14 | constructor( 15 | private flightsService: FlightsService, 16 | ) { } 17 | 18 | ngOnInit(): void { 19 | this.updateFlights(); 20 | } 21 | 22 | updateFlights(): void { 23 | this.flights$ = this.flightsService.getAllFlights(); 24 | } 25 | 26 | updateStatus(flight: Flight, newStatus: string) { 27 | this.flightsService.updateFlight(flight.flightNumber, newStatus) 28 | .subscribe(() => flight.status = newStatus); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spa/src/app/controller-panel/controller-panel.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ControllerPanelComponent } from './controller-panel.component'; 4 | import { ControllerPanelRoutingModule } from './controller-panel-routing.module'; 5 | import { SharedModule } from '../shared/shared.module'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | ControllerPanelComponent, 10 | ], 11 | imports: [ 12 | CommonModule, 13 | ControllerPanelRoutingModule, 14 | SharedModule, 15 | ] 16 | }) 17 | export class ControllerPanelModule { } 18 | -------------------------------------------------------------------------------- /spa/src/app/flights/flights-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { FlightsComponent } from './flights.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: FlightsComponent, 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class FlightsRoutingModule { } 17 | -------------------------------------------------------------------------------- /spa/src/app/flights/flights.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/app/flights/flights.component.css -------------------------------------------------------------------------------- /spa/src/app/flights/flights.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Flights

4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 33 | 34 | 35 |
PilotFlight NumberStatusNext Step
{{ flight.pilotId }}{{ flight.flightNumber }}{{ flight.status | flightStatus }} 21 |
22 |
23 | 26 |
27 |
28 | Await Approval 29 |
30 |
None
31 |
32 |
36 |
37 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /spa/src/app/flights/flights.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | import { Flight, FlightsService } from '../services/flights.service'; 4 | 5 | @Component({ 6 | selector: 'app-flights', 7 | templateUrl: './flights.component.html', 8 | styleUrls: ['./flights.component.css'] 9 | }) 10 | export class FlightsComponent implements OnInit { 11 | 12 | flights$: Observable; 13 | 14 | constructor( 15 | private flightsService: FlightsService 16 | ) { 17 | this.flights$ = this.flightsService.getFlights(); 18 | } 19 | 20 | ngOnInit(): void { 21 | } 22 | 23 | updateStatus(flight: Flight, newStatus: string) { 24 | this.flightsService.updateFlight(flight.flightNumber, newStatus) 25 | .subscribe(() => flight.status = newStatus); 26 | } 27 | 28 | updateFlights(): void { 29 | this.flights$ = this.flightsService.getFlights(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /spa/src/app/flights/flights.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { FlightsRoutingModule } from './flights-routing.module'; 5 | import { FlightsComponent } from './flights.component'; 6 | import { SharedModule } from '../shared/shared.module'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [ 11 | FlightsComponent, 12 | ], 13 | imports: [ 14 | CommonModule, 15 | FlightsRoutingModule, 16 | SharedModule, 17 | ] 18 | }) 19 | export class FlightsModule { } 20 | -------------------------------------------------------------------------------- /spa/src/app/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { HomeComponent } from './home.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: HomeComponent 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class HomeRoutingModule { } 17 | -------------------------------------------------------------------------------- /spa/src/app/home/home.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/app/home/home.component.css -------------------------------------------------------------------------------- /spa/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Air Traffic Control #SpringOne2021

4 |
5 | 16 |
17 | -------------------------------------------------------------------------------- /spa/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.css'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spa/src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { HomeRoutingModule } from './home-routing.module'; 5 | import { HomeComponent } from './home.component'; 6 | 7 | 8 | @NgModule({ 9 | declarations: [ 10 | HomeComponent 11 | ], 12 | imports: [ 13 | CommonModule, 14 | HomeRoutingModule 15 | ] 16 | }) 17 | export class HomeModule { } 18 | -------------------------------------------------------------------------------- /spa/src/app/interceptors/gateway.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | @Injectable() 6 | export class GatewayInterceptor implements HttpInterceptor { 7 | 8 | constructor() {} 9 | 10 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 11 | if (req.url.startsWith('/assets')) { 12 | return next.handle(req); 13 | } 14 | 15 | const newReq = req.clone({ 16 | url: 'http://127.0.0.1:8000' + req.url 17 | }); 18 | 19 | return next.handle(newReq); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spa/src/app/layout/layout-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { LayoutComponent } from './layout.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: LayoutComponent, 9 | children: [ 10 | { 11 | path: 'home', 12 | loadChildren: () => import('../home/home.module').then(m => m.HomeModule), 13 | }, 14 | { 15 | path: 'user-info', 16 | loadChildren: () => import('../user-info/user-info.module').then(m => m.UserInfoModule) 17 | }, 18 | { 19 | path: 'flights', 20 | loadChildren: () => import('../flights/flights.module').then(m => m.FlightsModule) 21 | }, 22 | { 23 | path: 'panel', 24 | loadChildren: () => import('../controller-panel/controller-panel.module').then(m => m.ControllerPanelModule) 25 | } 26 | ], 27 | }, 28 | ]; 29 | 30 | @NgModule({ 31 | imports: [RouterModule.forChild(routes)], 32 | exports: [RouterModule] 33 | }) 34 | export class LayoutRoutingModule { } 35 | -------------------------------------------------------------------------------- /spa/src/app/layout/layout.component.css: -------------------------------------------------------------------------------- 1 | header { 2 | border-bottom: 1px solid #e3e3e3; 3 | } 4 | -------------------------------------------------------------------------------- /spa/src/app/layout/layout.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 19 |
20 |
21 |
22 | 23 |
24 | 32 | -------------------------------------------------------------------------------- /spa/src/app/layout/layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-layout', 5 | templateUrl: './layout.component.html', 6 | styleUrls: ['./layout.component.css'] 7 | }) 8 | export class LayoutComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /spa/src/app/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { LayoutRoutingModule } from './layout-routing.module'; 5 | import { LayoutComponent } from './layout.component'; 6 | 7 | 8 | @NgModule({ 9 | declarations: [ 10 | LayoutComponent 11 | ], 12 | imports: [ 13 | CommonModule, 14 | LayoutRoutingModule 15 | ] 16 | }) 17 | export class LayoutModule { } 18 | -------------------------------------------------------------------------------- /spa/src/app/services/flights.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | export interface Flight { 6 | pilotId: string; 7 | flightNumber: number; 8 | status: string; 9 | } 10 | 11 | @Injectable({providedIn: 'root'}) 12 | export class FlightsService { 13 | 14 | constructor(private http: HttpClient) { } 15 | 16 | getFlights(): Observable { 17 | return this.http.get('/flights'); 18 | } 19 | 20 | updateFlight(flightNumber: number, status: string): Observable { 21 | return this.http.put(`/flights/${flightNumber}/${status}`, {}); 22 | } 23 | 24 | getAllFlights(): Observable { 25 | return this.http.get('/flights/all'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spa/src/app/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | @Injectable({providedIn: 'root'}) 6 | export class UserService { 7 | constructor(private http: HttpClient) { } 8 | 9 | getUserInfo(): Observable { 10 | return this.http.get('/user/info'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spa/src/app/shared/flight-status.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'flightStatus' 5 | }) 6 | 7 | export class FlightStatusPipe implements PipeTransform { 8 | transform(value: string): string { 9 | switch(value.toLowerCase()) { 10 | case 'board': return 'Boarding' 11 | case 'taxi': return 'On Runway' 12 | case 'take_off': return 'Taking Off' 13 | case 'take-off': return 'Taking Off' 14 | default: return 'None' 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spa/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FlightStatusPipe } from './flight-status.pipe'; 3 | 4 | @NgModule({ 5 | imports: [], 6 | exports: [ 7 | FlightStatusPipe, 8 | ], 9 | declarations: [ 10 | FlightStatusPipe, 11 | ], 12 | providers: [], 13 | }) 14 | export class SharedModule { } 15 | -------------------------------------------------------------------------------- /spa/src/app/user-info/user-info-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { UserInfoComponent } from './user-info.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: UserInfoComponent 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class UserInfoRoutingModule { } 17 | -------------------------------------------------------------------------------- /spa/src/app/user-info/user-info.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/app/user-info/user-info.component.css -------------------------------------------------------------------------------- /spa/src/app/user-info/user-info.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

User Info

4 |
5 |
6 |
7 |
Greetings, {{ user.name }}.
8 |
You are logged in via the OAuth 2.0 Client {{ user.authorizedClientRegistrationId }}
9 |
10 |
11 | Attributes: 12 |
{{ user.principal.attributes | json }}
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /spa/src/app/user-info/user-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { UserService } from '../services/user.service'; 5 | 6 | @Component({ 7 | selector: 'app-user-info', 8 | templateUrl: './user-info.component.html', 9 | styleUrls: ['./user-info.component.css'] 10 | }) 11 | export class UserInfoComponent implements OnInit { 12 | 13 | user$: Observable; 14 | 15 | constructor( 16 | private userService: UserService, 17 | ) { } 18 | 19 | ngOnInit(): void { 20 | this.user$ = this.userService.getUserInfo(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /spa/src/app/user-info/user-info.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { UserInfoRoutingModule } from './user-info-routing.module'; 5 | import { UserInfoComponent } from './user-info.component'; 6 | 7 | 8 | @NgModule({ 9 | declarations: [ 10 | UserInfoComponent 11 | ], 12 | imports: [ 13 | CommonModule, 14 | UserInfoRoutingModule 15 | ] 16 | }) 17 | export class UserInfoModule { } 18 | -------------------------------------------------------------------------------- /spa/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/assets/.gitkeep -------------------------------------------------------------------------------- /spa/src/assets/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/assets/img/github.png -------------------------------------------------------------------------------- /spa/src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/assets/img/logo.png -------------------------------------------------------------------------------- /spa/src/assets/img/spring-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/assets/img/spring-logo.png -------------------------------------------------------------------------------- /spa/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /spa/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /spa/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/spa/src/favicon.ico -------------------------------------------------------------------------------- /spa/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spring One 2021 - From Taxi to Take-off 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /spa/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /spa/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /*************************************************************************************************** 2 | * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. 3 | */ 4 | import '@angular/localize/init'; 5 | /** 6 | * This file includes polyfills needed by Angular and is loaded before the app. 7 | * You can add your own extra polyfills to this file. 8 | * 9 | * This file is divided into 2 sections: 10 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 11 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 12 | * file. 13 | * 14 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 15 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 16 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 17 | * 18 | * Learn more in https://angular.io/guide/browser-support 19 | */ 20 | 21 | /*************************************************************************************************** 22 | * BROWSER POLYFILLS 23 | */ 24 | 25 | /** 26 | * IE11 requires the following for NgClass support on SVG elements 27 | */ 28 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 29 | 30 | /** 31 | * Web Animations `@angular/platform-browser/animations` 32 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 33 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 34 | */ 35 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 36 | 37 | /** 38 | * By default, zone.js will patch all possible macroTask and DomEvents 39 | * user can disable parts of macroTask/DomEvents patch by setting following flags 40 | * because those flags need to be set before `zone.js` being loaded, and webpack 41 | * will put import in the top of bundle, so user need to create a separate file 42 | * in this directory (for example: zone-flags.ts), and put the following flags 43 | * into that file, and then add the following code before importing zone.js. 44 | * import './zone-flags'; 45 | * 46 | * The flags allowed in zone-flags.ts are listed here. 47 | * 48 | * The following flags will work for all browsers. 49 | * 50 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 51 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 52 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 53 | * 54 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 55 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 56 | * 57 | * (window as any).__Zone_enable_cross_context_check = true; 58 | * 59 | */ 60 | 61 | /*************************************************************************************************** 62 | * Zone JS is required by default for Angular itself. 63 | */ 64 | import 'zone.js'; // Included with Angular CLI. 65 | 66 | 67 | /*************************************************************************************************** 68 | * APPLICATION IMPORTS 69 | */ 70 | -------------------------------------------------------------------------------- /spa/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;0,800;1,300;1,400;1,600;1,700;1,800&display=swap'); 3 | -------------------------------------------------------------------------------- /spa/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | { teardown: { destroyAfterEach: true }}, 22 | ); 23 | 24 | // Then we find all the tests. 25 | const context = require.context('./', true, /\.spec\.ts$/); 26 | // And load the modules. 27 | context.keys().map(context); 28 | -------------------------------------------------------------------------------- /spa/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /spa/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "strictPropertyInitialization": false, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "sourceMap": true, 13 | "declaration": false, 14 | "downlevelIteration": true, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "importHelpers": true, 18 | "target": "es2017", 19 | "module": "es2020", 20 | "lib": [ 21 | "es2018", 22 | "dom" 23 | ] 24 | }, 25 | "angularCompilerOptions": { 26 | "enableI18nLegacyMessageIdFormat": false, 27 | "strictInjectionParameters": true, 28 | "strictInputAccessModifiers": true, 29 | "strictTemplates": true, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spa/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /sso/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /sso/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.6.3' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | } 6 | 7 | group = 'com.example' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '11' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation 'org.springframework.security:spring-security-oauth2-authorization-server:0.2.2' 17 | implementation 'org.springframework.boot:spring-boot-starter-web' 18 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 19 | testImplementation 'org.springframework.security:spring-security-test' 20 | } 21 | 22 | test { 23 | useJUnitPlatform() 24 | } 25 | -------------------------------------------------------------------------------- /sso/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjohnr/springone-2021/d9dd30cdc20ca0a6940a2800ad58cdf9d0e7b278/sso/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /sso/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /sso/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /sso/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /sso/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'authorization-server' 2 | -------------------------------------------------------------------------------- /sso/src/main/java/com/example/sso/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.sso; 2 | 3 | import java.security.KeyPair; 4 | import java.security.KeyPairGenerator; 5 | import java.security.interfaces.RSAPrivateKey; 6 | import java.security.interfaces.RSAPublicKey; 7 | import java.util.UUID; 8 | 9 | import com.nimbusds.jose.jwk.JWKSet; 10 | import com.nimbusds.jose.jwk.RSAKey; 11 | import com.nimbusds.jose.jwk.source.ImmutableJWKSet; 12 | import com.nimbusds.jose.jwk.source.JWKSource; 13 | import com.nimbusds.jose.proc.SecurityContext; 14 | 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.context.annotation.Role; 18 | import org.springframework.core.annotation.Order; 19 | import org.springframework.security.config.Customizer; 20 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 21 | import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; 22 | import org.springframework.security.core.userdetails.User; 23 | import org.springframework.security.core.userdetails.UserDetails; 24 | import org.springframework.security.core.userdetails.UserDetailsService; 25 | import org.springframework.security.oauth2.core.AuthorizationGrantType; 26 | import org.springframework.security.oauth2.core.ClientAuthenticationMethod; 27 | import org.springframework.security.oauth2.core.oidc.OidcScopes; 28 | import org.springframework.security.oauth2.jwt.JwtDecoder; 29 | import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; 30 | import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; 31 | import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; 32 | import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; 33 | import org.springframework.security.oauth2.server.authorization.config.ClientSettings; 34 | import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; 35 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 36 | import org.springframework.security.web.SecurityFilterChain; 37 | 38 | import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE; 39 | 40 | @Configuration 41 | public class SecurityConfiguration { 42 | 43 | @Bean 44 | @Order(1) 45 | public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { 46 | OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); 47 | return http.formLogin(Customizer.withDefaults()).build(); 48 | } 49 | 50 | @Bean 51 | @Order(2) 52 | public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception { 53 | // @formatter:off 54 | http 55 | .authorizeRequests((authorize) -> authorize.anyRequest().authenticated()) 56 | .formLogin(Customizer.withDefaults()); 57 | // @formatter:on 58 | 59 | return http.build(); 60 | } 61 | 62 | @Bean 63 | public RegisteredClientRepository registeredClientRepository() { 64 | // @formatter:off 65 | RegisteredClient webClient = RegisteredClient.withId(UUID.randomUUID().toString()) 66 | .clientId("air-traffic-control") 67 | .clientSecret("{noop}secret") 68 | .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) 69 | .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) 70 | .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) 71 | .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) 72 | .redirectUri("http://127.0.0.1:8000/login/oauth2/code/air-traffic-control-client") 73 | .scope(OidcScopes.OPENID) 74 | .scope("flights:read") 75 | .scope("flights:write") 76 | .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) 77 | .build(); 78 | RegisteredClient apiClient = RegisteredClient.withId(UUID.randomUUID().toString()) 79 | .clientId("josh") 80 | .clientSecret("{noop}control") 81 | .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) 82 | .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) 83 | .scope("flights:read") 84 | .scope("flights:write") 85 | .build(); 86 | // @formatter:on 87 | 88 | return new InMemoryRegisteredClientRepository(webClient, apiClient); 89 | } 90 | 91 | @Bean 92 | public JWKSource jwkSource(KeyPair keyPair) { 93 | RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); 94 | RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); 95 | // @formatter:off 96 | RSAKey rsaKey = new RSAKey.Builder(publicKey) 97 | .privateKey(privateKey) 98 | .keyID(UUID.randomUUID().toString()) 99 | .build(); 100 | JWKSet jwkSet = new JWKSet(rsaKey); 101 | return new ImmutableJWKSet<>(jwkSet); 102 | } 103 | 104 | @Bean 105 | public JwtDecoder jwtDecoder(KeyPair keyPair) { 106 | return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build(); 107 | } 108 | 109 | @Bean 110 | public ProviderSettings providerSettings() { 111 | return ProviderSettings.builder().issuer("http://auth-server:9000").build(); 112 | } 113 | 114 | @Bean 115 | public UserDetailsService userDetailsService() { 116 | UserDetails josh = User.withDefaultPasswordEncoder() 117 | .username("josh") 118 | .password("control") 119 | .roles("USER") 120 | .build(); 121 | UserDetails marcus = User.withDefaultPasswordEncoder() 122 | .username("marcus") 123 | .password("password") 124 | .roles("USER") 125 | .build(); 126 | UserDetails steve = User.withDefaultPasswordEncoder() 127 | .username("steve") 128 | .password("password") 129 | .roles("USER") 130 | .build(); 131 | return new InMemoryUserDetailsManager(josh, marcus, steve); 132 | } 133 | 134 | @Bean 135 | @Role(ROLE_INFRASTRUCTURE) 136 | KeyPair generateRsaKey() { 137 | KeyPair keyPair; 138 | try { 139 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); 140 | keyPairGenerator.initialize(2048); 141 | keyPair = keyPairGenerator.generateKeyPair(); 142 | } 143 | catch (Exception ex) { 144 | throw new IllegalStateException(ex); 145 | } 146 | return keyPair; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /sso/src/main/java/com/example/sso/SsoApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.sso; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SsoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SsoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /sso/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9000 3 | 4 | logging: 5 | level: 6 | org.springframework.security: trace 7 | -------------------------------------------------------------------------------- /sso/src/test/java/com/example/sso/SsoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.sso; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class SsoApplicationTests { 9 | 10 | @Test 11 | void contextLoads() { 12 | } 13 | 14 | } 15 | --------------------------------------------------------------------------------