From 11925d99dd5733d706751694fecc922793de47f4 Mon Sep 17 00:00:00 2001 From: Patrick Kubiak Date: Tue, 5 Nov 2024 22:06:07 -0500 Subject: [PATCH] WebFlux submodule (#4) * prep for webflux module * Setup webflux * WebFlux update logout url to port 3001 * separate job for webflux * update README * github actions separate mvc and webflux subprojects * Refactor mvc vs webflux packages * Make public for unit test * Update WebFlux Theme Templates --- .github/workflows/build.yml | 49 ++- .gitignore | 2 +- .idea/gradle.xml | 2 + README.md | 37 +- build.gradle | 51 --- exec.ps1 | 2 - exec.sh | 3 - Dockerfile => mvc-login/Dockerfile | 0 mvc-login/README.md | 73 ++++ mvc-login/build.gradle | 59 ++++ mvc-login/exec.ps1 | 2 + mvc-login/exec.sh | 3 + .../org/example/mvc}/DemoApplication.java | 2 +- .../main/java/org/example/mvc}/Generated.java | 2 +- .../java/org/example/mvc}/HomeController.java | 2 +- .../org/example/mvc}/ProfileController.java | 2 +- .../java/org/example/mvc}/SecurityConfig.java | 4 +- .../main/resources/application.yml.example | 0 .../main/resources/public/images/logo.png | Bin .../main/resources/static/css/auth0-theme.css | 0 .../resources/templates/fragments/footer.html | 0 .../resources/templates/fragments/header.html | 0 .../resources/templates/fragments/navbar.html | 10 +- .../templates/fragments/scripts.html | 0 .../src}/main/resources/templates/index.html | 0 .../resources/templates/layouts/default.html | 0 .../main/resources/templates/profile.html | 0 .../org/example/mvc}/DemoApplicationTest.java | 2 +- .../org/example/mvc}/HomeControllerTest.java | 2 +- .../example/mvc}/ProfileControllerTest.java | 2 +- .../java/org/example/mvc}/SecurityConfig.java | 2 +- settings.gradle | 2 + webflux-login/Dockerfile | 9 + webflux-login/README.md | 73 ++++ webflux-login/build.gradle | 59 ++++ webflux-login/exec.ps1 | 2 + webflux-login/exec.sh | 3 + .../org/example/webflux/DemoApplication.java | 13 + .../org/example/webflux/HomeController.java | 22 ++ .../example/webflux/ProfileController.java | 41 +++ .../org/example/webflux/SecurityConfig.java | 62 ++++ .../main/resources/application.yml.example | 19 + .../main/resources/public/css/auth0-theme.css | 329 ++++++++++++++++++ .../src/main/resources/public/images/logo.png | Bin 0 -> 5682 bytes .../resources/templates/fragments/footer.html | 13 + .../resources/templates/fragments/header.html | 15 + .../resources/templates/fragments/navbar.html | 93 +++++ .../templates/fragments/scripts.html | 16 + .../src/main/resources/templates/index.html | 68 ++++ .../resources/templates/layouts/default.html | 14 + .../src/main/resources/templates/profile.html | 28 ++ .../example/webflux/DemoApplicationTest.java | 13 + .../example/webflux/HomeControllerTest.java | 51 +++ .../webflux/ProfileControllerTest.java | 74 ++++ .../org/example/webflux/SecurityConfig.java | 8 + 55 files changed, 1242 insertions(+), 98 deletions(-) delete mode 100755 exec.ps1 delete mode 100755 exec.sh rename Dockerfile => mvc-login/Dockerfile (100%) create mode 100644 mvc-login/README.md create mode 100644 mvc-login/build.gradle create mode 100755 mvc-login/exec.ps1 create mode 100755 mvc-login/exec.sh rename {src/main/java/org/example => mvc-login/src/main/java/org/example/mvc}/DemoApplication.java (92%) rename {src/main/java/org/example => mvc-login/src/main/java/org/example/mvc}/Generated.java (92%) rename {src/main/java/org/example => mvc-login/src/main/java/org/example/mvc}/HomeController.java (96%) rename {src/main/java/org/example => mvc-login/src/main/java/org/example/mvc}/ProfileController.java (98%) rename {src/main/java/org/example => mvc-login/src/main/java/org/example/mvc}/SecurityConfig.java (87%) rename {src => mvc-login/src}/main/resources/application.yml.example (100%) rename {src => mvc-login/src}/main/resources/public/images/logo.png (100%) rename {src => mvc-login/src}/main/resources/static/css/auth0-theme.css (100%) rename {src => mvc-login/src}/main/resources/templates/fragments/footer.html (100%) rename {src => mvc-login/src}/main/resources/templates/fragments/header.html (100%) rename {src => mvc-login/src}/main/resources/templates/fragments/navbar.html (95%) rename {src => mvc-login/src}/main/resources/templates/fragments/scripts.html (100%) rename {src => mvc-login/src}/main/resources/templates/index.html (100%) rename {src => mvc-login/src}/main/resources/templates/layouts/default.html (100%) rename {src => mvc-login/src}/main/resources/templates/profile.html (100%) rename {src/test/java/org/example => mvc-login/src/test/java/org/example/mvc}/DemoApplicationTest.java (87%) rename {src/test/java/org/example => mvc-login/src/test/java/org/example/mvc}/HomeControllerTest.java (98%) rename {src/test/java/org/example => mvc-login/src/test/java/org/example/mvc}/ProfileControllerTest.java (99%) rename {src/test/java/org/example => mvc-login/src/test/java/org/example/mvc}/SecurityConfig.java (87%) create mode 100644 webflux-login/Dockerfile create mode 100644 webflux-login/README.md create mode 100644 webflux-login/build.gradle create mode 100755 webflux-login/exec.ps1 create mode 100755 webflux-login/exec.sh create mode 100644 webflux-login/src/main/java/org/example/webflux/DemoApplication.java create mode 100644 webflux-login/src/main/java/org/example/webflux/HomeController.java create mode 100644 webflux-login/src/main/java/org/example/webflux/ProfileController.java create mode 100644 webflux-login/src/main/java/org/example/webflux/SecurityConfig.java create mode 100644 webflux-login/src/main/resources/application.yml.example create mode 100644 webflux-login/src/main/resources/public/css/auth0-theme.css create mode 100644 webflux-login/src/main/resources/public/images/logo.png create mode 100644 webflux-login/src/main/resources/templates/fragments/footer.html create mode 100644 webflux-login/src/main/resources/templates/fragments/header.html create mode 100644 webflux-login/src/main/resources/templates/fragments/navbar.html create mode 100644 webflux-login/src/main/resources/templates/fragments/scripts.html create mode 100644 webflux-login/src/main/resources/templates/index.html create mode 100644 webflux-login/src/main/resources/templates/layouts/default.html create mode 100644 webflux-login/src/main/resources/templates/profile.html create mode 100644 webflux-login/src/test/java/org/example/webflux/DemoApplicationTest.java create mode 100644 webflux-login/src/test/java/org/example/webflux/HomeControllerTest.java create mode 100644 webflux-login/src/test/java/org/example/webflux/ProfileControllerTest.java create mode 100644 webflux-login/src/test/java/org/example/webflux/SecurityConfig.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 402adeb..630a952 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Build on: [push] jobs: - build: + mvc-login: runs-on: ubuntu-latest steps: - name: Checkout sources @@ -15,15 +15,15 @@ jobs: java-version: 21 - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - name: Build with Gradle - run: ./gradlew build - - name: Unit Test with Gradle - run: ./gradlew test + - name: Build MVC Login with Gradle + run: ./gradlew :mvc-login:build + - name: Unit Test MVC Login with Gradle + run: ./gradlew :mvc-login:test - name: Generate JaCoCo badge id: jacoco uses: cicirello/jacoco-badge-generator@v2 with: - jacoco-csv-file: build/reports/jacoco/test/jacocoTestReport.csv + jacoco-csv-file: mvc-login/build/reports/jacoco/test/jacocoTestReport.csv badges-directory: badges generate-branches-badge: true generate-summary: true @@ -34,5 +34,38 @@ jobs: - name: Upload JaCoCo coverage report as a workflow artifact uses: actions/upload-artifact@v4 with: - name: jacoco-report - path: build/reports/jacoco/test/ \ No newline at end of file + name: jacoco-report-mvc-login + path: mvc-login/build/reports/jacoco/test/ + webflux-login: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Build WebFlux Login with Gradle + run: ./gradlew :webflux-login:build + - name: Unit Test WebFlux Login with Gradle + run: ./gradlew :webflux-login:test + - name: Generate JaCoCo badge + id: jacoco + uses: cicirello/jacoco-badge-generator@v2 + with: + jacoco-csv-file: webflux-login/build/reports/jacoco/test/jacocoTestReport.csv + badges-directory: badges + generate-branches-badge: true + generate-summary: true + - name: Log coverage percentages to workflow output + run: | + echo "coverage = ${{ steps.jacoco.outputs.coverage }}" + echo "branches = ${{ steps.jacoco.outputs.branches }}" + - name: Upload JaCoCo coverage report as a workflow artifact + uses: actions/upload-artifact@v4 + with: + name: jacoco-report-webflux-login + path: webflux-login/build/reports/jacoco/test/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 346b236..d330843 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,4 @@ bin/ ### Custom ### # Spring Boot config with secrets -src/main/resources/application.yml +**/src/main/resources/application.yml diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2a65317..26a8daf 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -9,6 +9,8 @@ diff --git a/README.md b/README.md index 7d967be..cb904a7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Description -This is a fork of Okta's [Spring Boot Login - MVC](https://github.com/auth0-samples/auth0-spring-boot-login-samples/tree/master/mvc-login), without Okta Spring Boot Starter. +This is a fork of Okta's [Spring Boot Login Samples](https://github.com/auth0-samples/auth0-spring-boot-login-samples), without Okta Spring Boot Starter. ## Requirements @@ -12,19 +12,22 @@ This is a fork of Okta's [Spring Boot Login - MVC](https://github.com/auth0-samp ### Auth0 Dashboard 1. On the [Auth0 Dashboard](https://manage.auth0.com/#/clients) create a new Application of type **Regular Web Application**. -1. On the **Settings** tab of your application, add the URL `http://localhost:3000/login/oauth2/code/okta` to the **Allowed Callback URLs** field. -1. On the **Settings** tab of your application, add the URL `http://localhost:3000/` to the **Allowed Logout URLs** field. +1. On the **Settings** tab of your application, add the following URLs to the **Allowed Callback URLs** field. + - `http://localhost:3000/login/oauth2/code/okta, http://localhost:3001/login/oauth2/code/okta` +1. On the **Settings** tab of your application, add the following URLs to the **Allowed Logout URLs** field. + - `http://localhost:3000, http://localhost:3001` 1. Save the changes to your application settings. Don't close this page; you'll need some of the settings when configuring the application below. ### Application configuration -Copy `src/main/resources/application.yml.example` to `src/main/resources/application.yml`: +Create application.yml by copying example config: ```bash -cp src/main/resources/application.yml.example src/main/resources/application.yml +cp mvc-login/src/main/resources/application.yml.example mvc-login/src/main/resources/application.yml +cp webflux-login/src/main/resources/application.yml.example webflux-login/src/main/resources/application.yml ``` -Set the application values in the `src/main/resources/application.yml` file to the values of your Auth0 application. +Set the application values in the `src/main/resources/application.yml` file to the values of your Auth0 application for both mvc-login and webflux-login subprojects. ```yaml issuer-uri: https://{YOUR-DOMAIN}/ @@ -32,47 +35,43 @@ client-id: {YOUR-CLIENT-ID} client-secret: {YOUR-CLIENT-SECRET} ``` -## Running the sample +## Running the MVC sample Open a terminal, go to the project root directory and run the following command: Linux or MacOS: ```bash -./gradlew bootRun +./gradlew :mvc-login:bootRun ``` Windows: ```bash -gradlew.bat bootRun +gradlew.bat :mvc-login:bootRun ``` The application will be accessible at http://localhost:3000. -### Running the sample with podman +## Running the WebFlux sample -In order to run the example with [Podman](https://podman.io/docs/installation) you need to have `podman` installed. - -You also need to set the client values as explained [previously](#application-configuration). - -Execute the command to run Podman for your environment: +Open a terminal, go to the project root directory and run the following command: Linux or MacOS: ```bash -sh exec.sh +./gradlew :webflux-login:bootRun ``` Windows: ```bash -.\exec.ps1 +gradlew.bat :webflux-login:bootRun ``` -The application will be accessible at http://localhost:3000. +The application will be accessible at http://localhost:3001. -## Upgrading the sample +## Upgrading the samples Use [OpenRewrite](https://docs.openrewrite.org/) to upgrade to latest Java and SpringBoot. diff --git a/build.gradle b/build.gradle index e66b8b1..45ddf95 100644 --- a/build.gradle +++ b/build.gradle @@ -1,67 +1,16 @@ plugins { - id 'java' - id 'jacoco' - - // https://plugins.gradle.org/plugin/org.springframework.boot - id 'org.springframework.boot' version '3.3.5' - // https://plugins.gradle.org/plugin/io.spring.dependency-management - id 'io.spring.dependency-management' version '1.1.6' - // Use OpenRewrite to upgrade Java / SpringBoot version: https://docs.openrewrite.org/ // https://plugins.gradle.org/plugin/org.openrewrite.rewrite id 'org.openrewrite.rewrite' version '6.26.0' } -ext { - springBootVersion = '3.3.5' -} - group = 'org.example' version = '1.0-SNAPSHOT' -java { - sourceCompatibility = '21' -} - repositories { mavenCentral() } -dependencies { - // - // SpringBoot - // - // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web - implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: "${springBootVersion}" - // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-client - implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-client', version: "${springBootVersion}" - // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test - implementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: "${springBootVersion}" - - // - // Thymeleaf - // - // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf - implementation group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: "${springBootVersion}" - // https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity6 - implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity6', version: '3.1.2.RELEASE' - // https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect - implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '3.3.0' -} - -test { - useJUnitPlatform() - // report is always generated after tests run - finalizedBy jacocoTestReport -} -jacocoTestReport { - reports { - xml.required = true - csv.required = true - html.required = true - } -} - // // Use OpenRewrite to upgrade Java / SpringBoot version: https://docs.openrewrite.org/ // diff --git a/exec.ps1 b/exec.ps1 deleted file mode 100755 index 4a41c2d..0000000 --- a/exec.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -podman build -t sample-oauth2-spring-boot . -podman run -p 3000:3000 -it sample-oauth2-spring-boot \ No newline at end of file diff --git a/exec.sh b/exec.sh deleted file mode 100755 index c2a300f..0000000 --- a/exec.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -podman build -t sample-oauth2-spring-boot . -podman run -p 3000:3000 -it sample-oauth2-spring-boot \ No newline at end of file diff --git a/Dockerfile b/mvc-login/Dockerfile similarity index 100% rename from Dockerfile rename to mvc-login/Dockerfile diff --git a/mvc-login/README.md b/mvc-login/README.md new file mode 100644 index 0000000..6b2f2b5 --- /dev/null +++ b/mvc-login/README.md @@ -0,0 +1,73 @@ +# sample-oauth2-spring-boot - MVC + +## Description + +This is a fork of Okta's [Spring Boot Login - MVC](https://github.com/auth0-samples/auth0-spring-boot-login-samples/tree/master/mvc-login), without Okta Spring Boot Starter. + +## Requirements + +- Java 21 + +## Configuration + +### Auth0 Dashboard +1. On the [Auth0 Dashboard](https://manage.auth0.com/#/clients) create a new Application of type **Regular Web Application**. +1. On the **Settings** tab of your application, add the URL `http://localhost:3000/login/oauth2/code/okta` to the **Allowed Callback URLs** field. +1. On the **Settings** tab of your application, add the URL `http://localhost:3000/` to the **Allowed Logout URLs** field. +1. Save the changes to your application settings. Don't close this page; you'll need some of the settings when configuring the application below. + +### Application configuration + +Create application.yml by copying example config: + +```bash +cp src/main/resources/application.yml.example src/main/resources/application.yml +``` + +Set the application values in the `src/main/resources/application.yml` file to the values of your Auth0 application. + +```yaml +issuer-uri: https://{YOUR-DOMAIN}/ +client-id: {YOUR-CLIENT-ID} +client-secret: {YOUR-CLIENT-SECRET} +``` + +## Running the Spring MVC Sample + +Open a terminal, go to the project root directory and run the following command: + +Linux or MacOS: + +```bash +./gradlew bootRun +``` + +Windows: + +```bash +gradlew.bat bootRun +``` + +The application will be accessible at http://localhost:3000. + +### Running the sample with podman + +In order to run the example with [Podman](https://podman.io/docs/installation) you need to have `podman` installed. + +You also need to set the client values as explained [previously](#application-configuration). + +Execute the command to run Podman for your environment: + +Linux or MacOS: + +```bash +sh exec.sh +``` + +Windows: + +```bash +.\exec.ps1 +``` + +The application will be accessible at http://localhost:3000. diff --git a/mvc-login/build.gradle b/mvc-login/build.gradle new file mode 100644 index 0000000..8c01bf1 --- /dev/null +++ b/mvc-login/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'java' + id 'jacoco' + + // https://plugins.gradle.org/plugin/org.springframework.boot + id 'org.springframework.boot' version '3.3.5' + // https://plugins.gradle.org/plugin/io.spring.dependency-management + id 'io.spring.dependency-management' version '1.1.6' +} + +ext { + springBootVersion = '3.3.5' +} + +group = 'org.example' +version = '1.0-SNAPSHOT' + +java { + sourceCompatibility = '21' +} + +repositories { + mavenCentral() +} + +dependencies { + // + // SpringBoot + // + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: "${springBootVersion}" + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-client + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-client', version: "${springBootVersion}" + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: "${springBootVersion}" + + // + // Thymeleaf + // + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: "${springBootVersion}" + // https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity6 + implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity6', version: '3.1.2.RELEASE' + // https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect + implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '3.3.0' +} + +test { + useJUnitPlatform() + // report is always generated after tests run + finalizedBy jacocoTestReport +} +jacocoTestReport { + reports { + xml.required = true + csv.required = true + html.required = true + } +} diff --git a/mvc-login/exec.ps1 b/mvc-login/exec.ps1 new file mode 100755 index 0000000..b908e97 --- /dev/null +++ b/mvc-login/exec.ps1 @@ -0,0 +1,2 @@ +podman build -t sample-oauth2-spring-boot-mvc . +podman run -p 3000:3000 -it sample-oauth2-spring-boot-mvc \ No newline at end of file diff --git a/mvc-login/exec.sh b/mvc-login/exec.sh new file mode 100755 index 0000000..325bb37 --- /dev/null +++ b/mvc-login/exec.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +podman build -t sample-oauth2-spring-boot-mvc . +podman run -p 3000:3000 -it sample-oauth2-spring-boot-mvc \ No newline at end of file diff --git a/src/main/java/org/example/DemoApplication.java b/mvc-login/src/main/java/org/example/mvc/DemoApplication.java similarity index 92% rename from src/main/java/org/example/DemoApplication.java rename to mvc-login/src/main/java/org/example/mvc/DemoApplication.java index 21ebdd8..693508f 100644 --- a/src/main/java/org/example/DemoApplication.java +++ b/mvc-login/src/main/java/org/example/mvc/DemoApplication.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/org/example/Generated.java b/mvc-login/src/main/java/org/example/mvc/Generated.java similarity index 92% rename from src/main/java/org/example/Generated.java rename to mvc-login/src/main/java/org/example/mvc/Generated.java index 3ca4c42..bd46957 100644 --- a/src/main/java/org/example/Generated.java +++ b/mvc-login/src/main/java/org/example/mvc/Generated.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/org/example/HomeController.java b/mvc-login/src/main/java/org/example/mvc/HomeController.java similarity index 96% rename from src/main/java/org/example/HomeController.java rename to mvc-login/src/main/java/org/example/mvc/HomeController.java index 26d5592..525abbe 100644 --- a/src/main/java/org/example/HomeController.java +++ b/mvc-login/src/main/java/org/example/mvc/HomeController.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.oauth2.core.oidc.user.OidcUser; diff --git a/src/main/java/org/example/ProfileController.java b/mvc-login/src/main/java/org/example/mvc/ProfileController.java similarity index 98% rename from src/main/java/org/example/ProfileController.java rename to mvc-login/src/main/java/org/example/mvc/ProfileController.java index 2356c25..eabefa0 100644 --- a/src/main/java/org/example/ProfileController.java +++ b/mvc-login/src/main/java/org/example/mvc/ProfileController.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/org/example/SecurityConfig.java b/mvc-login/src/main/java/org/example/mvc/SecurityConfig.java similarity index 87% rename from src/main/java/org/example/SecurityConfig.java rename to mvc-login/src/main/java/org/example/mvc/SecurityConfig.java index 6ae4e81..491578f 100644 --- a/src/main/java/org/example/SecurityConfig.java +++ b/mvc-login/src/main/java/org/example/mvc/SecurityConfig.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -38,6 +38,8 @@ private LogoutHandler logoutHandler() { return (request, response, authentication) -> { try { String baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString(); + // The URL to log the user out of Auth0 and redirect them to the home page. + // URL will look like https://YOUR-DOMAIN/v2/logout?clientId=YOUR-CLIENT-ID&returnTo=http://localhost:3000 response.sendRedirect(issuer + "v2/logout?client_id=" + clientId + "&returnTo=" + baseUrl); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/main/resources/application.yml.example b/mvc-login/src/main/resources/application.yml.example similarity index 100% rename from src/main/resources/application.yml.example rename to mvc-login/src/main/resources/application.yml.example diff --git a/src/main/resources/public/images/logo.png b/mvc-login/src/main/resources/public/images/logo.png similarity index 100% rename from src/main/resources/public/images/logo.png rename to mvc-login/src/main/resources/public/images/logo.png diff --git a/src/main/resources/static/css/auth0-theme.css b/mvc-login/src/main/resources/static/css/auth0-theme.css similarity index 100% rename from src/main/resources/static/css/auth0-theme.css rename to mvc-login/src/main/resources/static/css/auth0-theme.css diff --git a/src/main/resources/templates/fragments/footer.html b/mvc-login/src/main/resources/templates/fragments/footer.html similarity index 100% rename from src/main/resources/templates/fragments/footer.html rename to mvc-login/src/main/resources/templates/fragments/footer.html diff --git a/src/main/resources/templates/fragments/header.html b/mvc-login/src/main/resources/templates/fragments/header.html similarity index 100% rename from src/main/resources/templates/fragments/header.html rename to mvc-login/src/main/resources/templates/fragments/header.html diff --git a/src/main/resources/templates/fragments/navbar.html b/mvc-login/src/main/resources/templates/fragments/navbar.html similarity index 95% rename from src/main/resources/templates/fragments/navbar.html rename to mvc-login/src/main/resources/templates/fragments/navbar.html index cf88b62..fd87bd0 100644 --- a/src/main/resources/templates/fragments/navbar.html +++ b/mvc-login/src/main/resources/templates/fragments/navbar.html @@ -51,11 +51,11 @@ - Profile + Profile
@@ -75,19 +75,19 @@
  • - + Profile
  • diff --git a/src/main/resources/templates/fragments/scripts.html b/mvc-login/src/main/resources/templates/fragments/scripts.html similarity index 100% rename from src/main/resources/templates/fragments/scripts.html rename to mvc-login/src/main/resources/templates/fragments/scripts.html diff --git a/src/main/resources/templates/index.html b/mvc-login/src/main/resources/templates/index.html similarity index 100% rename from src/main/resources/templates/index.html rename to mvc-login/src/main/resources/templates/index.html diff --git a/src/main/resources/templates/layouts/default.html b/mvc-login/src/main/resources/templates/layouts/default.html similarity index 100% rename from src/main/resources/templates/layouts/default.html rename to mvc-login/src/main/resources/templates/layouts/default.html diff --git a/src/main/resources/templates/profile.html b/mvc-login/src/main/resources/templates/profile.html similarity index 100% rename from src/main/resources/templates/profile.html rename to mvc-login/src/main/resources/templates/profile.html diff --git a/src/test/java/org/example/DemoApplicationTest.java b/mvc-login/src/test/java/org/example/mvc/DemoApplicationTest.java similarity index 87% rename from src/test/java/org/example/DemoApplicationTest.java rename to mvc-login/src/test/java/org/example/mvc/DemoApplicationTest.java index f46f74f..83d5d1f 100644 --- a/src/test/java/org/example/DemoApplicationTest.java +++ b/mvc-login/src/test/java/org/example/mvc/DemoApplicationTest.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/example/HomeControllerTest.java b/mvc-login/src/test/java/org/example/mvc/HomeControllerTest.java similarity index 98% rename from src/test/java/org/example/HomeControllerTest.java rename to mvc-login/src/test/java/org/example/mvc/HomeControllerTest.java index 83fab9a..3eab36b 100644 --- a/src/test/java/org/example/HomeControllerTest.java +++ b/mvc-login/src/test/java/org/example/mvc/HomeControllerTest.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/example/ProfileControllerTest.java b/mvc-login/src/test/java/org/example/mvc/ProfileControllerTest.java similarity index 99% rename from src/test/java/org/example/ProfileControllerTest.java rename to mvc-login/src/test/java/org/example/mvc/ProfileControllerTest.java index 8ed4a8d..4eb89dd 100644 --- a/src/test/java/org/example/ProfileControllerTest.java +++ b/mvc-login/src/test/java/org/example/mvc/ProfileControllerTest.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/test/java/org/example/SecurityConfig.java b/mvc-login/src/test/java/org/example/mvc/SecurityConfig.java similarity index 87% rename from src/test/java/org/example/SecurityConfig.java rename to mvc-login/src/test/java/org/example/mvc/SecurityConfig.java index 60eeb86..935f4e7 100644 --- a/src/test/java/org/example/SecurityConfig.java +++ b/mvc-login/src/test/java/org/example/mvc/SecurityConfig.java @@ -1,4 +1,4 @@ -package org.example; +package org.example.mvc; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; diff --git a/settings.gradle b/settings.gradle index f22b981..e157942 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ rootProject.name = 'sample-oauth2-spring-boot' +include 'mvc-login' +include 'webflux-login' diff --git a/webflux-login/Dockerfile b/webflux-login/Dockerfile new file mode 100644 index 0000000..ccf74a5 --- /dev/null +++ b/webflux-login/Dockerfile @@ -0,0 +1,9 @@ +FROM docker.io/gradle:8-jdk21-alpine + +WORKDIR /tmp +ADD . /tmp + +RUN gradle build + +CMD ["gradle", "clean", "bootRun"] +EXPOSE 3001 diff --git a/webflux-login/README.md b/webflux-login/README.md new file mode 100644 index 0000000..a1534b4 --- /dev/null +++ b/webflux-login/README.md @@ -0,0 +1,73 @@ +# sample-oauth2-spring-boot - WebFlux + +## Description + +This is a fork of Okta's [Spring Boot Login - MVC](https://github.com/auth0-samples/auth0-spring-boot-login-samples/tree/master/mvc-login), without Okta Spring Boot Starter. + +## Requirements + +- Java 21 + +## Configuration + +### Auth0 Dashboard +1. On the [Auth0 Dashboard](https://manage.auth0.com/#/clients) create a new Application of type **Regular Web Application**. +1. On the **Settings** tab of your application, add the URL `http://localhost:3001/login/oauth2/code/okta` to the **Allowed Callback URLs** field. +1. On the **Settings** tab of your application, add the URL `http://localhost:3001/` to the **Allowed Logout URLs** field. +1. Save the changes to your application settings. Don't close this page; you'll need some of the settings when configuring the application below. + +### Application configuration + +Create application.yml by copying example config: + +```bash +cp src/main/resources/application.yml.example src/main/resources/application.yml +``` + +Set the application values in the `src/main/resources/application.yml` file to the values of your Auth0 application. + +```yaml +issuer-uri: https://{YOUR-DOMAIN}/ +client-id: {YOUR-CLIENT-ID} +client-secret: {YOUR-CLIENT-SECRET} +``` + +## Running the Spring WebFlux Sample + +Open a terminal, go to the project root directory and run the following command: + +Linux or MacOS: + +```bash +./gradlew bootRun +``` + +Windows: + +```bash +gradlew.bat bootRun +``` + +The application will be accessible at http://localhost:3001. + +### Running the sample with podman + +In order to run the example with [Podman](https://podman.io/docs/installation) you need to have `podman` installed. + +You also need to set the client values as explained [previously](#application-configuration). + +Execute the command to run Podman for your environment: + +Linux or MacOS: + +```bash +sh exec.sh +``` + +Windows: + +```bash +.\exec.ps1 +``` + +The application will be accessible at http://localhost:3001. diff --git a/webflux-login/build.gradle b/webflux-login/build.gradle new file mode 100644 index 0000000..c84aa95 --- /dev/null +++ b/webflux-login/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'java' + id 'jacoco' + + // https://plugins.gradle.org/plugin/org.springframework.boot + id 'org.springframework.boot' version '3.3.5' + // https://plugins.gradle.org/plugin/io.spring.dependency-management + id 'io.spring.dependency-management' version '1.1.6' +} + +ext { + springBootVersion = '3.3.5' +} + +group = 'org.example' +version = '1.0-SNAPSHOT' + +java { + sourceCompatibility = '21' +} + +repositories { + mavenCentral() +} + +dependencies { + // + // SpringBoot + // + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: "${springBootVersion}" + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-oauth2-client + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-client', version: "${springBootVersion}" + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: "${springBootVersion}" + + // + // Thymeleaf + // + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-thymeleaf', version: "${springBootVersion}" + // https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity6 + implementation group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity6', version: '3.1.2.RELEASE' + // https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect + implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '3.3.0' +} + +test { + useJUnitPlatform() + // report is always generated after tests run + finalizedBy jacocoTestReport +} +jacocoTestReport { + reports { + xml.required = true + csv.required = true + html.required = true + } +} diff --git a/webflux-login/exec.ps1 b/webflux-login/exec.ps1 new file mode 100755 index 0000000..c56fe3d --- /dev/null +++ b/webflux-login/exec.ps1 @@ -0,0 +1,2 @@ +podman build -t sample-oauth2-spring-boot-webflux . +podman run -p 3001:3001 -it sample-oauth2-spring-boot-webflux \ No newline at end of file diff --git a/webflux-login/exec.sh b/webflux-login/exec.sh new file mode 100755 index 0000000..458e69c --- /dev/null +++ b/webflux-login/exec.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +podman build -t sample-oauth2-spring-boot-webflux . +podman run -p 3001:3001 -it sample-oauth2-spring-boot-webflux \ No newline at end of file diff --git a/webflux-login/src/main/java/org/example/webflux/DemoApplication.java b/webflux-login/src/main/java/org/example/webflux/DemoApplication.java new file mode 100644 index 0000000..ed16f53 --- /dev/null +++ b/webflux-login/src/main/java/org/example/webflux/DemoApplication.java @@ -0,0 +1,13 @@ +package org.example.webflux; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} \ No newline at end of file diff --git a/webflux-login/src/main/java/org/example/webflux/HomeController.java b/webflux-login/src/main/java/org/example/webflux/HomeController.java new file mode 100644 index 0000000..0446807 --- /dev/null +++ b/webflux-login/src/main/java/org/example/webflux/HomeController.java @@ -0,0 +1,22 @@ +package org.example.webflux; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * Controller for the home page. + */ +@Controller +public class HomeController { + + @GetMapping("/") + public String home(Model model, @AuthenticationPrincipal OidcUser principal) { + if (principal != null) { + model.addAttribute("profile", principal.getClaims()); + } + return "index"; + } +} diff --git a/webflux-login/src/main/java/org/example/webflux/ProfileController.java b/webflux-login/src/main/java/org/example/webflux/ProfileController.java new file mode 100644 index 0000000..2b9ec8d --- /dev/null +++ b/webflux-login/src/main/java/org/example/webflux/ProfileController.java @@ -0,0 +1,41 @@ +package org.example.webflux; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.Map; + +/** + * Controller for requests to the {@code /profile} resource. Populates the model with the claims from the + * {@linkplain OidcUser} for use by the view. + */ +@Controller +public class ProfileController { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + public static ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()); + + @GetMapping("/profile") + public String profile(Model model, @AuthenticationPrincipal OidcUser oidcUser) { + model.addAttribute("profile", oidcUser.getClaims()); + model.addAttribute("profileJson", claimsToJson(oidcUser.getClaims())); + return "profile"; + } + + private String claimsToJson(Map claims) { + try { + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(claims); + } catch (JsonProcessingException jpe) { + log.error("Error parsing claims to JSON", jpe); + } + return "Error parsing claims to JSON."; + } +} diff --git a/webflux-login/src/main/java/org/example/webflux/SecurityConfig.java b/webflux-login/src/main/java/org/example/webflux/SecurityConfig.java new file mode 100644 index 0000000..a88af13 --- /dev/null +++ b/webflux-login/src/main/java/org/example/webflux/SecurityConfig.java @@ -0,0 +1,62 @@ +package org.example.webflux; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler; +import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; + +import static org.springframework.security.config.Customizer.withDefaults; + +/** + * Configures the application's security settings + */ +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig { + + @Value("${spring.security.oauth2.client.provider.okta.issuer-uri}") + private String issuer; + @Value("${spring.security.oauth2.client.registration.okta.client-id}") + private String clientId; + + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + return http.authorizeExchange(authorize -> { + authorize.pathMatchers("/", "/images/**", "/css/**").permitAll(); + authorize.anyExchange().authenticated(); + }) + .oauth2Login(withDefaults()) + .logout(logout -> + logout.logoutSuccessHandler(logoutSuccessHandler()) + ).build(); + } + + /** + * Configures the logout handling to log users out of Auth0 after successful logout from the application. + * @return a {@linkplain ServerLogoutSuccessHandler} that will be called on successful logout. + */ + @Bean + public ServerLogoutSuccessHandler logoutSuccessHandler() { + // Change this as needed to URI where users should be redirected to after logout + String returnTo = "http://localhost:3001/"; + + // Build the URL to log the user out of Auth0 and redirect them to the home page. + // URL will look like https://YOUR-DOMAIN/v2/logout?clientId=YOUR-CLIENT-ID&returnTo=http://localhost:3001 + String logoutUrl = UriComponentsBuilder + .fromHttpUrl(issuer + "v2/logout?client_id={clientId}&returnTo={returnTo}") + .encode() + .buildAndExpand(clientId, returnTo) + .toUriString(); + + RedirectServerLogoutSuccessHandler handler = new RedirectServerLogoutSuccessHandler(); + handler.setLogoutSuccessUrl(URI.create(logoutUrl)); + return handler; + } +} diff --git a/webflux-login/src/main/resources/application.yml.example b/webflux-login/src/main/resources/application.yml.example new file mode 100644 index 0000000..0471b54 --- /dev/null +++ b/webflux-login/src/main/resources/application.yml.example @@ -0,0 +1,19 @@ +server: + port: 3001 + +spring: + security: + oauth2: + client: + provider: + okta: + issuer-uri: https://{DOMAIN}/ + registration: + okta: + client-id: {CLIENT_ID} + client-secret: {CLIENT_SECRET} + scope: openid,profile,email + +logging: + level: + org.springframework.security: DEBUG diff --git a/webflux-login/src/main/resources/public/css/auth0-theme.css b/webflux-login/src/main/resources/public/css/auth0-theme.css new file mode 100644 index 0000000..7a7ada2 --- /dev/null +++ b/webflux-login/src/main/resources/public/css/auth0-theme.css @@ -0,0 +1,329 @@ +.navbar-light.bg-light .nav-link { + color: #041433; +} + +.navbar { + background-color: #eff1f5; + padding: 1.375rem 0; + box-shadow: 0px 1px 2px 0px #dfe3ec; +} + +.navbar .router-link-exact-active { + color: #041433; + border-bottom: 3px solid #0066f9; +} + +.navbar-brand { + margin-right: 60px; +} + +.navbar-nav .nav-link { + color: #041433; + padding: 0 0 0.5rem 0; + box-sizing: border-box; + margin: 0 40px 0 0; + font-weight: 400; +} + +.navbar-nav .nav-link:hover { + color: #000104; +} + +.navbar-nav .btn { + min-width: 145px; +} + +.nav-user-profile { + border-radius: 50% 50%; + max-width: 3.785rem; + height: auto; + box-shadow: 0px 0px 4px 0px #b3bac7; +} + +.nav-item.dropdown .dropdown-toggle { + margin: 0; +} + +.nav-item.dropdown .dropdown-name { + font-weight: 600; +} + +.nav-item.dropdown .router-link-exact-active { + border: 0; +} + +.nav-item.dropdown .dropdown-menu { + box-shadow: 0px 0px 4px 0px #b3bac7; +} + +.nav-item.dropdown .dropdown-header { + border-bottom: 1px solid #b3bac7; + font-size: 1rem; + font-weight: 600; + color: #041433; +} + +.nav-item.dropdown .dropdown-item { + border-bottom: 1px solid #b3bac7; + padding: 0.55rem 1.5rem; +} + +.nav-item.dropdown .dropdown-item .icon { + margin-right: 8px; + vertical-align: middle; +} + +.nav-item.dropdown .dropdown-item:first-child:hover { + background: inherit; +} + +.nav-item.dropdown .dropdown-item:last-child { + border-bottom: 0; +} + +.navbar { + padding: 1.375rem 0; +} + +@media (min-width: 576px) { + .container { + max-width: inherit; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +@media (max-width: 767px) { + .nav-item { + margin-bottom: 1rem; + } + + .nav-item .nav-link { + padding: 0 0 0 16px; + } + + .nav-item .nav-link.router-link-exact-active { + border-bottom: 0; + border-left: 3px solid #0066f9; + padding-left: 13px !important; + vertical-align: middle; + } + + .navbar-brand { + margin-left: 2.5rem; + vertical-align: top; + } + + .navbar-toggler { + margin-right: 2.5rem; + } + + .navbar-nav { + margin-left: 1.5rem; + margin-right: 1.5rem; + } + + .navbar-nav:first-child { + margin-top: 1.5em; + } + + .navbar-nav:last-child { + background: white; + box-shadow: 0 -1px 2px 0 #dfe3ec; + margin: 1.5em 0 0 0; + padding: 1.5em 2.5rem; + } + + .navbar-nav:last-child li { + margin-bottom: 1em; + } + + .navbar-nav:last-child li .icon { + margin-right: 1em; + vertical-align: middle; + } + + .navbar-nav:last-child li a { + vertical-align: middle; + color: #041433; + } + + .navbar-nav .user-info img { + margin-right: 1em; + } + + .navbar-nav .btn-link { + padding: 0; + color: #041433; + min-width: 0px; + } +} + +.logo { + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzYgNDEiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGRlZnM+PGZpbHRlciB4PSItNi45JSIgeT0iLTYuMiUiIHdpZHRoPSIxMTMuOSUiIGhlaWdodD0iMTEyLjUlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXItMSI+PGZlT2Zmc2V0IGR4PSIwIiBkeT0iMSIgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMSI+PC9mZU9mZnNldD48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMSAgIDAgMCAwIDAgMSAgIDAgMCAwIDAgMSAgMCAwIDAgMC41IDAiIHR5cGU9Im1hdHJpeCIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dNYXRyaXhPdXRlcjEiPjwvZmVDb2xvck1hdHJpeD48ZmVNZXJnZT48ZmVNZXJnZU5vZGUgaW49InNoYWRvd01hdHJpeE91dGVyMSI+PC9mZU1lcmdlTm9kZT48ZmVNZXJnZU5vZGUgaW49IlNvdXJjZUdyYXBoaWMiPjwvZmVNZXJnZU5vZGU+PC9mZU1lcmdlPjwvZmlsdGVyPjwvZGVmcz48ZyBpZD0iUHJvamVjdC1TYW1wbGUtVUkiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGlkPSJIb21lLUxvZ2dlZCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI0Ny4wMDAwMDAsIC0yOC4wMDAwMDApIj48ZyBpZD0iSGVhZGVyIj48ZyBpZD0iTG9nbyIgZmlsdGVyPSJ1cmwoI2ZpbHRlci0xKSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjQ3LjAwMDAwMCwgMjguMDAwMDAwKSI+PHBhdGggZD0iTTMxLjA3NzIzMjQsMCBMMTcuOTk5NTI4NywwIEw0LjkxOTYzMTUyLDAgTDAuODc4MDkwMjU4LDEyLjM1OTU2MjUgQy0xLjU3OTczNzE0LDE5Ljk0OTAyODQgMS4yODcxNzk3OCwyNy45Mzc3NzIxIDcuNDE2OTQyMDksMzIuMzYwNjI0NCBMMTcuOTk3MzM1Miw0MCBMMjguNTgxMDE4NiwzMi4zNTk1NjI1IEMzNS4wNDA5MDQsMjcuNjk1NjU2OCAzNy40NjAzNDUsMTkuNTU5MzA3NiAzNS4xMzQxMjgyLDEyLjM5Nzc5MTIgQzM1LjEyNDI1NzQsMTIuMzY0ODcyIDMxLjA3NzIzMjQsMCAzMS4wNzcyMzI0LDAiIGlkPSJGaWxsLTQiIGZpbGw9IiMwNDE0MzMiPjwvcGF0aD48cG9seWdvbiBpZD0iRmlsbC02IiBmaWxsPSIjRUZGMUY1IiBwb2ludHM9IjE4IDAgMjIuMDEyOTU0NyAxMi42MDQ3OTE4IDM1IDEyLjYwNDc5MTggMjQuNDkyOTgwMiAyMC4zOTUyMDgyIDI4LjUwNzAxOTggMzMgMTggMjUuMjEwNjY4MiA3LjQ5Mjk4MDIyIDMzIDExLjUwNTkzNDkgMjAuMzk1MjA4MiAxIDEyLjYwNDc5MTggMTMuOTg3MDQ1MyAxMi42MDQ3OTE4Ij48L3BvbHlnb24+PC9nPjwvZz48L2c+PC9nPjwvc3ZnPg=="); + width: 2rem; + height: 2.25rem; + background-repeat: no-repeat; + background-size: cover; +} + +footer { + background: #eff1f5; + box-shadow: 0px -1px 2px 0px #dfe3ec; + min-height: 132px; + text-align: center; + padding: 32px; +} + +footer .logo { + margin: 0 auto 10px auto; +} + +.hero { + max-width: 500px; + margin: 0 auto; +} + +.hero p.lead { + margin-bottom: 10rem; + font-size: 1.45rem; +} + +.hero p.lead a { + font-weight: 500; +} + +.hero .app-logo { + max-width: 7.5rem; +} + +.next-steps { + padding: 0 32px; +} + +.next-steps h2 { + margin-bottom: 4rem; +} + +.next-steps .row { + margin-bottom: 3rem; +} + +@media (max-width: 768px) { + .next-steps .row { + margin-bottom: 0; + } + + .next-steps h6 { + margin-top: 1.5rem; + } +} + +pre { + width: 100%; +} + +.profile-header { + text-align: center; + margin-bottom: 3rem; +} + +.profile-header .lead { + font-size: 1.25rem; +} + +.profile-header .profile-picture { + box-shadow: 0px 0px 4px 0px #b3bac7; + margin-bottom: 1.5rem; +} + +@media (min-width: 768px) { + .profile-header { + text-align: left; + } + + .profile-header .profile-picture { + margin-bottom: inherit; + } +} + +.result-block-container { + position: relative; + min-height: 300px; +} + +.result-block { + top: -10px; + position: absolute; + opacity: 0; + transition: all 0.3s; +} + +.result-block.show { + opacity: 1; + top: 0px; +} + +@media only screen and (min-width: 768px) { + .result-block { + min-width: 700px; + } +} + +html { + font-size: 16px; +} + +body { + color: #041433; +} + +html, +body { + height: 100%; + margin: 0; + padding: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 600; +} + +h6.muted { + color: #6c757d; + text-transform: uppercase; + font-weight: 300; + margin-bottom: 1.5rem; + font-size: 1rem; +} + +a { + color: #0066f9; +} +a:hover { + text-decoration: none; +} + +.btn-primary { + background-color: #0066f9; + border: 1px solid #005ce0; +} + +.btn { + border-radius: 0.1rem; +} \ No newline at end of file diff --git a/webflux-login/src/main/resources/public/images/logo.png b/webflux-login/src/main/resources/public/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..238d3d5b6a6e8dbaa16e94a82686573c02fa27a2 GIT binary patch literal 5682 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 + + + Footer + +
    + +

    + Sample project provided by + Auth0 +

    +
    + \ No newline at end of file diff --git a/webflux-login/src/main/resources/templates/fragments/header.html b/webflux-login/src/main/resources/templates/fragments/header.html new file mode 100644 index 0000000..e3c6f37 --- /dev/null +++ b/webflux-login/src/main/resources/templates/fragments/header.html @@ -0,0 +1,15 @@ + + + + + + + Spring Boot 3 MVC WebFlux Quickstart + + + + + + + + \ No newline at end of file diff --git a/webflux-login/src/main/resources/templates/fragments/navbar.html b/webflux-login/src/main/resources/templates/fragments/navbar.html new file mode 100644 index 0000000..67c4231 --- /dev/null +++ b/webflux-login/src/main/resources/templates/fragments/navbar.html @@ -0,0 +1,93 @@ + + + + + + + Spring WebFlux Login Quickstart + + + \ No newline at end of file diff --git a/webflux-login/src/main/resources/templates/fragments/scripts.html b/webflux-login/src/main/resources/templates/fragments/scripts.html new file mode 100644 index 0000000..8f52e38 --- /dev/null +++ b/webflux-login/src/main/resources/templates/fragments/scripts.html @@ -0,0 +1,16 @@ + + + + + Scripts + + +
    + + + + + +
    + + \ No newline at end of file diff --git a/webflux-login/src/main/resources/templates/index.html b/webflux-login/src/main/resources/templates/index.html new file mode 100644 index 0000000..329f84a --- /dev/null +++ b/webflux-login/src/main/resources/templates/index.html @@ -0,0 +1,68 @@ + + + +
    +
    +
    +
    + +

    Spring Boot Login Sample Project

    +

    + This is a sample application that demonstrates an authentication flow for a Spring Boot WebFlux web application. +

    +
    + +
    + +
    +

    What can I do next?

    + +
    +
    +
    + + Configure other identity providers + +
    +

    Auth0 supports social providers as Facebook, Twitter, Instagram and 100+, Enterprise providers as Microsoft Office 365, Google Apps, Azure, and more. You can also use any OAuth2 Authorization Server.

    +
    + +
    + +
    +
    + + Enable Multifactor Authentication + +
    +

    Add an extra layer of security by enabling Multi-factor Authentication, requiring your users to provide more than one piece of identifying information. Push notifications, authenticator apps, SMS, and DUO Security are supported.

    +
    +
    + +
    +
    +
    + + Anomaly Detection + +
    +

    Auth0 can detect anomalies and stop malicious attempts to access your application. Anomaly detection can alert you and your users of suspicious activity, as well as block further login attempts.

    +
    + +
    + +
    +
    + + Learn About Rules + +
    +

    Rules are JavaScript functions that execute when a user authenticates to your application. They run once the authentication process is complete, and you can use them to customize and extend Auth0's capabilities.

    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/webflux-login/src/main/resources/templates/layouts/default.html b/webflux-login/src/main/resources/templates/layouts/default.html new file mode 100644 index 0000000..c51c267 --- /dev/null +++ b/webflux-login/src/main/resources/templates/layouts/default.html @@ -0,0 +1,14 @@ + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/webflux-login/src/main/resources/templates/profile.html b/webflux-login/src/main/resources/templates/profile.html new file mode 100644 index 0000000..11c4f33 --- /dev/null +++ b/webflux-login/src/main/resources/templates/profile.html @@ -0,0 +1,28 @@ + + + +
    +
    +
    +
    +
    +
    + +
    +
    +

    +

    +

    +
    + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/webflux-login/src/test/java/org/example/webflux/DemoApplicationTest.java b/webflux-login/src/test/java/org/example/webflux/DemoApplicationTest.java new file mode 100644 index 0000000..e5e420a --- /dev/null +++ b/webflux-login/src/test/java/org/example/webflux/DemoApplicationTest.java @@ -0,0 +1,13 @@ +package org.example.webflux; + +import org.junit.jupiter.api.Test; + +public class DemoApplicationTest { + + @Test + public void main() { + // Act + DemoApplication.main(new String[] {}); + } + +} diff --git a/webflux-login/src/test/java/org/example/webflux/HomeControllerTest.java b/webflux-login/src/test/java/org/example/webflux/HomeControllerTest.java new file mode 100644 index 0000000..818ecd6 --- /dev/null +++ b/webflux-login/src/test/java/org/example/webflux/HomeControllerTest.java @@ -0,0 +1,51 @@ +package org.example.webflux; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.ui.Model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@SpringBootTest +class HomeControllerTest { + + private HomeController homeController; + + @Mock + private Model model; + + @Mock + private OidcUser principal; + + @BeforeEach + public void beforeEach(){ + homeController = new HomeController(); + } + + @Test + public void testHomeModel(){ + // Act + String returnValue = homeController.home(model, principal); + + // Assert + verify(model, times(1)).addAttribute(eq("profile"), Mockito.any()); + assertEquals("index", returnValue); + } + + @Test + public void testHomeModelNullPrincipal(){ + // Act + String returnValue = homeController.home(model, null); + + // Assert + verify(model, never()).addAttribute(eq("profile"), Mockito.any()); + assertEquals("index", returnValue); + } + +} \ No newline at end of file diff --git a/webflux-login/src/test/java/org/example/webflux/ProfileControllerTest.java b/webflux-login/src/test/java/org/example/webflux/ProfileControllerTest.java new file mode 100644 index 0000000..23d37c2 --- /dev/null +++ b/webflux-login/src/test/java/org/example/webflux/ProfileControllerTest.java @@ -0,0 +1,74 @@ +package org.example.webflux; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.ui.Model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@SpringBootTest +public class ProfileControllerTest { + + private ProfileController profileController; + + @Mock + private Model model; + + @Mock + private OidcUser oidcUser; + + @BeforeEach + public void beforeEach(){ + profileController = new ProfileController(); + } + + @Test + public void testProfileModel() { + // Act + String returnValue = profileController.profile(model, oidcUser); + + // Assert + verify(model, times(1)).addAttribute(eq("profile"), Mockito.any()); + verify(model, times(1)).addAttribute(eq("profileJson"), Mockito.any()); + assertEquals("profile", returnValue); + } + + @Test + public void testProfileModel_Exception() throws JsonProcessingException { + // Prepare + // Mock Logger + Logger mockedLogger = Mockito.mock(Logger.class); + ReflectionTestUtils.setField(profileController, "log", mockedLogger); + + // Mocked ObjectWriter calls writeValueAsString() and throws JsonProcessingException + ObjectWriter mockedWriter = Mockito.mock(ObjectWriter.class); + when(mockedWriter.writeValueAsString(Mockito.any())).thenThrow(new JsonProcessingException("Error"){}); + + // Mocked ObjectMapper returns mocked ObjectWriter + ObjectMapper mockedMapper = Mockito.mock(ObjectMapper.class); + when(mockedMapper.writerWithDefaultPrettyPrinter()).thenReturn(mockedWriter); + + // Set mocked ObjectMapper on class through reflection + ReflectionTestUtils.setField(profileController, "mapper", mockedMapper); + + // Act + String returnValue = profileController.profile(model, oidcUser); + + // Assert + verify(model, times(1)).addAttribute(eq("profile"), Mockito.any()); + verify(model, times(1)).addAttribute(eq("profileJson"), Mockito.any()); + assertEquals("profile", returnValue); + } + +} diff --git a/webflux-login/src/test/java/org/example/webflux/SecurityConfig.java b/webflux-login/src/test/java/org/example/webflux/SecurityConfig.java new file mode 100644 index 0000000..e5912fa --- /dev/null +++ b/webflux-login/src/test/java/org/example/webflux/SecurityConfig.java @@ -0,0 +1,8 @@ +package org.example.webflux; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Profile("test") +@Configuration +class SecurityConfig {} \ No newline at end of file