diff --git a/Spring BFF/BFF/Dockerfile b/Spring BFF/BFF/Dockerfile deleted file mode 100644 index 9f54a73..0000000 --- a/Spring BFF/BFF/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# Use an official Java runtime as a parent image -FROM openjdk:17-jdk-alpine - -# Create user to run the app as (instead of root) -RUN addgroup -S app && adduser -S app -G app - -# User user "app" -USER app - -# Set the working directory in the container -WORKDIR /app - -# Copy the JAR file into the container -COPY build/libs/BFF-0.0.1-SNAPSHOT.jar /app/app.jar - -# Set the command to run the JAR file -ENTRYPOINT ["java", "-jar", "/app/app.jar"] - -# Expose the port BFF will use -EXPOSE 9090 - -# accessible to all network interfaces (e.g. other containers) inside the docker network -CMD ["--server.address=0.0.0.0"] \ No newline at end of file diff --git a/Spring BFF/BFF/HELP.md b/Spring BFF/BFF/HELP.md deleted file mode 100644 index 768ff83..0000000 --- a/Spring BFF/BFF/HELP.md +++ /dev/null @@ -1,24 +0,0 @@ -# Getting Started - -### Reference Documentation - -For further reference, please consider the following sections: - -* [Official Gradle documentation](https://docs.gradle.org) -* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.3.2/gradle-plugin/reference/html/) -* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.3.2/gradle-plugin/reference/html/#build-image) -* [Coroutines section of the Spring Framework Documentation](https://docs.spring.io/spring/docs/6.1.11/spring-framework-reference/languages.html#coroutines) -* [Reactive Gateway](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/) - -### Guides - -The following guides illustrate how to use some features concretely: - -* [Using Spring Cloud Gateway](https://github.com/spring-cloud-samples/spring-cloud-gateway-sample) - -### Additional Links - -These additional references should also help you: - -* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) - diff --git a/Spring BFF/BFF/build.gradle.kts b/Spring BFF/BFF/build.gradle.kts deleted file mode 100644 index 9feb07e..0000000 --- a/Spring BFF/BFF/build.gradle.kts +++ /dev/null @@ -1,86 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("org.springframework.boot") version "3.3.2" - id("io.spring.dependency-management") version "1.1.6" - kotlin("jvm") version "1.9.24" - kotlin("plugin.spring") version "1.9.24" -} - -group = "com.frontiers" -version = "0.0.1-SNAPSHOT" - -java { - sourceCompatibility = JavaVersion.VERSION_17 -} - -configurations { - compileOnly { - extendsFrom(configurations.annotationProcessor.get()) - } -} - -repositories { - mavenCentral() - // needed for the spring oidc addons - maven { url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") } -} - -extra["springCloudVersion"] = "2023.0.3" - -dependencies { - - /* kotlin co-routines */ - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") - - /* kotlin reflection */ - implementation("org.jetbrains.kotlin:kotlin-reflect") - - /* kotlin standard library with JDK 8 extensions */ - implementation(kotlin("stdlib-jdk8")) - - /* redis cache */ - implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive") - - /* spring session */ -// implementation("org.springframework.session:spring-session-core") - implementation("org.springframework.session:spring-session-data-redis") - implementation("org.apache.commons:commons-pool2:2.11.1") - - /* spring cloud gateway */ - implementation("org.springframework.cloud:spring-cloud-starter-gateway") - - /* spring actuator */ - implementation("org.springframework.boot:spring-boot-starter-actuator") - - /* spring circuit breaker */ - implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j") - - /* spring security - essentials */ - implementation("org.springframework.boot:spring-boot-starter-security") - - /* spring security - oauth2 */ - // OAuth2 client functionality - implementation("org.springframework.security:spring-security-oauth2-client") - // JSON Object Signing and Encryption (JOSE). - implementation("org.springframework.security:spring-security-oauth2-jose") - - /* spring other */ - // Jakarta Servlet API - implementation("jakarta.servlet:jakarta.servlet-api:5.0.0") - // Java DSL for reading JSON documents - implementation("com.jayway.jsonpath:json-path:2.9.0") -} - -dependencyManagement { - imports { - mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}") - } -} - -tasks.withType { - kotlinOptions { - freeCompilerArgs += "-Xjsr305=strict" - jvmTarget = "17" - } -} \ No newline at end of file diff --git a/Spring BFF/BFF/gradle/wrapper/gradle-wrapper.jar b/Spring BFF/BFF/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e644113..0000000 Binary files a/Spring BFF/BFF/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/Spring BFF/BFF/gradle/wrapper/gradle-wrapper.properties b/Spring BFF/BFF/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index a441313..0000000 --- a/Spring BFF/BFF/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/Spring BFF/BFF/gradlew b/Spring BFF/BFF/gradlew deleted file mode 100644 index b740cf1..0000000 --- a/Spring BFF/BFF/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/Spring BFF/BFF/gradlew.bat b/Spring BFF/BFF/gradlew.bat deleted file mode 100644 index 7101f8e..0000000 --- a/Spring BFF/BFF/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/Spring BFF/BFF/settings.gradle b/Spring BFF/BFF/settings.gradle deleted file mode 100644 index 509dfb3..0000000 --- a/Spring BFF/BFF/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'BFF' diff --git a/Spring BFF/BFF/settings.gradle.kts b/Spring BFF/BFF/settings.gradle.kts deleted file mode 100644 index eb52504..0000000 --- a/Spring BFF/BFF/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "BFF" diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/BFFApplication.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/BFFApplication.kt deleted file mode 100644 index 11abe1a..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/BFFApplication.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.frontiers.bff - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication - -@SpringBootApplication -class BFFApplication - -fun main(args: Array) { - - // run SpringBoot application - runApplication(*args) - -} diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/BackChannelLogoutController.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/BackChannelLogoutController.kt deleted file mode 100644 index 3013f8f..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/BackChannelLogoutController.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.frontiers.bff.api.controllers - -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import org.springframework.web.bind.annotation.RequestBody - -/**********************************************************************************************************************/ -/**************************************************** CONTROLLER ******************************************************/ -/**********************************************************************************************************************/ - -// see more here: -// https://auth0.com/docs/authenticate/login/logout/back-channel-logout - -@RestController -@RequestMapping("/logout/connect/back-channel/in-house-auth-server") -internal class BackChannelLogoutController { - - @PostMapping - fun handleLogout(@RequestBody logoutRequest: Map): ResponseEntity { - // Handle the logout notification here - // For example, invalidate user sessions or perform other cleanup - - println("Received back-channel logout request: $logoutRequest") - - // Return HTTP 200 OK to acknowledge receipt of the logout request - return ResponseEntity.ok().build() - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/GatewayFallback.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/GatewayFallback.kt deleted file mode 100644 index 6f13823..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/GatewayFallback.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.frontiers.bff.api.controllers - -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RestController - -/**********************************************************************************************************************/ -/**************************************************** CONTROLLER ******************************************************/ -/**********************************************************************************************************************/ - -@RestController -internal class FallbackController { - - @GetMapping("/fallback") - fun fallback(): Map { - return mapOf("message" to "Gateway fallback occured") - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/LoginOptionsController.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/LoginOptionsController.kt deleted file mode 100644 index 81b5858..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/api/controllers/LoginOptionsController.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.frontiers.bff.api.controllers - -import com.frontiers.bff.props.ServerProperties -import com.fasterxml.jackson.annotation.JsonProperty -import jakarta.validation.constraints.NotEmpty -import org.springframework.http.MediaType -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter -import org.springframework.security.oauth2.core.AuthorizationGrantType -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import reactor.core.publisher.Mono -import java.net.URI - -/**********************************************************************************************************************/ -/**************************************************** CONTROLLER ******************************************************/ -/**********************************************************************************************************************/ - -@RestController -@RequestMapping("/login-options") -internal class LoginOptionsController( - private val serverProperties: ServerProperties, - private val clientRegistrationRepository: ReactiveClientRegistrationRepository, -) { - - private var loginOptions: List? = null - - @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) - fun getLoginOptions(): Mono> { - if (loginOptions == null || loginOptions!!.isEmpty()) { - - val clientAuthority = URI.create(serverProperties.reverseProxyUri).authority - val clientRegistrations = (clientRegistrationRepository as? InMemoryReactiveClientRegistrationRepository)?.toList() - ?: emptyList() - - loginOptions = clientRegistrations - .filter { it.authorizationGrantType == AuthorizationGrantType.AUTHORIZATION_CODE } - .mapNotNull { registration -> - // client name - val label = registration.clientName - // internal oauth2 request redirection filter - val oauth2Redirection = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI - // endpoint that redirects to the auth server - val loginUri = "${serverProperties.bffServerUri}$oauth2Redirection/${registration.registrationId}" - // checks if issuer authority and reverse proxy authority are the same - val providerIssuerAuthority = registration.providerDetails.issuerUri?.toString() - ?.let { URI.create(it).authority } - LoginOptionDto(label, loginUri, clientAuthority == providerIssuerAuthority) - } - } - - return Mono.just(loginOptions!!) - } - - data class LoginOptionDto( - @field:NotEmpty val label: String, - @field:NotEmpty val loginUri: String, - @get:JsonProperty("isSameAuthority") val isSameAuthority: Boolean - ) -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/BffSecurityConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/BffSecurityConfig.kt deleted file mode 100644 index 85b2f8e..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/BffSecurityConfig.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.frontiers.bff.auth - -import com.frontiers.bff.auth.configurations.postprocessors.ClientConfigurationSupport -import com.frontiers.bff.auth.configurations.ClientAuthorizeExchangeSpecPostProcessor -import com.frontiers.bff.auth.configurations.ClientReactiveHttpSecurityPostProcessor -import com.frontiers.bff.auth.cors.CORSConfig -import com.frontiers.bff.auth.csrf.CsrfProtectionMatcher -import com.frontiers.bff.auth.filters.csrf.CsrfWebCookieFilter -import com.frontiers.bff.auth.handlers.DelegatingAuthenticationSuccessHandler -import com.frontiers.bff.auth.handlers.SessionServerLogoutHandler -import com.frontiers.bff.auth.handlers.csrf.SPACsrfTokenRequestHandler -import com.frontiers.bff.auth.handlers.oauth2.OAuth2ServerAuthenticationFailureHandler -import com.frontiers.bff.auth.handlers.oauth2.OAuth2ServerLogoutSuccessHandler -import com.frontiers.bff.auth.handlers.oauth2.PreAuthorizationCodeServerRedirectStrategy -import com.frontiers.bff.auth.handlers.sessions.CustomMaximumSessionsExceededHandler -import com.frontiers.bff.auth.repositories.authrequests.RedisAuthorizationRequestRepository -import com.frontiers.bff.auth.repositories.authclients.RedisServerOAuth2AuthorizedClientRepository -import com.frontiers.bff.auth.repositories.securitycontext.RedisSecurityContextRepository -import com.frontiers.bff.auth.requestcache.ReactiveRequestCache -import com.frontiers.bff.props.* -import com.frontiers.bff.props.ServerProperties -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.core.Ordered -import org.springframework.core.annotation.Order -import org.springframework.data.redis.core.RedisKeyValueAdapter -import org.springframework.data.redis.repository.configuration.EnableRedisRepositories -import org.springframework.http.HttpMethod -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity -import org.springframework.security.config.web.server.SecurityWebFiltersOrder -import org.springframework.security.config.web.server.ServerHttpSecurity -import org.springframework.security.core.session.ReactiveSessionRegistry -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver -import org.springframework.security.web.server.SecurityWebFilterChain -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint -import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository -import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher -import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers -import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisIndexedWebSession -import org.springframework.web.util.UriComponentsBuilder -import java.net.URI -import org.springframework.boot.autoconfigure.web.ServerProperties as NettyServerProperties - -/**********************************************************************************************************************/ -/*********************************************** DEFAULT SECURITY CONFIGURATION ***************************************/ -/**********************************************************************************************************************/ - -@Configuration -@EnableWebFluxSecurity -@EnableRedisIndexedWebSession -@Order(Ordered.LOWEST_PRECEDENCE - 1) -internal class BffSecurityConfig ( - private val serverProperties: ServerProperties, -) { - - @Bean - fun clientSecurityFilterChain( - http: ServerHttpSecurity, - nettyServerProperties: NettyServerProperties, - - loginProperties: LoginProperties, - - serverCsrfTokenRepository: ServerCsrfTokenRepository, - spaCsrfTokenRequestHandler: SPACsrfTokenRequestHandler, - csrfProtectionMatcher: CsrfProtectionMatcher, - csrfCookieWebFilter: CsrfWebCookieFilter, - - corsConfig: CORSConfig, - corsProperties: CorsProperties, - - reactiveRequestCache: ReactiveRequestCache, - reactiveSessionRegistry: ReactiveSessionRegistry, - maximumSessionsExceededHandler: CustomMaximumSessionsExceededHandler, - - authenticationProperties: AuthenticationProperties, - authorizationProperties: AuthorizationProperties, - - oauthAuthorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver, - redisAuthorizationRequestRepository: RedisAuthorizationRequestRepository, - preAuthorizationCodeRedirectStrategy: PreAuthorizationCodeServerRedirectStrategy, - delegatingAuthenticationSuccessHandler: DelegatingAuthenticationSuccessHandler, - oauth2ServerAuthenticationFailureHandler: OAuth2ServerAuthenticationFailureHandler, - - redisSecurityContextRepository: RedisSecurityContextRepository, - reactiveClientRegistrationRepository: ReactiveClientRegistrationRepository, - redisAuthorizedClientRepository: RedisServerOAuth2AuthorizedClientRepository, - - logoutProperties: LogoutProperties, - logoutHandler: SessionServerLogoutHandler, - logoutSuccessHandler: OAuth2ServerLogoutSuccessHandler, - backChannelLogoutProperties: BackChannelLogoutProperties, - - authorizePostProcessor: ClientAuthorizeExchangeSpecPostProcessor, - httpPostProcessor: ClientReactiveHttpSecurityPostProcessor, - ): SecurityWebFilterChain { - - /* initialise logger */ - val log = LoggerFactory.getLogger(SecurityWebFilterChain::class.java) - - /* apply security matchers to this filter chain */ - val clientRoutes: List = authenticationProperties - .securityMatchers - .map { PathPatternParserServerWebExchangeMatcher(it) } - log.info( - "Applying client OAuth2 configuration for: {}", - authenticationProperties.securityMatchers - ) - http.securityMatcher(OrServerWebExchangeMatcher(clientRoutes)) - - /* unauthenticated exception handler */ - loginProperties.LOGIN_URL.let { loginPath -> - http.exceptionHandling { exceptionHandling -> - exceptionHandling.authenticationEntryPoint( - RedirectServerAuthenticationEntryPoint( - UriComponentsBuilder.fromUri( - URI.create(serverProperties.clientUri) - ).path(loginPath).build().toString() - ) - ) - } - } - - /* enable csrf */ - http.csrf { csrf -> - csrf.csrfTokenRepository(serverCsrfTokenRepository) - csrf.csrfTokenRequestHandler(spaCsrfTokenRequestHandler) - csrf.requireCsrfProtectionMatcher(csrfProtectionMatcher) - } - - /* configure cors */ - http.cors { cors -> - cors.configurationSource( - corsConfig.corsConfigurationSource() - ) - } - - /* configure request cache */ - http.requestCache { cache -> - cache.requestCache(reactiveRequestCache) - } - - /* security context */ - http.securityContextRepository(redisSecurityContextRepository) - - /* session management */ - // this is also handled in the success handler, customConcurrentSessionControlSuccessHandler - - /* oauth2.0 client login */ - http.oauth2Login { oauth2 -> - oauth2.authorizationRequestResolver(oauthAuthorizationRequestResolver) - oauth2.authorizationRequestRepository(redisAuthorizationRequestRepository) - - oauth2.authorizationRedirectStrategy(preAuthorizationCodeRedirectStrategy) - oauth2.authenticationSuccessHandler(delegatingAuthenticationSuccessHandler) - oauth2.authenticationFailureHandler(oauth2ServerAuthenticationFailureHandler) - - oauth2.securityContextRepository(redisSecurityContextRepository) - oauth2.clientRegistrationRepository(reactiveClientRegistrationRepository) - oauth2.authorizedClientRepository(redisAuthorizedClientRepository) - } - - /* oauth2.0 client */ - http.oauth2Client {} - - /* logout configuration (local session logout, then relying-party initiated logout) */ - http.logout { logoutSpec -> - logoutSpec.logoutUrl("/logout") - logoutSpec.requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout")) - logoutSpec.logoutHandler(logoutHandler) - logoutSpec.logoutSuccessHandler(logoutSuccessHandler) - } - - /* oidc backchannel logout configuration */ - // automatically creates: /logout/connect/back-channel/{registrationId} - if(backChannelLogoutProperties.enabled) { - http.oidcLogout { logout -> - logout.backChannel { bc -> - bc.logoutUri(backChannelLogoutProperties.backChannelLogoutUri) - } -// logout.oidcSessionRegistry() -// logout.clientRegistrationRepository() - // what is ReactiveOidcSessionStrategy for? - // https://docs.spring.io/spring-security/reference/reactive/oauth2/login/logout.html#configure-provider-initiated-oidc-logout - } - } - - /* other filters */ - // apply csrf filter after the logout handler - http.addFilterAfter(csrfCookieWebFilter, SecurityWebFiltersOrder.LOGOUT) - - /* apply additional configuraitons */ - ClientConfigurationSupport.configureClient( - http, - nettyServerProperties, - corsProperties, - authorizationProperties, - authorizePostProcessor, - httpPostProcessor - ) - - return http.build() - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/configurations/PostProcessorsConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/configurations/PostProcessorsConfig.kt deleted file mode 100644 index 657236e..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/configurations/PostProcessorsConfig.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.frontiers.bff.auth.configurations - -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.security.config.web.server.ServerHttpSecurity -import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec - -/**********************************************************************************************************************/ -/*************************************************** CONFIGURATION ****************************************************/ -/**********************************************************************************************************************/ - -@Configuration -internal class PostProcessorsConfig { - - /** - * Hook to override security rules for all paths that are not listed in - * "permit-all". Default is isAuthenticated(). - * - * @return a hook to override security rules for all paths that are not listed in - * "permit-all". Default is isAuthenticated(). - * - * Fine tunes access control from java configuration. It applies to all routes not listed in "permit-all" - * property configuration. Default requires users to be authenticated. - */ - @ConditionalOnMissingBean - @Bean - internal fun clientAuthorizePostProcessor(): ClientAuthorizeExchangeSpecPostProcessor { - return ClientAuthorizeExchangeSpecPostProcessor { - spec: ServerHttpSecurity.AuthorizeExchangeSpec -> - spec.anyExchange().authenticated() - } - } - - /** - * Hook to override all or part of HttpSecurity auto-configuration. - * - * @return a hook to override all or part of HttpSecurity auto-configuration. - * Overrides anything from above auto-configuration. - * It is called just before the security filter-chain is returned. Default is a no-op. - */ - @ConditionalOnMissingBean - @Bean - internal fun clientHttpPostProcessor(): ClientReactiveHttpSecurityPostProcessor { - return ClientReactiveHttpSecurityPostProcessor { - serverHttpSecurity: ServerHttpSecurity -> - serverHttpSecurity - } - } -} - -/**********************************************************************************************************************/ -/***************************************************** INTERFACES *****************************************************/ -/**********************************************************************************************************************/ - -internal fun interface ClientAuthorizeExchangeSpecPostProcessor : AuthorizeExchangeSpecPostProcessor - -internal fun interface ClientReactiveHttpSecurityPostProcessor : ReactiveHttpSecurityPostProcessor - -interface AuthorizeExchangeSpecPostProcessor { - fun authorizeHttpRequests(spec: AuthorizeExchangeSpec): AuthorizeExchangeSpec? -} - -interface ReactiveHttpSecurityPostProcessor { - fun process(serverHttpSecurity: ServerHttpSecurity): ServerHttpSecurity -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/configurations/postprocessors/ClientConfigurationSupport.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/configurations/postprocessors/ClientConfigurationSupport.kt deleted file mode 100644 index f02908e..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/configurations/postprocessors/ClientConfigurationSupport.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.frontiers.bff.auth.configurations.postprocessors - -import com.frontiers.bff.auth.configurations.ClientAuthorizeExchangeSpecPostProcessor -import com.frontiers.bff.auth.configurations.ClientReactiveHttpSecurityPostProcessor -import com.frontiers.bff.props.AuthorizationProperties -import com.frontiers.bff.props.CorsProperties -import org.springframework.boot.autoconfigure.web.ServerProperties as NettyServerProperties -import org.springframework.http.HttpMethod -import org.springframework.security.config.Customizer -import org.springframework.security.config.web.server.ServerHttpSecurity -import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository -import org.springframework.stereotype.Component - -/**********************************************************************************************************************/ -/*************************************************** CLIENT CONFIGURATION *********************************************/ -/**********************************************************************************************************************/ - -/** - * Sets additional configurations for the main OAuth2 Client Security chain - */ -@Component -internal class ClientConfigurationSupport private constructor() { - - companion object { - - @JvmStatic - // configure OAuth 2.0 client security filter - fun configureClient( - http: ServerHttpSecurity, - nettyServerProperties: NettyServerProperties, - corsProperties: CorsProperties, - authorizationProperties: AuthorizationProperties, - authorizePostProcessor: ClientAuthorizeExchangeSpecPostProcessor, - httpPostProcessor: ClientReactiveHttpSecurityPostProcessor - ): ServerHttpSecurity { - - // wrap corsProperties into a List - val corsProps: List = listOf(corsProperties) - - // configure state (default is false) - configureState(http, false) - - // configure access - configureAccess(http, authorizationProperties.permitAll, corsProps) - - // configure ssl - if (nettyServerProperties.ssl != null && nettyServerProperties.ssl.isEnabled) { - http.redirectToHttps { } - } - - // security rules for all paths that are not listed in "permit-all" - http.authorizeExchange { authorizeExchangeSpec -> - authorizePostProcessor.authorizeHttpRequests(authorizeExchangeSpec) - } - - // hook to override all or part of HttpSecurity auto-configuration. - httpPostProcessor.process(http) - - return http - } - - @JvmStatic - // function for configuring authorization access - fun configureAccess( - http: ServerHttpSecurity, - permitAll: List, - corsProperties: List - ): ServerHttpSecurity { - - // allow access for prefetch request - OPTIONS? - val permittedCorsOptions = corsProperties - .filter { cors -> - (cors.allowedMethods.contains("*") || cors.allowedMethods.contains("OPTIONS")) && - !cors.disableAnonymousOptions - } - .map { it.path } - - // configure with defaults - if (permitAll.isNotEmpty() || permittedCorsOptions.isNotEmpty()) { - http.anonymous(Customizer.withDefaults()) - } - - // set permitAll path matchers - if (permitAll.isNotEmpty()) { - http.authorizeExchange { authorizeExchange -> - authorizeExchange.pathMatchers( - *permitAll.toTypedArray() - ).permitAll() - } - } - - // set CORS path matchers - if (permittedCorsOptions.isNotEmpty()) { - http.authorizeExchange { authorizeExchange -> - authorizeExchange.pathMatchers( - HttpMethod.OPTIONS, - *permittedCorsOptions.toTypedArray() - ).permitAll() - } - } - - return http - } - - @JvmStatic - // function for configuring state - fun configureState(http: ServerHttpSecurity, isStateless: Boolean): ServerHttpSecurity { - if (isStateless) { - http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()) - } - - return http - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/connections/RedisConnectionFactoryConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/connections/RedisConnectionFactoryConfig.kt deleted file mode 100644 index 4ce101a..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/connections/RedisConnectionFactoryConfig.kt +++ /dev/null @@ -1,172 +0,0 @@ -package com.frontiers.bff.auth.connections - -import com.frontiers.bff.props.SpringDataProperties -import io.lettuce.core.ClientOptions.DEFAULT_DISCONNECTED_BEHAVIOR -import io.lettuce.core.SocketOptions -import io.lettuce.core.SslOptions -import io.lettuce.core.TimeoutOptions -import io.lettuce.core.cluster.ClusterClientOptions -import io.lettuce.core.cluster.ClusterClientOptions.DEFAULT_MAX_REDIRECTS -import io.lettuce.core.cluster.ClusterTopologyRefreshOptions -import io.lettuce.core.internal.HostAndPort -import io.lettuce.core.protocol.DecodeBufferPolicies -import io.lettuce.core.protocol.ProtocolVersion -import io.lettuce.core.resource.DefaultClientResources -import io.lettuce.core.resource.DnsResolvers -import io.lettuce.core.resource.MappingSocketAddressResolver -import org.apache.commons.pool2.impl.GenericObjectPoolConfig -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Primary -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory -import org.springframework.data.redis.connection.RedisPassword -import org.springframework.data.redis.connection.RedisStandaloneConfiguration -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory -import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration -import java.net.InetAddress -import java.net.UnknownHostException -import java.time.Duration - - -/**********************************************************************************************************************/ -/************************************************ CONNECTION FACTORY **************************************************/ -/**********************************************************************************************************************/ - -// see here for more: -// https://docs.spring.io/spring-session/reference/web-session.html#websession-redis -// https://docs.spring.io/spring-session/reference/configuration/reactive-redis-indexed.html -// https://github.com/Azure/AzureCacheForRedis/blob/main/Lettuce%20Best%20Practices.md -// https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/identity/azure-identity/src/samples/Azure-Cache-For-Redis/Lettuce/Azure-AAD-Authentication-With-Lettuce.md - -/** - * Establishes a Connection (factory) with Redis - */ -@Configuration -internal class RedisConnectionFactoryConfig( - private val springDataProperties: SpringDataProperties -) { - - @Bean - @Primary - fun reactiveRedisConnectionFactory(): ReactiveRedisConnectionFactory { - - // configure Redis standalone configuration - val config = RedisStandaloneConfiguration() - config.hostName = springDataProperties.redis.host - config.port = springDataProperties.redis.port - config.setPassword(RedisPassword.of(springDataProperties.redis.password)) - - // create client options - - // Create SSL options if SSL is required - val sslOptions = SslOptions.builder() - .jdkSslProvider() // Or use OpenSslProvider if you prefer - .build() - - // Create timeout options - val timeoutOptions = TimeoutOptions.builder() - .fixedTimeout(Duration.ofSeconds(20)) - .timeoutCommands(true) - .build() - - // cluster specific settings for optimal reliability. - val clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder() - .enablePeriodicRefresh(Duration.ofSeconds(5)) - .dynamicRefreshSources(false) - .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(5)) - .enableAllAdaptiveRefreshTriggers().build() - - // create socket options - val socketOptions = SocketOptions.builder() - .keepAlive(SocketOptions.DEFAULT_SO_KEEPALIVE) - .tcpNoDelay(SocketOptions.DEFAULT_SO_NO_DELAY) - // time to wait for connection to be established, before considering it as a failed connection - .connectTimeout(Duration.ofSeconds(60)) - .build() - - val mappingFunction: (HostAndPort) -> HostAndPort = { hostAndPort -> - val host = springDataProperties.redis.host - val addresses: Array = try { - DnsResolvers.JVM_DEFAULT.resolve(host) - } catch (e: UnknownHostException) { - e.printStackTrace() - emptyArray() // Handle error and return an empty array - } - - val cacheIP = addresses.firstOrNull()?.hostAddress ?: "" - var finalAddress = hostAndPort - - if (hostAndPort.hostText == cacheIP) { - finalAddress = HostAndPort.of(host, hostAndPort.port) - } - - finalAddress - } - - val resolver = MappingSocketAddressResolver.create(DnsResolvers.JVM_DEFAULT, mappingFunction) - - // customize thread pool size - val clientResources = DefaultClientResources.builder() - .ioThreadPoolSize(8) - .computationThreadPoolSize(8) - .socketAddressResolver(resolver) - .build() - - val clusterClientOptions = ClusterClientOptions.builder() - .autoReconnect(true) - .pingBeforeActivateConnection(true) - .sslOptions(sslOptions) - .timeoutOptions(timeoutOptions) - .socketOptions(socketOptions) - .topologyRefreshOptions(clusterTopologyRefreshOptions) - .validateClusterNodeMembership(true) - .suspendReconnectOnProtocolFailure(true) - .disconnectedBehavior(DEFAULT_DISCONNECTED_BEHAVIOR) - .decodeBufferPolicy(DecodeBufferPolicies.ratio(0.5F)) - .requestQueueSize(1000) - .maxRedirects(DEFAULT_MAX_REDIRECTS) - .publishOnScheduler(true) //DEFAULT_PUBLISH_ON_SCHEDULER. - .protocolVersion(ProtocolVersion.RESP2) // Use RESP2 Protocol to ensure AUTH command is used for handshake. - .build() - - // configure connection pool settings - fun buildLettucePoolConfig(): GenericObjectPoolConfig { - val poolConfig = GenericObjectPoolConfig() - poolConfig.maxTotal = 100 - poolConfig.maxIdle = 50 - poolConfig.minIdle = 10 - poolConfig.setMaxWait(Duration.ofSeconds(120)) - poolConfig.timeBetweenEvictionRuns = Duration.ofSeconds(120) - poolConfig.minEvictableIdleTime = Duration.ofMinutes(5) - poolConfig.testOnBorrow = true - poolConfig.testWhileIdle = true - poolConfig.testOnReturn = true - poolConfig.blockWhenExhausted = true - poolConfig.lifo = true - return poolConfig - } - - // create Lettuce client configuration with authentication details - val clientConfig = LettucePoolingClientConfiguration.builder() - // maximum time allowed for a Redis command to execute before the operation is considered timed out. - .commandTimeout(Duration.ofSeconds(60)) - .clientResources(clientResources) - .clientOptions(clusterClientOptions) - .poolConfig(buildLettucePoolConfig()) - .useSsl() - .build() - - // create Lettuce connection factory - return LettuceConnectionFactory(config, clientConfig).apply { - afterPropertiesSet() - validateConnection = false - setShareNativeConnection(true) - } - } - -} - - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/cookies/CookiesSessionConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/cookies/CookiesSessionConfig.kt deleted file mode 100644 index 6987150..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/cookies/CookiesSessionConfig.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.frontiers.bff.auth.cookies - -import com.frontiers.bff.props.SessionProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.web.server.session.CookieWebSessionIdResolver - -/**********************************************************************************************************************/ -/******************************************************* CONFIGURATION ************************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/common.html#custom-cookie-in-webflux - -/** - * Configures the Session Cookie Properties - */ -@Configuration -internal class CookiesSessionConfig( - private val sessionProperties: SessionProperties -) { - - @Bean - fun cookieWebSessionIdResolver(): CookieWebSessionIdResolver { - return CookieWebSessionIdResolver().apply { - setCookieName(sessionProperties.SESSION_COOKIE_NAME) - addCookieInitializer { cookie -> - cookie.httpOnly(sessionProperties.SESSION_COOKIE_HTTP_ONLY) - cookie.secure(sessionProperties.SESSION_COOKIE_SECURE) - cookie.sameSite(sessionProperties.SESSION_COOKIE_SAME_SITE) - cookie.maxAge(sessionProperties.SESSION_COOKIE_MAX_AGE) - cookie.path(sessionProperties.SESSION_COOKIE_PATH) - cookie.domain(sessionProperties.SESSION_COOKIE_DOMAIN) - } - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/cors/CORSConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/cors/CORSConfig.kt deleted file mode 100644 index bc55d7a..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/cors/CORSConfig.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.frontiers.bff.auth.cors - -import com.frontiers.bff.props.CorsProperties -import com.frontiers.bff.props.CsrfProperties -import com.frontiers.bff.props.ServerProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.web.cors.CorsConfiguration -import org.springframework.web.cors.reactive.CorsConfigurationSource -import org.springframework.web.cors.reactive.CorsWebFilter -import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource -import org.springframework.web.reactive.config.CorsRegistry -import org.springframework.web.reactive.config.WebFluxConfigurer - -/**********************************************************************************************************************/ -/**************************************************** CORS CONFIGURATION **********************************************/ -/**********************************************************************************************************************/ - -/** - * Configures all CORS Properties - */ -@Configuration -// applies to web browser clients only - not server-to-server -internal class CORSConfig ( - private val serverProperties : ServerProperties, - private val corsProperties: CorsProperties, - private val csrfProperties: CsrfProperties -){ - - @Bean - fun corsConfigurationSource(): CorsConfigurationSource { - val source = UrlBasedCorsConfigurationSource() - val config = CorsConfiguration().apply { - // ensure this matches the Angular app URI - allowedOrigins = corsProperties.allowedOriginPatterns - allowedMethods = corsProperties.allowedMethods - allowedHeaders = corsProperties.allowedHeaders - exposedHeaders = corsProperties.exposedHeaders - maxAge = corsProperties.maxAge - allowCredentials = corsProperties.allowCredentials - } - source.registerCorsConfiguration(corsProperties.path, config) - return source - } - - @Bean - fun corsFilter(): CorsWebFilter { - return CorsWebFilter(corsConfigurationSource()) - } - - @Bean - fun corsConfig(): WebFluxConfigurer { - return object : WebFluxConfigurer { - override fun addCorsMappings(registry: CorsRegistry) { - registry.addMapping(corsProperties.path) - .allowedOrigins(serverProperties.reverseProxyUri) - .allowedMethods( - HttpMethod.GET.name(), - HttpMethod.POST.name(), - HttpMethod.PUT.name(), - HttpMethod.PATCH.name(), - HttpMethod.DELETE.name(), - HttpMethod.OPTIONS.name(), - ) - .allowedHeaders( - HttpHeaders.CONTENT_TYPE, - HttpHeaders.AUTHORIZATION, - csrfProperties.CSRF_HEADER_NAME, - ) - .exposedHeaders( - HttpHeaders.CONTENT_TYPE, - HttpHeaders.AUTHORIZATION, - csrfProperties.CSRF_HEADER_NAME, - ) - .allowCredentials(true) - } - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/csrf/CsrfProtectionMatcher.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/csrf/CsrfProtectionMatcher.kt deleted file mode 100644 index 559bc8e..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/csrf/CsrfProtectionMatcher.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.frontiers.bff.auth.csrf - -import org.springframework.context.annotation.Configuration -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher -import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/************************************************** REQUEST MATCHER ***************************************************/ -/**********************************************************************************************************************/ - -/** - * Matcher implementation is designed to apply CSRF protection only to non-GET requests. - * It will produce a match result for all requests EXCEPT FOR: GET requests - */ -@Configuration -internal class CsrfProtectionMatcher : ServerWebExchangeMatcher { - override fun matches(exchange: ServerWebExchange): Mono? { - val method = exchange.request.method.name().uppercase() - return if (method != "GET") { - ServerWebExchangeMatcher.MatchResult.match() - } else { - Mono.empty() - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/filters/csrf/CsrfWebCookieFilter.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/filters/csrf/CsrfWebCookieFilter.kt deleted file mode 100644 index d777a00..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/filters/csrf/CsrfWebCookieFilter.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.frontiers.bff.auth.filters.csrf - -import org.slf4j.LoggerFactory -import org.springframework.security.web.server.csrf.CsrfToken -import org.springframework.stereotype.Component -import org.springframework.web.server.ServerWebExchange -import org.springframework.web.server.WebFilter -import org.springframework.web.server.WebFilterChain -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/******************************************************* FILTER *******************************************************/ -/**********************************************************************************************************************/ - -// needed for SPAs according to this: -// https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa - -/* - * Ensures that the CSRF token is loaded and processed as part of the request lifecycle. - * By accessing csrfToken.token, the filter causes any deferred operations related to the token to be executed - * And then included as an exchange response cookie - */ -@Component -internal class CsrfWebCookieFilter : WebFilter { - - private val logger = LoggerFactory.getLogger(CsrfWebCookieFilter::class.java) - - override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { - return exchange.getAttributeOrDefault(CsrfToken::class.java.name, Mono.empty()) - .doOnNext { csrfToken -> - // render the token value to a cookie by causing the deferred token to be loaded - logger.info("Rendering CSRF token: ${csrfToken}") - csrfToken.token - } - .then(chain.filter(exchange)) - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/DelegatingAuthenticationSuccessHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/DelegatingAuthenticationSuccessHandler.kt deleted file mode 100644 index cedde18..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/DelegatingAuthenticationSuccessHandler.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.frontiers.bff.auth.handlers - -import com.frontiers.bff.auth.handlers.oauth2.OAuth2ServerAuthenticationSuccessHandler -import com.frontiers.bff.auth.handlers.oauth2.CustomConcurrentSessionControlSuccessHandler -import org.slf4j.LoggerFactory -import org.springframework.security.core.Authentication -import org.springframework.security.web.server.WebFilterExchange -import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -/** - * Coordinates multiple success handlers to ensure that all necessary actions are taken when authentication succeeds. - * It allows for different authentication success strategies to be applied in sequence. - */ -@Component -internal class DelegatingAuthenticationSuccessHandler( - private val primaryHandler: CustomConcurrentSessionControlSuccessHandler, - private val secondaryHandler: OAuth2ServerAuthenticationSuccessHandler -) : ServerAuthenticationSuccessHandler { - - private val logger = LoggerFactory.getLogger(DelegatingAuthenticationSuccessHandler::class.java) - - override fun onAuthenticationSuccess( - webFilterExchange: WebFilterExchange, - authentication: Authentication - ): Mono { - - logger.info("Successfully authenticated: {}", authentication.name) - - // delegate to the primary handler - return primaryHandler.onAuthenticationSuccess(webFilterExchange, authentication) - .then( - // delegate to the secondary handler - secondaryHandler.onAuthenticationSuccess(webFilterExchange, authentication) - ) - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/SessionServerLogoutHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/SessionServerLogoutHandler.kt deleted file mode 100644 index 88d0ca3..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/SessionServerLogoutHandler.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.frontiers.bff.auth.handlers - -import com.frontiers.bff.auth.sessions.SessionControl -import com.frontiers.bff.props.CsrfProperties -import com.frontiers.bff.props.SessionProperties -import org.slf4j.LoggerFactory -import org.springframework.http.HttpHeaders -import org.springframework.http.ResponseCookie -import org.springframework.security.core.Authentication -import org.springframework.security.web.server.WebFilterExchange -import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -/** - * On logout invalidates the local session (which also has other security objects: e.g. security context, authorized clients) - */ -@Component -internal class SessionServerLogoutHandler( - private val sessionControl: SessionControl, - private val sessionProperties: SessionProperties, - private val csrfProperties: CsrfProperties, -) : ServerLogoutHandler { - - private val logger = LoggerFactory.getLogger(SessionServerLogoutHandler::class.java) - -// private val indexed = ReactiveRedisSessionIndexer(redisOperations, this.namespace) - - override fun logout(exchange: WebFilterExchange, authentication: Authentication?): Mono { - return exchange.exchange.session - .flatMap { session -> - logger.info("Logging out: Invalidating User Session: ${session.id}") - - val response = exchange.exchange.response - - // invalidate and delete session - sessionControl.invalidateSession(session.id) - .then(Mono.fromRunnable { - - logger.info("Deleting Session Cookie: ${sessionProperties.SESSION_COOKIE_NAME}") - - // delete the session cookie - val sessionCookie = ResponseCookie.from(sessionProperties.SESSION_COOKIE_NAME) - .maxAge(0) - .httpOnly(sessionProperties.SESSION_COOKIE_HTTP_ONLY) - .secure(sessionProperties.SESSION_COOKIE_SECURE) - .sameSite(sessionProperties.SESSION_COOKIE_SAME_SITE) - .path(sessionProperties.SESSION_COOKIE_PATH) - .domain(sessionProperties.SESSION_COOKIE_DOMAIN) - .build() - response.headers.add( - HttpHeaders.SET_COOKIE, - sessionCookie.toString() - ) - - logger.info("Deleting Session Cookie: ${csrfProperties.CSRF_COOKIE_NAME}") - - // delete the CSRF cookie - val csrfCookie = ResponseCookie.from(csrfProperties.CSRF_COOKIE_NAME) - .maxAge(0) - .httpOnly(csrfProperties.CSRF_COOKIE_HTTP_ONLY) - .secure(csrfProperties.CSRF_COOKIE_SECURE) - .sameSite(csrfProperties.CSRF_COOKIE_SAME_SITE) - .path(csrfProperties.CSRF_COOKIE_PATH) - .domain(csrfProperties.CSRF_COOKIE_DOMAIN) - .build() - response.headers.add( - HttpHeaders.SET_COOKIE, - csrfCookie.toString() - ) - }) - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/csrf/CsrfTokenRequestHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/csrf/CsrfTokenRequestHandler.kt deleted file mode 100644 index 3983b3a..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/csrf/CsrfTokenRequestHandler.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.frontiers.bff.auth.handlers.csrf - -import com.frontiers.bff.props.CsrfProperties -import org.slf4j.LoggerFactory -import org.springframework.security.web.server.csrf.CsrfToken -import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestHandler -import org.springframework.security.web.server.csrf.XorServerCsrfTokenRequestAttributeHandler -import org.springframework.stereotype.Component -import org.springframework.util.StringUtils -import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -// needed for SPAs according to this: -// https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa - -/** - * ensures that CSRF tokens are managed appropriately depending on whether they are included in headers - * or request parameters and incorporates protection mechanisms against specific security threats like BREACH. - */ -@Component -internal class SPACsrfTokenRequestHandler( - private val csrfProperties: CsrfProperties -) : ServerCsrfTokenRequestHandler { - - private val logger = LoggerFactory.getLogger(SPACsrfTokenRequestHandler::class.java) - - private val delegate: ServerCsrfTokenRequestHandler - = XorServerCsrfTokenRequestAttributeHandler() - - override fun handle( - exchange: ServerWebExchange?, - csrfToken: Mono? - ) { - /* - * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of - * the CsrfToken when it is rendered in the response body. - */ - logger.info("Handling CSRF token: {}", csrfToken) - delegate.handle(exchange, csrfToken) - } - - override fun resolveCsrfTokenValue(exchange: ServerWebExchange, csrfToken: CsrfToken): Mono? { - /* - * If the request contains a request header, use CsrfTokenRequestAttributeHandler - * to resolve the CsrfToken. This applies when a single-page application includes - * the header value automatically, which was obtained via a cookie containing the - * raw CsrfToken. - */ - val headerToken = exchange.request.headers.getFirst(csrfProperties.CSRF_HEADER_NAME) - return if (StringUtils.hasText(headerToken)) { - logger.info("Resolving CSRF token: {}", csrfToken) - super.resolveCsrfTokenValue(exchange, csrfToken) - } else { - /* - * In all other cases (e.g. if the request contains a request parameter), use - * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies - * when a server-side rendered form includes the _csrf request parameter as a - * hidden input. - */ - logger.info("Resolving CSRF token: {}", csrfToken) - delegate.resolveCsrfTokenValue(exchange, csrfToken) - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/ConcurrentSessionControlServerAuthenticationSuccessHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/ConcurrentSessionControlServerAuthenticationSuccessHandler.kt deleted file mode 100644 index 305f6d2..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/ConcurrentSessionControlServerAuthenticationSuccessHandler.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.frontiers.bff.auth.handlers.oauth2 - -import org.slf4j.LoggerFactory -import org.springframework.security.core.Authentication -import org.springframework.security.core.session.ReactiveSessionRegistry -import org.springframework.security.web.server.WebFilterExchange -import org.springframework.security.web.server.authentication.ConcurrentSessionControlServerAuthenticationSuccessHandler -import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler -import org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler -import org.springframework.security.web.server.authentication.SessionLimit -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -/** - * Configures a session limit of 1, meaning users can only have one active session at a time. - */ -@Component -internal class CustomConcurrentSessionControlSuccessHandler( - reactiveSessionRegistry: ReactiveSessionRegistry, - maximumSessionsExceededHandler: ServerMaximumSessionsExceededHandler -) : ServerAuthenticationSuccessHandler { - - private val delegate: ConcurrentSessionControlServerAuthenticationSuccessHandler = - ConcurrentSessionControlServerAuthenticationSuccessHandler( - reactiveSessionRegistry, - maximumSessionsExceededHandler - ) - - init { - // configure the default session limit - delegate.setSessionLimit(SessionLimit.of(1)) - } - - // override method - override fun onAuthenticationSuccess( - webFilterExchange: WebFilterExchange, - authentication: Authentication - ): Mono { - - return delegate.onAuthenticationSuccess(webFilterExchange, authentication) - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ - diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerAuthenticationFailureHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerAuthenticationFailureHandler.kt deleted file mode 100644 index e82b9f9..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerAuthenticationFailureHandler.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.frontiers.bff.auth.handlers.oauth2 - -import com.frontiers.bff.auth.redirects.OAuth2ServerRedirectStrategy -import com.frontiers.bff.props.LoginProperties -import com.frontiers.bff.props.OAuth2RedirectionProperties -import org.slf4j.LoggerFactory -import org.springframework.security.core.AuthenticationException -import org.springframework.security.web.server.WebFilterExchange -import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler -import org.springframework.stereotype.Component -import org.springframework.web.util.UriComponentsBuilder -import org.springframework.web.util.UriUtils -import reactor.core.publisher.Mono -import reactor.core.scheduler.Schedulers -import java.net.URI -import java.net.URLDecoder -import java.nio.charset.StandardCharsets - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -/** - * Provides a comprehensive solution for handling authentication failures in OAuth2 scenarios, offering user - * feedback through redirection and error messages. - */ -@Component -internal class OAuth2ServerAuthenticationFailureHandler( - oauth2RedirectionProperties: OAuth2RedirectionProperties, - private val loginProperties: LoginProperties -) : ServerAuthenticationFailureHandler { - - private val logger = LoggerFactory.getLogger(OAuth2ServerAuthenticationFailureHandler::class.java) - - // construct default re-direct uri - private val defaultRedirectUri: URI = oauth2RedirectionProperties.getLoginErrorRedirectUri() - ?: URI.create("/") - - // construct the default redirect strategy - private val redirectStrategy: OAuth2ServerRedirectStrategy = OAuth2ServerRedirectStrategy( - oauth2RedirectionProperties.postAuthorizationCode, oauth2RedirectionProperties - ) - - // override onFailure function - override fun onAuthenticationFailure( - webFilterExchange: WebFilterExchange, - exception: AuthenticationException? - ): Mono? { - - logger.info("Failed authentication: {}", exception?.message) - - return webFilterExchange.exchange?.session?.flatMap { session -> - - // retrieve the post-login failure URI from session or use a default URI - val uriString = session.getAttributeOrDefault( - loginProperties.POST_AUTHENTICATION_FAILURE_URI_SESSION_ATTRIBUTE, - defaultRedirectUri.toString() - ) - - logger.info("ON FAILURE REDIRECT URI: $uriString") - - // decode the exception message - val decodedMessageMono = Mono.fromCallable { - URLDecoder.decode(exception?.message ?: "unknown error", StandardCharsets.UTF_8.name()) - }.subscribeOn(Schedulers.boundedElastic()) - - - // add query param to uri - decodedMessageMono.flatMap { decodedMessage -> - val encodedMessage = UriUtils.encodePath( - decodedMessage, StandardCharsets.UTF_8.name() - ) - - // build the URI with the properly encoded query parameter - val uriWithQueryParam = UriComponentsBuilder.fromUri(URI.create(uriString)) - .queryParam( - loginProperties.POST_AUTHENTICATION_FAILURE_CAUSE_ATTRIBUTE, - encodedMessage - ).build(true) - .toUri() - - logger.info("URI with Query Parameters: $uriWithQueryParam") - - // Apply the redirect and return Mono - redirectStrategy.sendRedirect( - webFilterExchange.exchange, - uriWithQueryParam - ) - } - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerAuthenticationSuccessHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerAuthenticationSuccessHandler.kt deleted file mode 100644 index 9a92a11..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerAuthenticationSuccessHandler.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.frontiers.bff.auth.handlers.oauth2 - -import com.frontiers.bff.auth.redirects.OAuth2ServerRedirectStrategy -import com.frontiers.bff.props.LoginProperties -import com.frontiers.bff.props.OAuth2RedirectionProperties -import org.slf4j.LoggerFactory -import org.springframework.security.core.Authentication -import org.springframework.security.web.server.WebFilterExchange -import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono -import java.net.URI - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -/** - * Provides a customizable and flexible way to handle successful OAuth2 authentication by redirecting users to - * appropriate URIs based on session data and configuration properties - */ -@Component -internal class OAuth2ServerAuthenticationSuccessHandler( - oauth2RedirectionProperties: OAuth2RedirectionProperties, - private val loginProperties: LoginProperties -) : ServerAuthenticationSuccessHandler { - - private val logger = LoggerFactory.getLogger(OAuth2ServerAuthenticationSuccessHandler::class.java) - - // construct default re-direct uri - private val defaultRedirectUri: URI = oauth2RedirectionProperties.getPostLoginRedirectUri() - ?: URI.create("/") - - // construct the post authorization redirect strategy - private val redirectStrategy: OAuth2ServerRedirectStrategy = OAuth2ServerRedirectStrategy( - oauth2RedirectionProperties.postAuthorizationCode, - oauth2RedirectionProperties - ) - - // override onSuccess function - override fun onAuthenticationSuccess( - webFilterExchange: WebFilterExchange, - authentication: Authentication - ): Mono { - - return webFilterExchange.exchange.session.flatMap { session -> - - // retrieve the post-login success URI from session or use a default URI - val uriString = session.getAttributeOrDefault( - loginProperties.POST_AUTHENTICATION_SUCCESS_URI_SESSION_ATTRIBUTE, - defaultRedirectUri.toString() - ) - - logger.info("ON SUCCESSFUL REDIRECT URI: $uriString") - - // apply the redirect and return Mono - redirectStrategy.sendRedirect( - webFilterExchange.exchange, - URI.create(uriString) - ) - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerLogoutSuccessHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerLogoutSuccessHandler.kt deleted file mode 100644 index 8b7a467..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/OAuth2ServerLogoutSuccessHandler.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.frontiers.bff.auth.handlers.oauth2 - -import com.frontiers.bff.auth.handlers.oauth2.builders.LogoutRequestUriBuilder -import com.frontiers.bff.auth.redirects.OAuth2ServerRedirectStrategy -import com.frontiers.bff.props.LogoutProperties -import com.frontiers.bff.props.OAuth2RedirectionProperties -import org.slf4j.LoggerFactory -import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.core.oidc.user.OidcUser -import org.springframework.security.web.server.WebFilterExchange -import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono -import java.net.URI - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -/** - * Determines the redirection URI based on headers, query parameters, or a default value. - * Constructs the appropriate logout URI using client registration details and ID token. - * Uses OAuth2ServerRedirectStrategy to handle the actual redirection process - */ -@Component -internal class OAuth2ServerLogoutSuccessHandler( - private val uriBuilder: LogoutRequestUriBuilder, - private val clientRegistrationRepo: ReactiveClientRegistrationRepository, - private val logoutProperties: LogoutProperties, - oauth2RedirectionProperties: OAuth2RedirectionProperties, -) : ServerLogoutSuccessHandler { - - private val logger = LoggerFactory.getLogger(OAuth2ServerLogoutSuccessHandler::class.java) - - private val defaultPostLogoutUri: String? = logoutProperties.getPostLogoutRedirectUri()?.toString() - private val redirectStrategy = OAuth2ServerRedirectStrategy( - oauth2RedirectionProperties.rpInitiatedLogout, - oauth2RedirectionProperties - ) - - override fun onLogoutSuccess(exchange: WebFilterExchange, authentication: Authentication): Mono { - - logger.info("Successfully logged out: {}", authentication.name) - - // run, if there is an authentication token - if (authentication is OAuth2AuthenticationToken) { - val oauth = authentication - - // get logout uri from - val postLogoutUri = exchange.exchange.request.headers - .getFirst(logoutProperties.POST_LOGOUT_SUCCESS_URI_HEADER) - ?: exchange.exchange.request.queryParams - .getFirst(logoutProperties.POST_LOGOUT_SUCCESS_URI_PARAM) - ?: defaultPostLogoutUri - - // perform a redirection to the constructed URI - return clientRegistrationRepo - .findByRegistrationId(oauth.authorizedClientRegistrationId) - .flatMap { client -> - val idToken = (oauth.principal as OidcUser).idToken.tokenValue - val uri = if (postLogoutUri.isNullOrBlank()) { - uriBuilder.getLogoutRequestUri(client, idToken) - } else { - uriBuilder.getLogoutRequestUri(client, idToken, URI.create(postLogoutUri)) - } - Mono.justOrEmpty(uri) - } - .flatMap { logoutUri -> - - logger.info("ON LOGOUT REDIRECT URI: $logoutUri") - - redirectStrategy.sendRedirect( - exchange.exchange, - URI.create(logoutUri) - ) - } - } - return Mono.empty() - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/PreAuthorizationCodeOAuth2ServerRedirectStrategyConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/PreAuthorizationCodeOAuth2ServerRedirectStrategyConfig.kt deleted file mode 100644 index 313b6eb..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/PreAuthorizationCodeOAuth2ServerRedirectStrategyConfig.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.frontiers.bff.auth.handlers.oauth2 - -import com.frontiers.bff.auth.redirects.OAuth2ServerRedirectStrategy -import com.frontiers.bff.props.OAuth2RedirectionProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpStatus -import org.springframework.security.web.server.ServerRedirectStrategy - -/**********************************************************************************************************************/ -/**************************************************** REDIRECTION STRATEGIES ******************************************/ -/**********************************************************************************************************************/ - -/** - * Provides a way to set up and manage the PreAuthorizationCodeOAuth2ServerRedirectStrategy bean within the - * Spring application context, enabling it to be used for any pre-authorization re-direction - */ -@Configuration -internal class PreAuthorizationCodeOAuth2ServerRedirectStrategyConfig(){ - - @Bean - // construct the redirect strategy as a function - internal fun preAuthorizationCodeOAuth2RedirectStrategy( - oauth2RedirectionProperties: OAuth2RedirectionProperties, - ): PreAuthorizationCodeServerRedirectStrategy { - return PreAuthorizationCodeOAuth2ServerRedirectStrategy( - oauth2RedirectionProperties.preAuthorizationCode, - oauth2RedirectionProperties - ) - } - - // pre-defined class - internal class PreAuthorizationCodeOAuth2ServerRedirectStrategy( - defaultStatus: HttpStatus, - oauth2RedirectionProperties: OAuth2RedirectionProperties - ) : OAuth2ServerRedirectStrategy( - defaultStatus, - oauth2RedirectionProperties - ), PreAuthorizationCodeServerRedirectStrategy -} - -/**********************************************************************************************************************/ -/***************************************************** INTERFACES *****************************************************/ -/**********************************************************************************************************************/ - -interface PreAuthorizationCodeServerRedirectStrategy : ServerRedirectStrategy - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/builders/OAuth2LogoutRequestUriBuilder.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/builders/OAuth2LogoutRequestUriBuilder.kt deleted file mode 100644 index 5e806b4..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/oauth2/builders/OAuth2LogoutRequestUriBuilder.kt +++ /dev/null @@ -1,181 +0,0 @@ -package com.frontiers.bff.auth.handlers.oauth2.builders - -import com.frontiers.bff.props.LogoutProperties -import com.frontiers.bff.props.OAuth2LogoutProperties -import com.frontiers.bff.props.OAuth2RedirectionProperties -import org.slf4j.LoggerFactory -import org.springframework.security.oauth2.client.registration.ClientRegistration -import org.springframework.stereotype.Component -import org.springframework.web.util.UriComponentsBuilder -import java.net.URI -import java.nio.charset.StandardCharsets - -/**********************************************************************************************************************/ -/****************************************************** BUILDER *******************************************************/ -/**********************************************************************************************************************/ - -/** - * logoutRequestUriBuilder: builder for RP-Initiated Logout queries, taking configuration from properties for - * OIDC providers which do not strictly comply with the spec: logout URI not provided by OIDC conf or non-standard - * parameter names (Auth0 and Cognito are samples of such OPs) - * Overall, gets logout request URI (which may call the logout endpoint of the authentication provider) - * So, ensures that the logout request is properly constructed and redirects the user to the appropriate URI - * based on the OAuth2 and application-specific configurations. - */ - -@Component -internal class OAuth2LogoutRequestUriBuilder( - private val oauth2RedirectionProperties: OAuth2RedirectionProperties, - private val logoutProperties: LogoutProperties -) : LogoutRequestUriBuilder { - - private val logger = LoggerFactory.getLogger(OAuth2LogoutRequestUriBuilder::class.java) - - companion object { - private const val OIDC_RP_INITIATED_LOGOUT_CONFIGURATION_ENTRY = "end_session_endpoint" - private const val OIDC_RP_INITIATED_LOGOUT_CLIENT_ID_REQUEST_PARAM = "client_id" - private const val OIDC_RP_INITIATED_LOGOUT_ID_TOKEN_HINT_REQUEST_PARAM = "id_token_hint" - private const val OIDC_RP_INITIATED_LOGOUT_POST_LOGOUT_URI_REQUEST_PARAM = "post_logout_redirect_uri" - } - - /*************************/ - /* MAIN FUNCTIONS */ - /*************************/ - - // if postLogout URI call this function - override fun getLogoutRequestUri( - clientRegistration: ClientRegistration, - idToken: String, - postLogoutUri: URI? - ): String? { - - logger.info("Getting logout request URI") - - // get client registration logout properties (if they exist) from custom OAuth2 Logout Hashmap - val logoutProps = oauth2RedirectionProperties.getLogoutProperties(clientRegistration.registrationId) - - // if logout properties exist and rp-initialied logout is not enabled, - // return postLogout URI (don't go to logout endpoint) - if (logoutProps?.rpInitiatedLogoutEnabled == false) { - return postLogoutUri?.toString()?.takeIf { it.isNotBlank() } - } - - // otherwise, go and get the logout endpoint on the auth server (i.e. end session endpoint): - val logoutEndpointUri = getLogoutEndpointUri(logoutProps, clientRegistration) - ?: throw MisconfiguredProviderException(clientRegistration.registrationId) - - // create builder from logout endpoint - val builder = UriComponentsBuilder.fromUri(logoutEndpointUri) - - // add token hint request parameter, with idToken, to builder - getIdTokenHintRequestParam(logoutProps).let { idTokenHintParamName -> - if (idToken.isNotBlank()) { - builder.queryParam(idTokenHintParamName, idToken) - } - } - - // add client id request parameter, with clientId, to builder - getClientIdRequestParam(logoutProps).let { clientIdParamName -> - clientRegistration.clientId?.takeIf { it.isNotBlank() }?.let { clientId -> - builder.queryParam(clientIdParamName, clientId) - } - } - - // add post logout request parameter, with postLogoutURI, to builder - getPostLogoutUriRequestParam(logoutProps).let { postLogoutUriParamName -> - postLogoutUri?.toString()?.takeIf { it.isNotBlank() }?.let { uri -> - builder.queryParam(postLogoutUriParamName, uri) - } - } - - return builder.encode(StandardCharsets.UTF_8).build().toUriString() - } - - // if no postLogout URI exists then still call the main function above! - override fun getLogoutRequestUri( - clientRegistration: ClientRegistration, - idToken: String - ): String? { - return getLogoutRequestUri( - clientRegistration, - idToken, - logoutProperties.getPostLogoutRedirectUri() - ) - } - - /*************************/ - /* HELPER FUNCTIONS */ - /*************************/ - // get LogoutEndPoint URI - fun getLogoutEndpointUri( - logoutProps: OAuth2LogoutProperties?, - clientRegistration: ClientRegistration - ): URI? { - // if logout properties exist & rp-initiated logout is enabled, return OAuth2 property logout endpoint - return logoutProps?.let { - if (it.rpInitiatedLogoutEnabled) { - it.uri - } else { - null - } - } - // otherwise, if logout properties do not exist - // return standard OIDC logout endpoint (as defined in client registrations) - ?: run { - val oidcConfig = clientRegistration.providerDetails.configurationMetadata - oidcConfig[OIDC_RP_INITIATED_LOGOUT_CONFIGURATION_ENTRY]?.toString()?.let { URI.create(it) } - } - } - - // get logoutTokenHint request parameter - fun getIdTokenHintRequestParam( - logoutProps: OAuth2LogoutProperties? - ): String { - return logoutProps?.idTokenHintRequestParam ?: OIDC_RP_INITIATED_LOGOUT_ID_TOKEN_HINT_REQUEST_PARAM - } - - // get clientID request parameter - fun getClientIdRequestParam( - logoutProps: OAuth2LogoutProperties?): String { - return logoutProps?.clientIdRequestParam ?: OIDC_RP_INITIATED_LOGOUT_CLIENT_ID_REQUEST_PARAM - } - - // get postLogout URI request param - fun getPostLogoutUriRequestParam( - logoutProps: OAuth2LogoutProperties? - ): String { - return logoutProps?.postLogoutUriRequestParam ?: OIDC_RP_INITIATED_LOGOUT_POST_LOGOUT_URI_REQUEST_PARAM - } - - // misconfigured provider exception - internal class MisconfiguredProviderException(clientRegistrationId: String) : RuntimeException( - "OAuth2 client registration for $clientRegistrationId RP-Initiated Logout is misconfigured: it is neither OIDC compliant nor defined in spring-addons properties" - ) { - companion object { - private const val serialVersionUID = -6023478025541369262L - } - } - -} - -/**********************************************************************************************************************/ -/***************************************************** INTERFACES *****************************************************/ -/**********************************************************************************************************************/ - -internal interface LogoutRequestUriBuilder { - - fun getLogoutRequestUri( - clientRegistration: ClientRegistration, - idToken: String - ): String? - - fun getLogoutRequestUri( - clientRegistration: ClientRegistration, - idToken: String, - postLogoutUri: URI? - ): String? -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/sessions/ServerMaximumSessionsExceededHandler.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/sessions/ServerMaximumSessionsExceededHandler.kt deleted file mode 100644 index fbc73ae..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/handlers/sessions/ServerMaximumSessionsExceededHandler.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.frontiers.bff.auth.handlers.sessions - -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler -import org.springframework.security.web.server.authentication.MaximumSessionsContext -import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler -import org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler -import org.springframework.stereotype.Component -import org.springframework.web.server.adapter.WebHttpHandlerBuilder -import org.springframework.web.server.session.DefaultWebSessionManager -import org.springframework.web.server.session.WebSessionManager -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/****************************************************** HANDLER *******************************************************/ -/**********************************************************************************************************************/ - -//* FOR WHEN MAXIMUM SESSIONS ARE EXCEEDED *// - -/** - * Customizes the behavior when the maximum number of concurrent sessions is exceeded. - * It offers two strategies based on the preventLogin flag. - * If preventLogin is true, it prevents additional logins by using PreventLoginServerMaximumSessionsExceededHandler. - * If preventLogin is false, it invalidates the least used sessions to allow new logins by using - * InvalidateLeastUsedServerMaximumSessionsExceededHandler - */ -@Component -internal class CustomMaximumSessionsExceededHandler( - @Qualifier(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME) - private val webSessionManager: WebSessionManager -) : ServerMaximumSessionsExceededHandler { - - private val preventLogin = true // set accordingly - - override fun handle(context: MaximumSessionsContext): Mono { - // choose the implementation based on the flag - return if (preventLogin) { - PreventLoginServerMaximumSessionsExceededHandler().handle(context) - } else { - val sessionStore = (webSessionManager as DefaultWebSessionManager).sessionStore - InvalidateLeastUsedServerMaximumSessionsExceededHandler(sessionStore).handle(context) - } - } -} -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/managers/OAuth2AuthorizedManagerConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/managers/OAuth2AuthorizedManagerConfig.kt deleted file mode 100644 index b26f289..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/managers/OAuth2AuthorizedManagerConfig.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.frontiers.bff.auth.managers - -import com.frontiers.bff.auth.repositories.authclients.RedisServerOAuth2AuthorizedClientRepository -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager - -/**********************************************************************************************************************/ -/********************************************* AUTHORIZED CLIENT MANAGER **********************************************/ -/**********************************************************************************************************************/ - -/** - * Manages the state of authorized clients, including obtaining, refreshing, and storing access tokens - * Configures and provides a ReactiveOAuth2AuthorizedClientManager bean. This bean manages OAuth2 clients in a - * reactive Spring application, handling client authorization and token management. - * - * ReactiveClientRegistrationRepository: Provides OAuth2 client registration details. - * RedisServerOAuth2AuthorizedClientRepository: Manages authorized OAuth2 clients. - * ReactiveOAuth2AuthorizedClientProvider: Handles token acquisition and refresh. - */ -@Configuration -internal class OAuth2AuthorizedManagerConfig { - - private val logger: Logger = LoggerFactory.getLogger(OAuth2AuthorizedManagerConfig::class.java) - - @Bean - fun reactiveAuthorizedClientManager( - reactiveClientRegistrationRepository: ReactiveClientRegistrationRepository, - redisServerOAuth2AuthorizedClientRepository: RedisServerOAuth2AuthorizedClientRepository, - reactiveAuthorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider, - ): ReactiveOAuth2AuthorizedClientManager { - - logger.info("Creating reactiveAuthorizedClientManager instance") - - // create the AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager instance - val reactiveAuthorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager( - reactiveClientRegistrationRepository, - redisServerOAuth2AuthorizedClientRepository - ) - - logger.info("Setting reactiveAuthorizedClientManager with reativeAuthorizedClientProvider ") - - // set the authorized client provider to the manager - reactiveAuthorizedClientManager.setAuthorizedClientProvider(reactiveAuthorizedClientProvider) - - return reactiveAuthorizedClientManager - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/GrantedAuthoritiesMapperConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/GrantedAuthoritiesMapperConfig.kt deleted file mode 100644 index 5755c1f..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/GrantedAuthoritiesMapperConfig.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.frontiers.bff.auth.mappers - -import com.frontiers.bff.auth.mappers.converters.ClaimSetAuthoritiesConverter -import com.frontiers.bff.auth.mappers.converters.ConfigurableClaimSetAuthoritiesConverter -import com.frontiers.bff.auth.mappers.converters.OpenIdProviderPropertiesResolver -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.security.core.GrantedAuthority -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority - -/**********************************************************************************************************************/ -/************************************************* AUTHORITY MAPPER ***************************************************/ -/**********************************************************************************************************************/ - -/* - * Params: authoritiesConverter – the authorities converter to use (by default ConfigurableClaimSetAuthoritiesConverter) - * Returns: GrantedAuthoritiesMapper using the authorities converter in the context - * Helps with mapping custom user authorities from ID Token claims or attributes, to Spring SimpleGrantedAuthority - * that are then stored in a security context object - * Note: only gets custom claims from the ID Token (or attributes) - those in Access Token stay in that token! - */ -@Configuration -internal class GrantedAuthoritiesMapperConfig { - - private val logger = LoggerFactory.getLogger(GrantedAuthoritiesMapperConfig::class.java) - - @Bean - fun grantedAuthoritiesMapper( - authoritiesConverter: ClaimSetAuthoritiesConverter, - ): GrantedAuthoritiesMapper { - - return GrantedAuthoritiesMapper { authorities -> - val mappedAuthorities = mutableSetOf() - - authorities.forEach { authority -> - logger.info("PROCESSING AUTHORITY: $authority") - - when (authority) { - is OidcUserAuthority -> { - val idTokenClaims = authority.idToken.claims - logger.info("OidcUserAuthority ID Token Claims: $idTokenClaims") - - val convertedAuthorities = authoritiesConverter.convert(idTokenClaims) ?: emptyList() - logger.info("Converted authorities: $convertedAuthorities") - mappedAuthorities.addAll(convertedAuthorities) - } - is OAuth2UserAuthority -> { - val attributes = authority.attributes - logger.info("OAuth2UserAuthority attributes: $attributes") - - val convertedAuthorities = authoritiesConverter.convert(attributes) ?: emptyList() - logger.info("Converted authorities: $convertedAuthorities") - mappedAuthorities.addAll(convertedAuthorities) - } - } - } - - logger.info("Final mapped authorities: $mappedAuthorities") - mappedAuthorities - } - } - - /* - * Retrieves granted authorities from the Jwt (from its private claims or with the help of an external service) - * and converts them into a collection of GrantedAuthorities - * Note: The term "provider" underscores that this resolver is supplying the properties required for - * further processing, such as mapping claims to authorities in an authentication or authorization context. - */ - @Bean - fun authoritiesConverter( - authoritiesMappingPropertiesProvider: OpenIdProviderPropertiesResolver - ): ClaimSetAuthoritiesConverter { - logger.info("Initializing authorities converter with properties OpenId provider properties resolver: $authoritiesMappingPropertiesProvider") - return ConfigurableClaimSetAuthoritiesConverter(authoritiesMappingPropertiesProvider) - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/ClaimSetAuthoritiesConverter.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/ClaimSetAuthoritiesConverter.kt deleted file mode 100644 index 7ec12ca..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/ClaimSetAuthoritiesConverter.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.frontiers.bff.auth.mappers.converters - -import org.springframework.core.convert.converter.Converter -import org.springframework.security.core.GrantedAuthority - -/**********************************************************************************************************************/ -/***************************************************** INTERFACE ******************************************************/ -/**********************************************************************************************************************/ - -internal interface ClaimSetAuthoritiesConverter : Converter, Collection> { -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/ConfigurableClaimSetAuthoritiesConverter.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/ConfigurableClaimSetAuthoritiesConverter.kt deleted file mode 100644 index 1a333ca..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/ConfigurableClaimSetAuthoritiesConverter.kt +++ /dev/null @@ -1,106 +0,0 @@ -package com.frontiers.bff.auth.mappers.converters - -import com.frontiers.bff.props.OidcProviderProperties.SimpleAuthoritiesMappingProperties -import com.frontiers.bff.props.OidcProviderProperties.SimpleAuthoritiesMappingProperties.Case -import com.frontiers.bff.props.OidcProviderProperties.SimpleAuthoritiesMappingProperties.Case.* -import com.jayway.jsonpath.JsonPath -import com.jayway.jsonpath.PathNotFoundException -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpStatus -import org.springframework.security.core.GrantedAuthority -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.util.StringUtils -import org.springframework.web.bind.annotation.ResponseStatus - -/**********************************************************************************************************************/ -/*********************************************** AUTHORITIES CONVERTER ************************************************/ -/**********************************************************************************************************************/ - -/* - * A converter that takes JWT claims and converts defined ones into Spring Authorities (for the Authentication Object) - */ -@Configuration -internal class ConfigurableClaimSetAuthoritiesConverter ( - private val opPropertiesResolver: OpenIdProviderPropertiesResolver -) : ClaimSetAuthoritiesConverter { - - private val logger = LoggerFactory.getLogger(ConfigurableClaimSetAuthoritiesConverter::class.java) - - // find claims and convert them into authorities - override fun convert(source: Map): Collection { - val opProperties = opPropertiesResolver.resolve(source) - .orElseThrow { NotAConfiguredOpenidProviderException(source) } - - logger.info("Resolved OpenID provider properties: $opProperties") - - val authorities = opProperties.authorities - .flatMap { authoritiesMappingProps -> getAuthorities(source, authoritiesMappingProps) } - .map { role -> SimpleGrantedAuthority(role) } - - return authorities - } - - // get authorities, from relevant claim(s), and return as a list of (formatted) strings - private fun getAuthorities( - claims: Map, - props: SimpleAuthoritiesMappingProperties - ): List { - - val extractedClaims = getClaims(claims, props.path) - - logger.info("Extracted claims for path {}: {}", props.path, extractedClaims) - - return extractedClaims - .flatMap { claim -> claim.split(",").flatMap { it.split(" ") } } - .filter { StringUtils.hasText(it) } - .map { it.trim() } - .map { processCase(it, props.case) } - .map { "${props.prefix}$it" } - } - - // convert to the correct case - private fun processCase(role: String, case: Case): String { - return when (case) { - UPPER -> role.uppercase() - LOWER -> role.lowercase() - else -> role - } - } - - // get claims from claims json (i.e. from the Map) - private fun getClaims(claims: Map, path: String): List { - - logger.info("Attempting to extract claims from path: {}", path) - - return try { - when (val res = JsonPath.read(claims, path)) { - is String -> listOf(res) - is List<*> -> when { - res.isEmpty() -> emptyList() - res[0] is String -> res.filterIsInstance() - res[0] is List<*> -> res.flatMap { (it as? List<*>)?.filterIsInstance() ?: emptyList() } - else -> emptyList() - } - else -> emptyList() - } - } catch (e: PathNotFoundException) { - logger.error("Path not found: {}. Exception: {}", path, e.message) - emptyList() - } - } - -} - -@ResponseStatus(HttpStatus.UNAUTHORIZED) -internal class NotAConfiguredOpenidProviderException(claims: Map) : RuntimeException( - "Could not resolve OpenID Provider configuration properties from a JWT with ${claims["iss"]} as issuer and ${claims["aud"]} as audience" -) { - companion object { - private const val serialVersionUID = 5189849969622154264L - } -} - -/**********************************************************************************************************************/ -/*************************************************** END OF KOTLIN ****************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/OpenIdPropertiesResolversConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/OpenIdPropertiesResolversConfig.kt deleted file mode 100644 index f66f167..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/OpenIdPropertiesResolversConfig.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.frontiers.bff.auth.mappers.converters - -import com.frontiers.bff.props.OidcProviderProperties -import com.frontiers.bff.props.OidcProviderProperties.OpenidProviderProperties -import org.slf4j.LoggerFactory - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Primary -import org.springframework.stereotype.Component -import java.util.* - -/**********************************************************************************************************************/ -/************************************************ PROPERTIES RESOLVERS ************************************************/ -/**********************************************************************************************************************/ - -/* - * Returns the ByIssuerOpenidProviderPropertiesResolver with a list of oidcProviderProperties passed to it - * Note: The term "resolve" aligns with the functionality of identifying and returning specific properties - * based on some input. - */ -@Configuration -internal class OpenIdPropertiesResolversConfig { - - private val logger = LoggerFactory.getLogger(OpenIdPropertiesResolversConfig::class.java) - - @Bean - @Primary - fun openidProviderPropertiesResolver( - oidcProviderProperties: OidcProviderProperties - ): OpenIdProviderPropertiesResolver { - logger.info("Building default OpenidProviderPropertiesResolver with the following OpenID provider properties list:") - - oidcProviderProperties.openidProviderPropertiesList.forEach { providerProperties -> - logger.info("Found pre-configured OpenIdProviderProperties: iss={}, jwkSetUri={}, aud={}, authorities={}, usernameClaim={}", - providerProperties.iss, - providerProperties.jwkSetUri, - providerProperties.aud, - providerProperties.authorities, - providerProperties.usernameClaim - ) - } - return ByIssuerOpenidProviderPropertiesResolver(oidcProviderProperties) - } -} - -/* - * ByIssuerOpenidProviderPropertiesResolver finds the appropriate OpenID provider properties based on the - * issuer claim in a token. - * Note: The term "resolve" aligns with the functionality of identifying and returning specific properties - * based on some input. - */ -@Component -internal class ByIssuerOpenidProviderPropertiesResolver( - private val oidcProviderProperties: OidcProviderProperties -) : OpenIdProviderPropertiesResolver { - - private val logger = LoggerFactory.getLogger(ByIssuerOpenidProviderPropertiesResolver::class.java) - - override fun resolve(claimSet: Map): Optional { - val iss = claimSet["iss"]?.toString() - - logger.info("Resolving OpenID provider properties for issuer: {}", iss) - - val resolvedProperties = oidcProviderProperties.openidProviderPropertiesList - .find { it.iss?.toString() == iss } - .let { Optional.ofNullable(it) } - - if (resolvedProperties.isPresent) { - logger.info("Found matching OpenIdProviderProperties for issuer: {}", iss) - } else { - logger.info("No matching OpenIdProviderProperties found for issuer: {}", iss) - } - - return resolvedProperties - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/OpenIdProviderPropertiesResolver.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/OpenIdProviderPropertiesResolver.kt deleted file mode 100644 index 85329dc..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/mappers/converters/OpenIdProviderPropertiesResolver.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.frontiers.bff.auth.mappers.converters - -import com.frontiers.bff.props.OidcProviderProperties.OpenidProviderProperties -import java.util.* - -/**********************************************************************************************************************/ -/***************************************************** INTERFACE ******************************************************/ -/**********************************************************************************************************************/ - -/* - * Resolves OpenID Provider configuration properties from OAuth2 / OpenID claims - * (decoded from a JWT, introspected from an opaque token or retrieved from userinfo endpoint) - */ -internal interface OpenIdProviderPropertiesResolver { - fun resolve(claimSet: Map): Optional -} -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/providers/OAuth2AuthorizedClientProviderConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/providers/OAuth2AuthorizedClientProviderConfig.kt deleted file mode 100644 index e3af927..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/providers/OAuth2AuthorizedClientProviderConfig.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.frontiers.bff.auth.providers - -import com.frontiers.bff.props.RequestParameterProperties -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository - -/**********************************************************************************************************************/ -/********************************************* AUTHORIZED CLIENT PROVIDER *********************************************/ -/**********************************************************************************************************************/ - -/** - * Provides a mechanism to obtain and refresh OAuth 2.0 access tokens. - * ReactiveOAuth2AuthorizedClientProvider is responsible for managing OAuth2 tokens, which includes obtaining - * access tokens, refreshing them, and handling token expiration. - */ -@Configuration -internal class OAuth2AuthorizedClientProviderConfig { - - @Bean - fun reativeAuthorizedClientProvider( - requestParameterProperties: RequestParameterProperties, - reactiveClientRegistrationRepository: ReactiveClientRegistrationRepository - ): ReactiveOAuth2AuthorizedClientProvider { - - val providersMap = PerRegistrationReactiveOAuth2AuthorizedClientProvider( - reactiveClientRegistrationRepository, - requestParameterProperties, - emptyMap() - ) - - return providersMap - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/providers/PerRegistrationReactiveOAuth2AuthorizedClientProvider.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/providers/PerRegistrationReactiveOAuth2AuthorizedClientProvider.kt deleted file mode 100644 index 456302d..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/providers/PerRegistrationReactiveOAuth2AuthorizedClientProvider.kt +++ /dev/null @@ -1,165 +0,0 @@ -package com.frontiers.bff.auth.providers - -import com.frontiers.bff.props.RequestParameterProperties -import org.slf4j.LoggerFactory -import org.springframework.security.oauth2.client.* -import org.springframework.security.oauth2.client.endpoint.WebClientReactiveClientCredentialsTokenResponseClient -import org.springframework.security.oauth2.client.endpoint.WebClientReactiveRefreshTokenTokenResponseClient -import org.springframework.security.oauth2.client.registration.ClientRegistration -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.core.AuthorizationGrantType -import reactor.core.publisher.Mono -import java.util.concurrent.ConcurrentHashMap - -/**********************************************************************************************************************/ -/********************************************* AUTHORIZED CLIENT PROVIDER **********************************************/ -/**********************************************************************************************************************/ - -/* -* An alternative ReactiveOAuth2AuthorizedClientProvider to DelegatingReactiveOAuth2AuthorizedClientProvider keeping -* a different provider for each client registration. This allows one to define for each, a set of extra parameters -* to add to token requests. -*/ - -/** - * Manages OAuth2 authorization on a per-client registration basis. It dynamically creates and manages providers for - * each client registration ID, handling different OAuth2 grant types (e.g., authorization code, client credentials). - */ - -internal class PerRegistrationReactiveOAuth2AuthorizedClientProvider( - clientRegistrationRepository: ReactiveClientRegistrationRepository, - private val requestParameterProperties: RequestParameterProperties, - private val customProvidersByRegistrationId: Map>, - - ) : ReactiveOAuth2AuthorizedClientProvider { - - private val logger = LoggerFactory.getLogger(PerRegistrationReactiveOAuth2AuthorizedClientProvider::class.java) - private val providersByRegistrationId = ConcurrentHashMap() - - // populate providersByRegistrationId map based on registration respository passed in from class constructor - init { - (clientRegistrationRepository as? InMemoryReactiveClientRegistrationRepository)?.toList()?.forEach { reg -> - // create providers for the current registration - val delegate = DelegatingReactiveOAuth2AuthorizedClientProvider( - getProvidersFor(reg, requestParameterProperties) - ) - - // log information about the registration and the provider created - logger.info("Created provider for client registration with ID: ${reg.registrationId}, Client Name: ${reg.clientName}") - - // store the provider in the map by registration ID - providersByRegistrationId[reg.registrationId] = delegate - } - } - - - // override authorize function - override fun authorize(context: OAuth2AuthorizationContext?): Mono { - context ?: return Mono.empty() - - // get current client registration from context - val registration = context.clientRegistration - - // if providersByRegistrationId map DOES NOT have provider for given registration id, then assign one - if (!providersByRegistrationId.containsKey(registration.registrationId)) { - val delegate = DelegatingReactiveOAuth2AuthorizedClientProvider( - getProvidersFor(registration, requestParameterProperties) - ) - providersByRegistrationId[registration.registrationId] = delegate - } - - // run the authorization function of the provider - return providersByRegistrationId[registration.registrationId]!!.authorize(context) - } - - - // get provider for the particular client registration - private fun getProvidersFor( - registration: ClientRegistration, - requestParameterProperties: RequestParameterProperties - ): List { - - // get providers for the given client registration id (as passed in from the class constructor) - val providers = ArrayList(customProvidersByRegistrationId[registration.registrationId] ?: listOf()) - - // if grant type is authorisation code, add authorization code provider - // also add refresh token provider (if 'offline_access' scope is provided) - if (AuthorizationGrantType.AUTHORIZATION_CODE == registration.authorizationGrantType) { - providers.add(AuthorizationCodeReactiveOAuth2AuthorizedClientProvider()) - - if (registration.scopes.contains("offline_access")) { - providers.add( - createRefreshTokenProvider(registration, requestParameterProperties) - ) - } - // otherwise, if grant type is client credentials, add client credentials provider - } else if (AuthorizationGrantType.CLIENT_CREDENTIALS == registration.authorizationGrantType) { - providers.add( - createClientCredentialsProvider(registration, requestParameterProperties) - ) - } - return providers - } - - - // create a client credentials provider - private fun createClientCredentialsProvider( - registration: ClientRegistration, - requestParameterProperties: RequestParameterProperties - ): ClientCredentialsReactiveOAuth2AuthorizedClientProvider { - - // create provider and get extraParameters - val provider = ClientCredentialsReactiveOAuth2AuthorizedClientProvider() - val extraParameters = requestParameterProperties.getExtraTokenParameters(registration.registrationId) - - // return provider early if no extraParameters - if (extraParameters.isEmpty()) { - return provider - } - - // create response client, and add extra parameters to it - val responseClient = WebClientReactiveClientCredentialsTokenResponseClient() - responseClient.addParametersConverter { extraParameters } - - // add response client into provider - provider.setAccessTokenResponseClient(responseClient) - - // return provider - return provider - } - - - // create a refresh token provider - private fun createRefreshTokenProvider( - registration: ClientRegistration, - requestParameterProperties: RequestParameterProperties - ): RefreshTokenReactiveOAuth2AuthorizedClientProvider { - - // create provider and get extraParameters - val provider = RefreshTokenReactiveOAuth2AuthorizedClientProvider() - val extraParameters = requestParameterProperties.getExtraTokenParameters(registration.registrationId) - - // return provider early if no extraParameters - if (extraParameters.isEmpty()) { - return provider - } - - println("TOKEN PARAMETERS") - logger.info(extraParameters.entries.joinToString(", ") { (key, value) -> "$key=$value" }) - - // create response client, and add extra parameters to it - val responseClient = WebClientReactiveRefreshTokenTokenResponseClient() - responseClient.addParametersConverter { extraParameters } - - // add response client into provider - provider.setAccessTokenResponseClient(responseClient) - - // return provider - return provider - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/redirects/OAuth2ServerRedirectStrategy.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/redirects/OAuth2ServerRedirectStrategy.kt deleted file mode 100644 index 0774ecb..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/redirects/OAuth2ServerRedirectStrategy.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.frontiers.bff.auth.redirects - -import com.frontiers.bff.props.OAuth2RedirectionProperties -import org.slf4j.LoggerFactory -import org.springframework.http.HttpStatus -import org.springframework.http.server.reactive.ServerHttpResponse -import org.springframework.security.web.server.ServerRedirectStrategy -import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Mono -import java.net.URI -import java.util.* - -/**********************************************************************************************************************/ -/**************************************************** REDIRECTION STRATEGIES ******************************************/ -/**********************************************************************************************************************/ - -/** - * A redirect strategy that might not actually redirect: the HTTP status is taken from - * OAuth2RedirectionProperties. User-agents will auto redirect only if the status is in 3xx range. - * If set to 2xx range (like OK, ACCEPTED, NO_CONTENT, ...), this gives single page and mobile applications a chance - * to intercept the redirection and choose to follow the redirection (or not), with which agent, and to potentially - * clear some headers - so a single page or mobile application can handle the redirection as it wishes - * (change the user-agent, clear some headers, ...). - */ - -/** - * Provides a flexible way to handle HTTP redirects by configuring the response status code - * and redirect location dynamically. - */ -internal open class OAuth2ServerRedirectStrategy( - private var defaultStatus: HttpStatus, - private val oauth2ServerRedirectionProperties: OAuth2RedirectionProperties -) : ServerRedirectStrategy { - - private val logger = LoggerFactory.getLogger(OAuth2ServerRedirectStrategy::class.java) - - override fun sendRedirect(exchange: ServerWebExchange, location: URI): Mono { - logger.info("RUNNING SERVER REDIRECT STRATEGY: $defaultStatus") - return Mono.fromRunnable { - val response: ServerHttpResponse = exchange.response - // get response status from the following header, otherwise use the passed in default - val status = Optional - .ofNullable( - exchange.request.headers[oauth2ServerRedirectionProperties.RESPONSE_STATUS_HEADER] - ) - .flatMap { it.stream().findAny() } - .filter { it.isNotBlank() } - .map { statusStr -> - try { - HttpStatus.valueOf(statusStr.toInt()) - } catch (e: NumberFormatException) { - HttpStatus.valueOf(statusStr.uppercase()) - } - } - .orElse(defaultStatus) - - response.statusCode = status - response.headers.location = location - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/authclients/RedisServerOAuth2AuthorizedClientRepository.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/authclients/RedisServerOAuth2AuthorizedClientRepository.kt deleted file mode 100644 index 87530db..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/authclients/RedisServerOAuth2AuthorizedClientRepository.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.frontiers.bff.auth.repositories.authclients - -import com.frontiers.bff.auth.serialisers.RedisSerialiserConfig -import org.slf4j.LoggerFactory -import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository -import org.springframework.stereotype.Repository -import org.springframework.util.Assert -import org.springframework.web.server.ServerWebExchange -import org.springframework.web.server.WebSession -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/***************************************************** REPOSITORY *****************************************************/ -/**********************************************************************************************************************/ - -/** - * Enables the use of Redis as a persistent storage solution for Authorized Client objects - */ -@Repository -internal class RedisServerOAuth2AuthorizedClientRepository( - private val redisSerialiserConfig: RedisSerialiserConfig -) : ServerOAuth2AuthorizedClientRepository { - - private val logger = LoggerFactory.getLogger(RedisServerOAuth2AuthorizedClientRepository::class.java) - private val springAuthorizedClientAttrName: String = "AUTHORIZED_CLIENTS" - - override fun loadAuthorizedClient( - clientRegistrationId: String, - principal: Authentication, - exchange: ServerWebExchange - ): Mono { - logger.info("LOADING AUTHORIZED CLIENT - REPOSITORY") - logger.info("Loading authorized client for clientRegistrationId: ${clientRegistrationId}") - Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty") - Assert.notNull(exchange, "exchange cannot be null") - - return exchange.session - .mapNotNull { getAuthorizedClients(it) } - .flatMap { clients -> - @Suppress("UNCHECKED_CAST") - val client = clients?.get(clientRegistrationId) as? T - if (client == null) { - logger.warn("No authorized client found for clientRegistrationId: $clientRegistrationId") - } - Mono.justOrEmpty(client) - } - } - - override fun saveAuthorizedClient( - authorizedClient: OAuth2AuthorizedClient, - principal: Authentication, - exchange: ServerWebExchange - ): Mono { - logger.info("SAVING AUTHORIZED CLIENT - REPOSITORY") - logger.info("Saving authorized client for clientRegistrationId: ${authorizedClient.clientRegistration.registrationId}") - Assert.notNull(authorizedClient, "authorizedClient cannot be null") - Assert.notNull(exchange, "exchange cannot be null") - - return exchange.session - .doOnSuccess { session -> - val authorizedClients = getAuthorizedClients(session) ?: mutableMapOf() - authorizedClients[authorizedClient.clientRegistration.registrationId] = authorizedClient - session.attributes[springAuthorizedClientAttrName] = authorizedClients - } - .then(Mono.empty()) - } - - override fun removeAuthorizedClient( - clientRegistrationId: String, - principal: Authentication, - exchange: ServerWebExchange - ): Mono { - logger.info("REMOVING AUTHORIZED CLIENT - REPOSITORY") - logger.info("Removing authorized client for clientRegistrationId: $clientRegistrationId") - Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty") - Assert.notNull(exchange, "exchange cannot be null") - - return exchange.session - .doOnSuccess { session -> - val authorizedClients = getAuthorizedClients(session) - authorizedClients?.remove(clientRegistrationId) - if (authorizedClients?.isEmpty() == true) { - session.attributes.remove(springAuthorizedClientAttrName) - } else { - session.attributes[springAuthorizedClientAttrName] = authorizedClients - } - } - .then(Mono.empty()) - } - - // Helper function to get authorized clients - private fun getAuthorizedClients(session: WebSession): MutableMap? { - Assert.notNull(session, "session cannot be null") - - val authorizedClientAttr = session.getAttribute>>(springAuthorizedClientAttrName) - val authorizedClients: MutableMap = mutableMapOf() - if (authorizedClientAttr != null) { - try { - // iterate over each entry in the map and deserialize it to OAuth2AuthorizedClient - for ((key, value) in authorizedClientAttr) { - val authorizedClient = redisSerialiserConfig.redisObjectMapper() - .convertValue(value, OAuth2AuthorizedClient::class.java) - logger.info("Successfully deserialized OAuth2AuthorizedClient for key: $key") - authorizedClients[key] = authorizedClient - } - return authorizedClients - } catch (e: Exception) { - logger.error("Error deserializing OAuth2AuthorizedClient: ${e.message}", e) - return null - } - } else { - logger.warn("No AuthorizedClients Map found in WebSession") - return null - } - - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/authrequests/RedisAuthorizationRequestRepository.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/authrequests/RedisAuthorizationRequestRepository.kt deleted file mode 100644 index d72537a..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/authrequests/RedisAuthorizationRequestRepository.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.frontiers.bff.auth.repositories.authrequests - -import com.frontiers.bff.auth.repositories.securitycontext.RedisSecurityContextRepository -import com.frontiers.bff.auth.serialisers.RedisSerialiserConfig -import org.slf4j.LoggerFactory -import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames -import org.springframework.stereotype.Repository -import org.springframework.util.Assert -import org.springframework.web.server.ServerWebExchange -import org.springframework.web.server.WebSession -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/***************************************************** REPOSITORY *****************************************************/ -/**********************************************************************************************************************/ - -/** - * Enables the use of Redis as a persistent storage solution for OAuth2 authorization requests - */ -@Repository -internal class RedisAuthorizationRequestRepository( - private val redisSerialiserConfig: RedisSerialiserConfig -) : ServerAuthorizationRequestRepository { - - private val logger = LoggerFactory.getLogger(RedisSecurityContextRepository::class.java) - private val springAuthorizationRequestAttrName: String = "AUTHORIZATION_REQUEST" - - override fun saveAuthorizationRequest( - authorizationRequest: OAuth2AuthorizationRequest?, - exchange: ServerWebExchange - ): Mono { - logger.info("SAVING AUTHORIZATION REQUEST") - - return exchange.session - .doOnNext { session -> - if (authorizationRequest?.state == null) { - logger.info("Authorization Request State cannot be empty") - } else { - session.attributes[springAuthorizationRequestAttrName] = authorizationRequest - logger.info("Authorization Request state: ${authorizationRequest.state}") - logger.info("Saved Authorization Request $authorizationRequest in WebSession: $session") - } - }.then() - } - - override fun loadAuthorizationRequest(exchange: ServerWebExchange): Mono { - logger.info("LOADING AUTHORIZATION REQUEST") - val state = getStateParameter(exchange) ?: return Mono.empty() - return exchange.session - .flatMap { session -> - val authorizationRequest = getAuthorizationRequest(session) - if (state == authorizationRequest?.state) { - logger.info("Loading authorization request") - Mono.just(authorizationRequest) - } else { - logger.warn("State in request does not match Authorization Request state in Session: $session") - Mono.empty() - } - } - } - - override fun removeAuthorizationRequest(exchange: ServerWebExchange): Mono { - logger.info("REMOVING AUTHORIZATION REQUEST") - val state = getStateParameter(exchange) ?: return Mono.empty() - return exchange.session - .flatMap { session -> - val authorizationRequest = getAuthorizationRequest(session) - if (state == authorizationRequest?.state) { - session.attributes.remove(this.springAuthorizationRequestAttrName) - logger.info("Removed authorization request") - Mono.just(authorizationRequest) - } else { - logger.info("State in request does not match Authorization Request state in Session: $session") - Mono.empty() - } - } - } - - // Helper methods - private fun getStateParameter(exchange: ServerWebExchange): String? { - requireNotNull(exchange) { "exchange cannot be null" } - return exchange.request.queryParams[OAuth2ParameterNames.STATE]?.firstOrNull() - } - - private fun getAuthorizationRequest(session: WebSession): OAuth2AuthorizationRequest? { - Assert.notNull(session, "session cannot be null") - val authRequestAttr = session.getAttribute>(springAuthorizationRequestAttrName) - if (authRequestAttr != null) { - // Deserialize from Map to OAuth2AuthorizationRequest - val map = authRequestAttr as? Map - try { - val authorizationRequest = redisSerialiserConfig.redisObjectMapper() - .convertValue(map, OAuth2AuthorizationRequest::class.java) - logger.info("Successfully deserialized Authorization Request: $authorizationRequest") - return authorizationRequest - } catch (e: Exception) { - logger.error("Error deserializing Authorization Request: ${e.message}", e) - return null - } - } else { - logger.warn("No Authorization Request found in WebSession") - return null - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/clientregistrations/ClientRegistrationRepository.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/clientregistrations/ClientRegistrationRepository.kt deleted file mode 100644 index d425433..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/clientregistrations/ClientRegistrationRepository.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.frontiers.bff.auth.repositories.clientregistrations - -import com.frontiers.bff.props.ClientSecurityProperties -import com.frontiers.bff.props.ServerProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.security.oauth2.client.registration.ClientRegistration -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.core.AuthorizationGrantType -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames - -/**********************************************************************************************************************/ -/***************************************************** REPOSITORY *****************************************************/ -/**********************************************************************************************************************/ - -/** - * Contains configuration details necessary for authenticating with an OAuth2 provider, such as client ID, - * client secret, authorization grant types, scopes, and endpoints. - * THIS STAYS IN MEMORY - IT DOES NOT NEED TO BE PERSISTED TO AN EXTERNAL STORE LIKE REDIS! - */ -@Configuration -internal class ClientRegistrationRepository( - private val serverProperties: ServerProperties, - private val clientSecurityProperties: ClientSecurityProperties, -) { - - @Bean - fun reactiveClientRegistrationRepository(): ReactiveClientRegistrationRepository { - return InMemoryReactiveClientRegistrationRepository( - auth0Registration(), inHouseAuthRegistration() - ) - } - - // Auth-0 Authentication Provider - private fun auth0Registration(): ClientRegistration { - return ClientRegistration - .withRegistrationId(serverProperties.auth0AuthRegistrationId) - .clientId(clientSecurityProperties.auth0ClientId) - .clientSecret(clientSecurityProperties.auth0ClientSecret) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .redirectUri("${serverProperties.clientUri}/login/oauth2/code/${serverProperties.auth0AuthRegistrationId}") - .authorizationUri("${serverProperties.auth0IssuerUri}/authorize") - .tokenUri("${serverProperties.auth0IssuerUri}/oauth/token") - .jwkSetUri("${serverProperties.auth0IssuerUri}/.well-known/jwks.json") - .userInfoUri("${serverProperties.auth0IssuerUri}/userinfo") - .providerConfigurationMetadata(mapOf( - "issuer" to "${serverProperties.auth0IssuerUri}/", - "authorization_endpoint" to "${serverProperties.auth0IssuerUri}/authorize", - "token_endpoint" to "${serverProperties.auth0IssuerUri}/oauth/token", - "userinfo_endpoint" to "${serverProperties.auth0IssuerUri}/userinfo", - "end_session_endpoint" to "${serverProperties.auth0IssuerUri}/v2/logout", - "jwks_uri" to "${serverProperties.auth0IssuerUri}/.well-known/jwks.json", - "revocation_endpoint" to "${serverProperties.auth0IssuerUri}/oauth/revoke" - )) - .userNameAttributeName(IdTokenClaimNames.SUB) - .scope("openid", "offline_access") - .clientName("BFF-Server") - .issuerUri("${serverProperties.auth0IssuerUri}/") - .build() - } - - // In-House Authentication Provider - private fun inHouseAuthRegistration(): ClientRegistration { - return ClientRegistration - .withRegistrationId(serverProperties.inHouseAuthRegistrationId) - .clientId(clientSecurityProperties.inHouseAuthClientId) - .clientSecret(clientSecurityProperties.inHouseAuthClientSecret) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .redirectUri("${serverProperties.clientUri}/login/oauth2/code/${serverProperties.inHouseAuthRegistrationId}") - .authorizationUri("${serverProperties.inHouseIssuerUri}/oauth2/authorize") - .tokenUri("${serverProperties.inHouseIssuerUri}/oauth2/token") - .jwkSetUri("${serverProperties.inHouseIssuerUri}/oauth2/jwks") - .userInfoUri("${serverProperties.inHouseIssuerUri}/userinfo") - .providerConfigurationMetadata(mapOf( - "issuer" to serverProperties.inHouseIssuerUri, - "authorization_endpoint" to "${serverProperties.inHouseIssuerUri}/oauth2/authorize", - "token_endpoint" to "${serverProperties.inHouseIssuerUri}/oauth2/token", - "userinfo_endpoint" to "${serverProperties.inHouseIssuerUri}/userinfo", - "end_session_endpoint" to "${serverProperties.inHouseIssuerUri}/connect/logout", - "jwks_uri" to "${serverProperties.inHouseIssuerUri}/oauth2/jwks", - "revocation_endpoint" to "${serverProperties.inHouseIssuerUri}/oauth2/revoke" - )) - .userNameAttributeName(IdTokenClaimNames.SUB) - .scope("openid", "offline_access") - .clientName("BFF-Server") - .issuerUri(serverProperties.inHouseIssuerUri) - .build() - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/securitycontext/RedisSecurityContextRepository.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/securitycontext/RedisSecurityContextRepository.kt deleted file mode 100644 index b275c37..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/securitycontext/RedisSecurityContextRepository.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.frontiers.bff.auth.repositories.securitycontext - -import com.frontiers.bff.auth.serialisers.RedisSerialiserConfig -import com.frontiers.bff.routing.filters.IgnoreFilter -import org.slf4j.LoggerFactory -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.web.server.context.ServerSecurityContextRepository -import org.springframework.stereotype.Repository -import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/***************************************************** REPOSITORY *****************************************************/ -/**********************************************************************************************************************/ - -/** - * Enables the use of Redis as a persistent storage solution for Spring Security Context objects - */ -@Repository -internal class RedisSecurityContextRepository( - private val uriEndPointFilter: IgnoreFilter, - private val redisSerialiserConfig: RedisSerialiserConfig, -) : ServerSecurityContextRepository { - - private val logger = LoggerFactory.getLogger(RedisSecurityContextRepository::class.java) - - // default session attribute name to save and load the SecurityContext - private var springSecurityContextAttrName = "SPRING_SECURITY_CONTEXT" - - // flag to cache the SecurityContext to avoid multiple lookups - private var cacheSecurityContext: Boolean = true - - fun setCacheSecurityContext(cache: Boolean) { - this.cacheSecurityContext = cache - } - - override fun save( - exchange: ServerWebExchange, - context: SecurityContext? - ): Mono { - return exchange.session.flatMap { session -> - logger.info("SAVING SECURITY CONTEXT") - if (context == null) { - session.attributes.remove(springSecurityContextAttrName) - logger.info("Removed SecurityContext from WebSession: $session") - } else { - session.attributes[springSecurityContextAttrName] = context - - // extract principalName and roles from the SecurityContext - val authentication = context.authentication - val principalName = authentication.name - - // save them as session attributes (needed for indexing) - session.attributes["principalName"] = principalName - - logger.info("Saved SecurityContext $context in WebSession: $session") - logger.info("Set session attributes principalName=$principalName") - } - session.changeSessionId() - } - - } - - override fun load(exchange: ServerWebExchange): Mono { - val requestPath = exchange.request.uri.path - // skip processing for certain defined request paths - if (uriEndPointFilter.shouldSkipRequestPath(requestPath)) { - logger.info("Skipping security context loading for static resource: $requestPath") - return Mono.empty() - } - - return exchange.session.flatMap { session -> - logger.info("LOADING SECURITY CONTEXT") - logger.info("FOR REQUEST PATH: $requestPath") - - // Attempt to retrieve the security context from the session - val contextAttr = session.getAttribute>(springSecurityContextAttrName) - if (contextAttr != null) { - try { - // Deserialize from Map to SecurityContext - val securityContext = redisSerialiserConfig.redisObjectMapper() - .convertValue(contextAttr, SecurityContext::class.java) - logger.info("Successfully deserialized SecurityContext: $securityContext") - return@flatMap Mono.just(securityContext) - } catch (e: Exception) { - logger.error("Error deserializing SecurityContext: ${e.message}", e) - } - } - - logger.warn("No SecurityContext found in WebSession: $session") - Mono.empty() - }.let { if (cacheSecurityContext) it.cache() else it } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/sessions/RedisIndexedSessionRepositoryConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/sessions/RedisIndexedSessionRepositoryConfig.kt deleted file mode 100644 index a6e1f39..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/sessions/RedisIndexedSessionRepositoryConfig.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.frontiers.bff.auth.repositories.sessions - -import com.frontiers.bff.auth.sessions.CustomSessionIdGenerator -import com.frontiers.bff.props.SpringSessionProperties -import org.slf4j.LoggerFactory -import org.springframework.context.ApplicationEventPublisher -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Primary -import org.springframework.data.redis.core.ReactiveRedisOperations -import org.springframework.data.redis.core.ReactiveRedisTemplate -import org.springframework.session.FindByIndexNameSessionRepository -import org.springframework.session.SaveMode -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository -import org.springframework.stereotype.Repository -import java.time.Duration - -/**********************************************************************************************************************/ -/***************************************************** REPOSITORY *****************************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/redis.html#choosing-between-regular-and-indexed - -@Repository -internal class RedisIndexedSessionRepositoryConfig ( - private val springSessionProperties: SpringSessionProperties -) { - - private val logger = LoggerFactory.getLogger(RedisIndexedSessionRepositoryConfig::class.java) - - @Bean - @Primary - fun reactiveRedisIndexedSessionRepository( - reactiveRedisOperations: ReactiveRedisOperations, - reactiveRedisTemplate: ReactiveRedisTemplate, - eventPublisher: ApplicationEventPublisher - ): ReactiveRedisIndexedSessionRepository { - val repository = ReactiveRedisIndexedSessionRepository(reactiveRedisOperations, reactiveRedisTemplate).apply { - setDefaultMaxInactiveInterval(Duration.ofSeconds(springSessionProperties.timeout.toLong())) - setRedisKeyNamespace(springSessionProperties.redis?.sessionNamespace) - setSessionIdGenerator(CustomSessionIdGenerator()) - setCleanupInterval(Duration.ofSeconds(120)) - setEventPublisher(eventPublisher) - setSaveMode(SaveMode.ON_SET_ATTRIBUTE) - } - - repository.setIndexResolver { session -> - val indexes = mutableMapOf() - - // safely handle potential null values - val principalName = session.getAttribute("principalName") - - // use safe calls or provide default values if necessary - if (principalName != null) { - indexes[FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME] = principalName - logger.info("Indexing principalName: $principalName") - } - - indexes - } - - return repository - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/tokens/CsrfTokenRepository.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/tokens/CsrfTokenRepository.kt deleted file mode 100644 index 0aa7d11..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/repositories/tokens/CsrfTokenRepository.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.frontiers.bff.auth.repositories.tokens - -import com.frontiers.bff.props.CsrfProperties -import org.slf4j.LoggerFactory -import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository -import org.springframework.security.web.server.csrf.CsrfToken -import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository -import org.springframework.stereotype.Repository -import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/***************************************************** REPOSITORY *****************************************************/ -/**********************************************************************************************************************/ - -/** - * The CSRF token is validated by comparing the token value from the request (header or parameter) with the token - * value in the cookie. The server does not need to persist or store tokens beyond the duration of the request. - * The actual comparison and validation are handled by CSRF protection filters or components within Spring Security, - * ensuring the token matches without needing additional server-side storage. - */ - -@Repository -internal class CustomServerCsrfTokenRepository( - private val csrfProperties: CsrfProperties -) : ServerCsrfTokenRepository { - - private val logger = LoggerFactory.getLogger(CustomServerCsrfTokenRepository::class.java) - - private val delegate = CookieServerCsrfTokenRepository() - - init { - delegate.setCookieName(csrfProperties.CSRF_COOKIE_NAME) - delegate.setHeaderName(csrfProperties.CSRF_HEADER_NAME) - delegate.setParameterName(csrfProperties.CSRF_PARAMETER_NAME) - delegate.setCookieCustomizer { cookie -> - cookie.httpOnly(csrfProperties.CSRF_COOKIE_HTTP_ONLY) - cookie.secure(csrfProperties.CSRF_COOKIE_SECURE) - cookie.sameSite(csrfProperties.CSRF_COOKIE_SAME_SITE) - cookie.maxAge(csrfProperties.CSRF_COOKIE_MAX_AGE) - cookie.path(csrfProperties.CSRF_COOKIE_PATH) - cookie.domain(csrfProperties.CSRF_COOKIE_DOMAIN) - } - } - - override fun generateToken(exchange: ServerWebExchange): Mono { - logger.info("Generating CSRF token") - return delegate.generateToken(exchange) - } - - override fun saveToken(exchange: ServerWebExchange, token: CsrfToken?): Mono { - logger.info("Saving CSRF token") - return delegate.saveToken(exchange, token) - } - - override fun loadToken(exchange: ServerWebExchange): Mono { - logger.info("Loading CSRF token") - return delegate.loadToken(exchange) - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/requestcache/ReactiveRequestCache.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/requestcache/ReactiveRequestCache.kt deleted file mode 100644 index 160607d..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/requestcache/ReactiveRequestCache.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.frontiers.bff.auth.requestcache - -import com.frontiers.bff.auth.repositories.tokens.CustomServerCsrfTokenRepository -import org.slf4j.LoggerFactory -import org.springframework.http.server.reactive.ServerHttpRequest -import org.springframework.security.web.server.savedrequest.NoOpServerRequestCache -import org.springframework.security.web.server.savedrequest.ServerRequestCache -import org.springframework.stereotype.Component -import org.springframework.web.server.ServerWebExchange -import reactor.core.publisher.Mono -import java.net.URI - -/**********************************************************************************************************************/ -/******************************************************* REQUEST CACHE ************************************************/ -/**********************************************************************************************************************/ - -/** - * Saves (caches) the request uri, before any redirect uris, to the user's session, in case needed - */ -@Component -internal class ReactiveRequestCache() : ServerRequestCache { - - private val logger = LoggerFactory.getLogger(CustomServerCsrfTokenRepository::class.java) - - // stateless to reduce load on redis - no need to persist request state to sessions - private val delegate = NoOpServerRequestCache.getInstance() - - override fun saveRequest(exchange: ServerWebExchange): Mono { - logger.info("Saving request for ${exchange.request.uri}") - return delegate.saveRequest(exchange) - } - - override fun getRedirectUri(exchange: ServerWebExchange): Mono { - logger.info("Getting redirect URI for ${exchange.request.uri}") - return delegate.getRedirectUri(exchange) - } - - override fun removeMatchingRequest(exchange: ServerWebExchange): Mono { - logger.info("Removing matching request for ${exchange.request.uri}") - return delegate.removeMatchingRequest(exchange) - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/OAuthAuthorizationRequestResolver.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/OAuthAuthorizationRequestResolver.kt deleted file mode 100644 index 783034b..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/OAuthAuthorizationRequestResolver.kt +++ /dev/null @@ -1,290 +0,0 @@ -package com.frontiers.bff.auth.resolvers - -import com.frontiers.bff.auth.resolvers.customizers.AdditionalParamsAuthorizationRequestCustomizer -import com.frontiers.bff.auth.resolvers.customizers.CompositeOAuth2AuthorizationRequestCustomizer -import com.frontiers.bff.props.LoginProperties -import com.frontiers.bff.props.RequestParameterProperties -import com.frontiers.bff.props.ServerProperties -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers -import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest -import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher -import org.springframework.stereotype.Component -import org.springframework.util.MultiValueMap -import org.springframework.web.server.ServerWebExchange -import org.springframework.web.server.WebSession -import org.springframework.web.util.UriComponentsBuilder -import reactor.core.publisher.Mono -import java.net.URI -import java.util.regex.Pattern - -/**********************************************************************************************************************/ -/***************************************************** RESOLVER *******************************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://www.baeldung.com/spring-security-pkce-secret-clients -// https://github.com/ch4mpy/spring-addons/tree/master/spring-addons-starter-oidc - -/** - * Adds the success uri, and failure uri to the user's session (for post-login redirections) - * Potentially modifies the re-direct URI, to handle a proxy server configuration - * Also adds PKCE, and the ability to add extra authorization parameters, if needed - */ -@Component -internal class OAuthAuthorizationRequestResolver( - private val serverProperties: ServerProperties, - private val clientRegistrationRepository: ReactiveClientRegistrationRepository, - private val loginProperties: LoginProperties, - private val requestParameterProperties: RequestParameterProperties -) : ServerOAuth2AuthorizationRequestResolver { - - - // instance variables - private final var authorizationRequestMatcher: ServerWebExchangeMatcher - private final var requestCustomizers: Map = emptyMap() - final val clientRegistrations = (clientRegistrationRepository as? InMemoryReactiveClientRegistrationRepository)?.toList() - ?: emptyList() - - - // initialize instance variables here - init { - this.authorizationRequestMatcher = - PathPatternParserServerWebExchangeMatcher( - DefaultServerOAuth2AuthorizationRequestResolver.DEFAULT_AUTHORIZATION_REQUEST_PATTERN - ) - - this.requestCustomizers = clientRegistrations.associate { clientRegistration -> - val key = clientRegistration.registrationId - val requestCustomizer = CompositeOAuth2AuthorizationRequestCustomizer() - - // add additional authorization request parameters! - val additionalProperties: MultiValueMap = - requestParameterProperties.getExtraAuthorizationParameters(key) - - if (additionalProperties.size > 0) { - requestCustomizer.addCustomizer( - AdditionalParamsAuthorizationRequestCustomizer(additionalProperties) - ) - } - - // add pkce - requestCustomizer.addCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()) - - // return entry - key to requestCustomizer - } - - } - - - // checks if HTTP request satisfies request matcher; then extracts registration id; runs the other resolver - override fun resolve(exchange: ServerWebExchange?): Mono { - // @formatter:off - return this.authorizationRequestMatcher - .matches(exchange) - .filter { matchResult -> matchResult.isMatch } - .map { matchResult -> matchResult.variables } - .mapNotNull { variables -> variables[DefaultServerOAuth2AuthorizationRequestResolver.DEFAULT_REGISTRATION_ID_URI_VARIABLE_NAME] } - .map { it as String } - .flatMap { clientRegistrationId -> resolve(exchange, clientRegistrationId) } - // @formatter:on - } - - - // runs saveURIs to session function; then gets the request resolver; then runs post processor - override fun resolve( - exchange: ServerWebExchange?, - clientRegistrationId: String? - ): Mono { - if (exchange == null || clientRegistrationId == null) { - return Mono.empty() - } - // obtain the request resolver - val delegate = getRequestResolver(exchange, clientRegistrationId) ?: return Mono.empty() - - // process and return the OAuth2AuthorizationRequest - return savePostLoginUrisInSession(exchange) - // resolve the authorization request - .then(delegate.resolve(exchange, clientRegistrationId)) - // post-process the resolved request - .map { postProcess(it) } - } - - - // saves post login uris (in header or query parameter) to session - private fun savePostLoginUrisInSession(exchange: ServerWebExchange): Mono { - val request = exchange.request - val headers = request.headers - val params = request.queryParams - - return exchange.session.map { session -> - - // Log the session ID and current attributes - logger.info("SAVING POST LOGIN URIS TO SESSION ID: ${session.id}") - - // get and process the success URI - val postLoginSuccessUri = headers.getFirst(loginProperties.POST_AUTHENTICATION_SUCCESS_URI_HEADER) - ?: params.getFirst(loginProperties.POST_AUTHENTICATION_SUCCESS_URI_PARAM) - ?.takeIf { it.isNotBlank() } - ?.let { URI.create(it) } - postLoginSuccessUri?.let { - session.attributes[loginProperties.POST_AUTHENTICATION_SUCCESS_URI_SESSION_ATTRIBUTE] = it - } - - // get and process the failure URI - val postLoginFailureUri = headers.getFirst(loginProperties.POST_AUTHENTICATION_FAILURE_URI_HEADER) - ?: params.getFirst(loginProperties.POST_AUTHENTICATION_FAILURE_URI_PARAM) - ?.takeIf { it.isNotBlank() } - ?.let { URI.create(it) } - postLoginFailureUri?.let { - session.attributes[loginProperties.POST_AUTHENTICATION_FAILURE_URI_SESSION_ATTRIBUTE] = it - } - - // Save session and return - session - } - } - - - // potentially modifies the re-direct URI, to handle a proxy server configuration - private fun postProcess(request: OAuth2AuthorizationRequest): OAuth2AuthorizationRequest { - - // create a mutable copy of the original request - val modified = OAuth2AuthorizationRequest.from(request) - - // parse the original redirect URI - val original = URI.create(request.redirectUri) - - // update redirect URI - val baseUri = URI.create(serverProperties.clientUri) - - // extract the authorities - val originalAuthority = original.authority - val baseAuthority = baseUri.authority - - // extract the first elements of the paths - val originalFirstElement = original.path.split("/").getOrNull(1) - val baseFirstElement = baseUri.path.split("/").getOrNull(1) - - // check if the authorities and the first elements of the paths match - val redirectUri = if (originalAuthority != baseAuthority && originalFirstElement != baseFirstElement) { - - // build the new redirect URI with the original path, query, and fragment - UriComponentsBuilder.fromUri(baseUri) - .path(original.path) - .query(original.query) - .fragment(original.fragment) - .build() - .toUriString() - } else { - original.toString() - } - - // set the modified redirect URI - modified.redirectUri(redirectUri) - - // log the changes - logger.info("Changed OAuth2AuthorizationRequest redirectUri from {} to {}", original, redirectUri) - - // return the modified request - return modified.build() - } - - - /** - * See getOAuth2AuthorizationRequestCustomizer to add advanced request customizer(s) - * - * @param exchange - * @param clientRegistrationId - * @return - */ - protected fun getRequestResolver(exchange: ServerWebExchange, clientRegistrationId: String): ServerOAuth2AuthorizationRequestResolver? { - val requestCustomizer = getOAuth2AuthorizationRequestCustomizer(exchange, clientRegistrationId) - if (requestCustomizer == null) { - return null - } - - val delegate = DefaultServerOAuth2AuthorizationRequestResolver(clientRegistrationRepository) - delegate.setAuthorizationRequestCustomizer(requestCustomizer) - - return delegate - } - - - /** - * Override this to use a "dynamic" request customizer. Something like: - * - *
-     * return CompositeOAuth2AuthorizationRequestCustomizer(
-     *     getCompositeOAuth2AuthorizationRequestCustomizer(clientRegistrationId),
-     *     MyDynamicCustomizer(request),
-     *     ...
-     * )
-     * 
- * - * @param exchange The ServerWebExchange to customize the request. - * @param clientRegistrationId The client registration ID. - * @return A Consumer for customizing OAuth2AuthorizationRequest. - */ - protected fun getOAuth2AuthorizationRequestCustomizer( - exchange: ServerWebExchange, - clientRegistrationId: String - ): CompositeOAuth2AuthorizationRequestCustomizer? { - return getCompositeOAuth2AuthorizationRequestCustomizer(clientRegistrationId) - } - - - /** - * @param clientRegistrationId The client registration ID. - * @return A request customizer adding PKCE token (if activated) and "static" parameters defined in spring-addons properties. - */ - protected fun getCompositeOAuth2AuthorizationRequestCustomizer( - clientRegistrationId: String - ): CompositeOAuth2AuthorizationRequestCustomizer? { - return this.requestCustomizers[clientRegistrationId] - } - - - // static variables (companion object) - companion object { - - private val logger: Logger = LoggerFactory.getLogger(OAuthAuthorizationRequestResolver::class.java) - private val authorizationRequestPattern: Pattern = Pattern.compile("/oauth2/authorization/([^/]+)") - - /** - * Resolves the registration ID from the exchange's request path. - * - * @param exchange The ServerWebExchange to get the request from. - * @return The resolved registration ID. - */ - @JvmStatic - fun resolveRegistrationId(exchange: ServerWebExchange): String? { - val requestPath = exchange.request.path.toString() - return resolveRegistrationId(requestPath) - } - - /** - * Resolves the registration ID from the request path. - * - * @param requestPath The request path. - * @return The resolved registration ID. - */ - @JvmStatic - fun resolveRegistrationId(requestPath: String): String? { - val matcher = authorizationRequestPattern.matcher(requestPath) - return if (matcher.matches()) matcher.group(1) else null - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/customizers/AdditionalParamsAuthorizationRequestCustomizer.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/customizers/AdditionalParamsAuthorizationRequestCustomizer.kt deleted file mode 100644 index 523fc89..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/customizers/AdditionalParamsAuthorizationRequestCustomizer.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.frontiers.bff.auth.resolvers.customizers - -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest -import org.springframework.util.MultiValueMap -import java.util.function.Consumer - -/**********************************************************************************************************************/ -/**************************************************** CUSTOMIZER ******************************************************/ -/**********************************************************************************************************************/ - -/** - * adds extra parameters to the OAuth2 authorization request (if needed). - */ -internal class AdditionalParamsAuthorizationRequestCustomizer( - private val additionalParams: MultiValueMap -) : Consumer { - - override fun accept(builder: OAuth2AuthorizationRequest.Builder) { - builder.additionalParameters { params -> - additionalParams.forEach { (key, values) -> - params[key] = values.joinToString(",") - } - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/customizers/CompositeOAuth2AuthorizationRequestCustomizer.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/customizers/CompositeOAuth2AuthorizationRequestCustomizer.kt deleted file mode 100644 index bed6d59..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/resolvers/customizers/CompositeOAuth2AuthorizationRequestCustomizer.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.frontiers.bff.auth.resolvers.customizers - -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest -import java.util.function.Consumer - -/**********************************************************************************************************************/ -/**************************************************** CUSTOMIZER ******************************************************/ -/**********************************************************************************************************************/ - -/** - * Helps organize and apply multiple customization strategies to the OAuth2 authorization request - * in a modular and flexible way. - */ -internal class CompositeOAuth2AuthorizationRequestCustomizer( - vararg customizers: Consumer -) : Consumer { - - private val delegates: MutableList> = - customizers.toMutableList() - - // secondary constructor to allow extending an existing instance - constructor( - other: CompositeOAuth2AuthorizationRequestCustomizer, - vararg customizers: Consumer - ) : this(*(other.delegates.toTypedArray() + customizers)) - - // applies all customizers to the given OAuth2AuthorizationRequest.Builder - override fun accept(builder: OAuth2AuthorizationRequest.Builder) { - for (consumer in delegates) { - consumer.accept(builder) - } - } - - // adds an additional customizer - fun addCustomizer(customizer: Consumer): - CompositeOAuth2AuthorizationRequestCustomizer { - delegates.add(customizer) - return this - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/serialisers/RedisSerialiserConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/serialisers/RedisSerialiserConfig.kt deleted file mode 100644 index 696d2ec..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/serialisers/RedisSerialiserConfig.kt +++ /dev/null @@ -1,411 +0,0 @@ -package com.frontiers.bff.auth.serialisers - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.databind.node.ArrayNode -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.BeanClassLoaderAware -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer -import org.springframework.data.redis.serializer.RedisSerializer -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.context.SecurityContextImpl -import org.springframework.security.jackson2.CoreJackson2Module -import org.springframework.security.jackson2.SecurityJackson2Modules -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken -import org.springframework.security.oauth2.client.registration.ClientRegistration -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository -import org.springframework.security.oauth2.core.OAuth2AccessToken -import org.springframework.security.oauth2.core.OAuth2RefreshToken -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType -import org.springframework.security.oauth2.core.oidc.OidcIdToken -import org.springframework.security.oauth2.core.oidc.OidcUserInfo -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority -import java.time.Instant -import java.util.* - -/**********************************************************************************************************************/ -/**************************************************** SERIALISERS *****************************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/reactive-redis-indexed.html - -/** - * User for Serialising to JSON and De-Serialising from JSON, when sending and retrieving from Redis - */ -@Configuration -internal class RedisSerialiserConfig( - private val clientRegistrationRepository: ReactiveClientRegistrationRepository -) : BeanClassLoaderAware { - - private var loader: ClassLoader? = null - - /** - * Note that the bean name for this bean is intentionally - * {@code springSessionDefaultRedisSerializer}. It must be named this way to override - * the default {@link RedisSerializer} used by Spring Session. - */ - @Bean - // setting a custom session serialiser for Redis - fun springSessionDefaultRedisSerializer(): RedisSerializer { - return object : GenericJackson2JsonRedisSerializer(redisObjectMapper()) { - override fun serialize(value: Any?): ByteArray { - value.let{ - // println("Serializing: $value of type: ${value!!::class.java}") - } - // println("Serializing: $value") - return super.serialize(value) - } - - override fun deserialize(bytes: ByteArray?): Any { - if (bytes == null || bytes.isEmpty()) { - // println("Deserialization: Received null or empty byte array") - return Any() - } - val result = super.deserialize(bytes) - return result - } - } - } - - /** - * Customized {@link ObjectMapper} to add mix-in for class that doesn't have default - * constructors. - * @return the {@link ObjectMapper} to use - */ - fun redisObjectMapper(): ObjectMapper { - val mapper = ObjectMapper() - - // Register custom serializers and deserializers - val module = SimpleModule().apply { - addDeserializer( - OAuth2AuthorizationRequest::class.java, - OAuth2AuthorizationRequestDeserializer() - ) - - // No Serialiser for OAuth2AuthorizationRequest (works using the default ObjectMapper! - - addDeserializer( - OAuth2AuthorizationResponseType::class.java, - OAuth2AuthorizationResponseTypeDeserializer() - ) - - // No Serialiser for OAuth2AuthorizationResponseType (works using the default ObjectMapper!) - - addDeserializer( - SecurityContext::class.java, - SpringSecurityContextDeserializer() - ) - - // No Serialiser for SecurityContext (works using the default ObjectMapper!) - - addDeserializer( - OAuth2AuthorizedClient::class.java, - OAuth2AuthorizedClientDeserializer(clientRegistrationRepository) - ) - addSerializer( - OAuth2AuthorizedClient::class.java, - OAuth2AuthorizedClientSerializer() - ) - } - mapper.registerModule(module) - - // register security-related modules - mapper.registerModule(CoreJackson2Module()) - mapper.registerModules(SecurityJackson2Modules.getModules(this::class.java.classLoader)) - - // deactivate default typing if it is enabled - mapper.deactivateDefaultTyping() - - // additional configurations - mapper.registerModule(JavaTimeModule()) - mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL) - return mapper - } - - /** - * Define custom de-serialiser for OAuth2AuthorizationRequest - */ - private class OAuth2AuthorizationRequestDeserializer : JsonDeserializer() { - - private val logger = LoggerFactory.getLogger(OAuth2AuthorizationRequestDeserializer::class.java) - - override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): OAuth2AuthorizationRequest { - logger.info("Starting deserialization of OAuth2AuthorizationRequest") - - val node = jp.codec.readTree(jp) - - // Extract values from JSON - val authorizationUri = node.get("authorizationUri")?.asText() ?: throw IllegalArgumentException("authorizationUri is required") - val clientId = node.get("clientId")?.asText() ?: throw IllegalArgumentException("clientId is required") - val redirectUri = node.get("redirectUri")?.asText() - val state = node.get("state")?.asText() - val scopes = node.get("scopes")?.elements()?.asSequence()?.map { it.asText() }?.toSet() ?: emptySet() - val additionalParameters = node.get("additionalParameters")?.fields()?.asSequence()?.associate { it.key to it.value.asText() } ?: emptyMap() - val attributes = node.get("attributes")?.fields()?.asSequence()?.associate { it.key to it.value.asText() } ?: emptyMap() - val authorizationRequestUri = node.get("authorizationRequestUri")?.asText() - - // Initialize Builder with extracted values - val builder = OAuth2AuthorizationRequest - .authorizationCode() // Assuming the default grant type for the builder - .authorizationUri(authorizationUri) - .clientId(clientId) - .redirectUri(redirectUri) - .scopes(scopes) - .state(state) - .additionalParameters(additionalParameters) - .attributes(attributes) - .authorizationRequestUri(authorizationRequestUri ?: "") - - val authorizationRequest = builder.build() - - return authorizationRequest - } - - } - - - /** - * Define custom de-serialiser for OAuth2AuthorizationResponseTypeDeserializer - */ - private class OAuth2AuthorizationResponseTypeDeserializer : JsonDeserializer() { - - private val logger = LoggerFactory.getLogger(OAuth2AuthorizationResponseTypeDeserializer::class.java) - - override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): OAuth2AuthorizationResponseType { - logger.info("Starting deserialization of OAuth2AuthorizationResponseTypeDeserializer") - - val node: JsonNode = p.codec.readTree(p) - val value = node.get("value")?.asText() ?: throw IllegalArgumentException("Missing value field") - return OAuth2AuthorizationResponseType(value) - } - } - - - /** - * Define custom de-serialiser for Security Context - */ - private class SpringSecurityContextDeserializer : JsonDeserializer() { - - private val logger = LoggerFactory.getLogger(SpringSecurityContextDeserializer::class.java) - - override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): SecurityContext { - logger.info("Starting deserialization of SecurityContext") - - val node: JsonNode = jp.codec.readTree(jp) - - // extract the authentication node - val authenticationNode = node.get("authentication") - - // extract principal details - val principalNode = authenticationNode.get("principal") - - // extract authorities - val authorities = principalNode.get("authorities") - .map { authNode -> - val authority = authNode.get("authority").asText() - if (authNode.has("idToken")) { - val idTokenNode = authNode.get("idToken") - val idToken = deserializeOidcIdToken(idTokenNode) - val userInfo = OidcUserInfo(idToken?.claims ?: emptyMap()) - OidcUserAuthority(authority, idToken, userInfo) - } else { - SimpleGrantedAuthority(authority) - } - }.toSet() - - // deserialize the OidcIdToken at the principal level - val idTokenNode = principalNode.get("idToken") - val principalIdToken = deserializeOidcIdToken(idTokenNode) - - // create the OidcUser principal - val nameAttributeKey = principalNode.get("nameAttributeKey").asText() - val principal = DefaultOidcUser(authorities, principalIdToken, nameAttributeKey) - - // extract the authorizedClientRegistrationId - val authorizedClientRegistrationId = authenticationNode.get("authorizedClientRegistrationId").asText() - - // create the Authentication object (assumed OAuth2AuthenticationToken) - val authentication = OAuth2AuthenticationToken(principal, authorities, authorizedClientRegistrationId) - - // create and return the SecurityContextImpl object - return SecurityContextImpl(authentication) - } - - private fun deserializeOidcIdToken(idTokenNode: JsonNode?): OidcIdToken? { - idTokenNode ?: return null - - val tokenValue = idTokenNode.get("tokenValue").asText() - val issuedAt = Instant.parse(idTokenNode.get("issuedAt").asText()) - val expiresAt = Instant.parse(idTokenNode.get("expiresAt").asText()) - - // extract claims - val claimsNode = idTokenNode.get("claims") - val claims = claimsNode.fields().asSequence() - .associate { it.key to it.value.asText() } - - return OidcIdToken(tokenValue, issuedAt, expiresAt, claims) - } - } - - - /** - * Define custom de-serialiser for Authorized Client - */ - private class OAuth2AuthorizedClientDeserializer( - private val clientRegistrationRepository: ReactiveClientRegistrationRepository - ): JsonDeserializer() { - - private val logger = LoggerFactory.getLogger(OAuth2AuthorizedClientDeserializer::class.java) - - override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): OAuth2AuthorizedClient { - logger.info("Starting deserialization of OAuth2AuthorizedClient") - - val node = jp.codec.readTree(jp) - - // extract 'principalName' - val principalName = node.get("principalName")?.asText() ?: throw IllegalArgumentException("principalName is required") - - // deserialize 'clientRegistration' - val clientRegistrationNode = node.get("clientRegistration") ?: throw IllegalArgumentException("clientRegistration is required") - val clientRegistration = jp.codec.treeToValue(clientRegistrationNode, ClientRegistration::class.java) - ?: throw IllegalArgumentException("Unable to deserialize clientRegistration") - - // retrieve the list of original client registrations which have passwords - val clientRegistrations = (clientRegistrationRepository as? InMemoryReactiveClientRegistrationRepository)?.toList() - ?: emptyList() - - // find the original client registration with the same ID - val originalClientRegistration = clientRegistrations.find { - it.registrationId == clientRegistration.registrationId - } - - // add the password back if the original client registration is found - val updatedClientRegistration = ClientRegistration.withClientRegistration(clientRegistration) - .clientSecret(originalClientRegistration?.clientSecret ?: clientRegistration.clientSecret) - .build() - - // deserialize 'accessToken' - val accessTokenNode = node.get("accessToken") ?: throw IllegalArgumentException("accessToken is required") - val tokenTypeValue = accessTokenNode.get("tokenType")?.asText() - ?: throw IllegalArgumentException("tokenType value is required") - val tokenType = when (tokenTypeValue.uppercase(Locale.getDefault())) { - "BEARER" -> OAuth2AccessToken.TokenType.BEARER - else -> throw IllegalArgumentException("Unknown token type: $tokenTypeValue") - } - val accessToken = OAuth2AccessToken( - tokenType, - accessTokenNode.get("tokenValue")?.asText() ?: throw IllegalArgumentException("tokenValue is required"), - accessTokenNode.get("issuedAt")?.asText()?.let { Instant.parse(it) }, - accessTokenNode.get("expiresAt")?.asText()?.let { Instant.parse(it) }, - accessTokenNode.get("scopes")?.elements()?.asSequence()?.map { it.asText() }?.toSet() ?: emptySet() - ) - - // deserialize 'refreshToken' - val refreshTokenNode = node.get("refreshToken") - val refreshToken = refreshTokenNode?.let { - OAuth2RefreshToken( - it.get("tokenValue")?.asText() ?: throw IllegalArgumentException("tokenValue is required"), - it.get("issuedAt")?.asText()?.let { date -> Instant.parse(date) } - ) - } - - println("CLIENT SECRET: ${updatedClientRegistration.clientSecret}") - - // return deserialized OAuth2AuthorizedClient object - return OAuth2AuthorizedClient( - updatedClientRegistration, - principalName, - accessToken, - refreshToken - ) - } - - } - - // Custom serializer for OAuth2AuthorizedClient - private class OAuth2AuthorizedClientSerializer : JsonSerializer() { - - private val logger = LoggerFactory.getLogger(OAuth2AuthorizedClientSerializer::class.java) - - override fun serialize( - value: OAuth2AuthorizedClient, - gen: JsonGenerator, - serializers: SerializerProvider - ) { - logger.info("Starting serialisation of OAuth2AuthorizedClient") - - val mapper = gen.codec as ObjectMapper - val node = mapper.createObjectNode() - - // add @class type information - node.put("@class", value.javaClass.name) - - // serialize 'clientRegistration' - val clientRegistrationNode: ObjectNode = mapper.valueToTree(value.clientRegistration) - node.set("clientRegistration", clientRegistrationNode) - - // remove the password field or set it to an empty string! - clientRegistrationNode.put("clientSecret", "") - - // serialize 'principalName' - node.put("principalName", value.principalName) - - // serialize 'accessToken' - val accessToken = value.accessToken - val accessTokenNode = mapper.createObjectNode() - accessTokenNode.put("tokenType", accessToken.tokenType.value) - accessTokenNode.put("tokenValue", accessToken.tokenValue) - accessTokenNode.put("issuedAt", accessToken.issuedAt?.toString()) - accessTokenNode.put("expiresAt", accessToken.expiresAt?.toString()) - - val scopesArrayNode = mapper.createArrayNode() - accessToken.scopes.forEach { scope -> - scopesArrayNode.add(scope) - } - accessTokenNode.set("scopes", scopesArrayNode) - - node.set("accessToken", accessTokenNode) - - // serialize 'refreshToken' - value.refreshToken?.let { - val refreshTokenNode = mapper.createObjectNode() - refreshTokenNode.put("tokenValue", it.tokenValue) - refreshTokenNode.put("issuedAt", it.issuedAt.toString()) - node.set("refreshToken", refreshTokenNode) - } - - // write the JSON node - gen.writeTree(node) - } - } - - - /* - * @see - * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader) - */ - override fun setBeanClassLoader(classLoader: ClassLoader) { - this.loader = classLoader - } - - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/CustomSessionIdGenerator.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/CustomSessionIdGenerator.kt deleted file mode 100644 index 8190ec2..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/CustomSessionIdGenerator.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import org.springframework.session.SessionIdGenerator -import org.springframework.stereotype.Component -import java.util.* - -/**********************************************************************************************************************/ -/**************************************************** SESSION ID GENERATOR ********************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/common.html#changing-how-session-ids-are-generated - -/** - * A Custom Session-ID Generator - */ -@Component -internal class CustomSessionIdGenerator : SessionIdGenerator { - - val prefix = "BFF" - - override fun generate(): String { - // adds custom prefix, timestamp, and UUID to create a session identifier - return generateEnhancedIdentifier(prefix) - } - - private fun generateEnhancedIdentifier(prefix: String): String { - val timestamp = System.currentTimeMillis() - val uuid = UUID.randomUUID() - return "$prefix-$timestamp-$uuid" - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/RedisSessionCleanUpConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/RedisSessionCleanUpConfig.kt deleted file mode 100644 index 96cbaa7..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/RedisSessionCleanUpConfig.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import com.frontiers.bff.props.SpringSessionProperties -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.data.domain.Range -import org.springframework.data.redis.connection.Limit -import org.springframework.data.redis.core.ReactiveRedisOperations -import org.springframework.scheduling.annotation.EnableScheduling -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.session.config.ReactiveSessionRepositoryCustomizer -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository -import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono -import reactor.core.scheduler.Schedulers -import java.time.Duration -import java.time.Instant -import java.util.* -import java.util.concurrent.TimeUnit - -/**********************************************************************************************************************/ -/************************************************ REDIS CONFIGURATION *************************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/reactive-redis-indexed.html#_configuring_redis_to_send_keyspace_events -// https://docs.spring.io/spring-session/reference/configuration/reactive-redis-indexed.html#how-spring-session-cleans-up-expired-sessions - -@Configuration -@EnableScheduling -internal class RedisCleanUpConfig { - - /** - * No specific configuration or action should be taken regarding Redis keyspace notifications. - */ - @Bean - fun configureReactiveRedisAction(): ConfigureReactiveRedisAction { - return ConfigureReactiveRedisAction.NO_OP - } - - /** - * Disables the default clean up task - */ - @Bean - fun reactiveSessionRepositoryCustomizer(): ReactiveSessionRepositoryCustomizer { - return ReactiveSessionRepositoryCustomizer { sessionRepository: ReactiveRedisIndexedSessionRepository -> - sessionRepository.disableCleanupTask() - } - } -} - -/** - * For cleanup operations (i.e. removing expired session from a ZSet (Sorted Sets) in Redis) - * Spring's scheduling mechanism will automatically call the cleanup method according to the schedule - * defined by the @Scheduled annotation. - */ -@Component -@EnableScheduling -internal class SessionEvicter( - private val redisOperations: ReactiveRedisOperations, - springSessionProperties: SpringSessionProperties, -) { - - private val logger = LoggerFactory.getLogger(SessionEvicter::class.java) - - private val redisKeyLocation = springSessionProperties.redis?.expiredSessionsNamespace - ?: "spring:session:sessions:expirations" - - data class CleanupContext( - val now: Instant, - val pastFiveDays: Instant, - val range: Range, - val limit: Limit - ) - - // run every 120 seconds - @Scheduled(fixedRate = 120, timeUnit = TimeUnit.SECONDS) - fun cleanup(): Mono { - return Mono.fromCallable { - val now = Instant.now() - val pastFiveDays = now.minus(Duration.ofDays(5)) - val range = Range.closed( - pastFiveDays.toEpochMilli().toDouble(), - now.toEpochMilli().toDouble() - ) - val limit = Limit.limit().count(500) - CleanupContext(now, pastFiveDays, range, limit) - } - .doOnNext { context -> - logger.info("Scheduled cleanup execution started at ${Instant.now()}.") - logger.info("Current time (now): ${context.now}") - logger.info("Time range start: ${Date(context.pastFiveDays.toEpochMilli())}") - logger.info("Time range end: ${Date(context.now.toEpochMilli())}") - logger.info("Limit count: ${context.limit.count}") - logger.info("Redis key location: $redisKeyLocation") - } - .flatMap { context -> - val zSetOps = redisOperations.opsForZSet() - zSetOps.reverseRangeByScore(redisKeyLocation, context.range, context.limit) - .collectList() - .flatMap { sessionIdsList -> - if (sessionIdsList.isNotEmpty()) { - logger.info("Found ${sessionIdsList.size} sessions to remove.") - zSetOps.remove( - redisKeyLocation, - *sessionIdsList.toTypedArray() - ).doOnSubscribe { logger.info("Started removal of sessions") } - .doOnSuccess { logger.info("Successfully removed sessions") } - .doOnError { e -> logger.error("Error during removal: ${e.message}") } - } else { - logger.info("No sessions found to remove.") - Mono.empty() - } - } - } - .doOnSuccess { - logger.info("Scheduled session cleanup check completed at ${Instant.now()}.") - } - .doOnError { e -> - logger.error("Error during session cleanup check: ${e.message}") - } - .then() - .doOnTerminate { - logger.info("Cleanup process terminated at ${Instant.now()}.") - } - .subscribeOn(Schedulers.boundedElastic()) // to ensure proper threading - } - - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/RedisSessionMapperConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/RedisSessionMapperConfig.kt deleted file mode 100644 index d3b8314..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/RedisSessionMapperConfig.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import com.frontiers.bff.props.SpringSessionProperties -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.data.redis.core.ReactiveRedisOperations -import org.springframework.session.MapSession -import org.springframework.session.config.ReactiveSessionRepositoryCustomizer -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository -import org.springframework.session.data.redis.RedisSessionMapper -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono -import java.util.function.BiFunction - -/**********************************************************************************************************************/ -/************************************************ REDIS CONFIGURATION *************************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/redis.html#configuring-redis-session-mapper - -@Configuration -internal class RedisSessionMapperConfig() { - - /** - * Customizes the ReactiveRedisIndexedSessionRepository to use the SafeSessionMapper. - */ - @Bean - fun redisSessionRepositoryCustomizer(): ReactiveSessionRepositoryCustomizer { - return ReactiveSessionRepositoryCustomizer { redisSessionRepository -> - redisSessionRepository.setRedisSessionMapper( - SafeRedisSessionMapper( - redisSessionRepository.sessionRedisOperations - ) - ) - } - } - - /** - * Implementation of SafeSessionMapper. - * Enhances the default session mapping behavior by adding error handling, ensuring that any mapping issues - * result in the removal of the problematic session from Redis. - * Improves the robustness and reliability of session management - */ - @Component - internal class SafeRedisSessionMapper( - private var redisOperations: ReactiveRedisOperations - ) : BiFunction, Mono>{ - - private val springSessionProperties = SpringSessionProperties() - private val delegate = RedisSessionMapper() - - /** - * Custom session mapper that delegates to the default RedisSessionMapper. - * If an exception occurs, the session is deleted from Redis. - */ - override fun apply(sessionId: String, map: Map): Mono { - return Mono.defer { - try { - // attempt to apply the session mapping - Mono.just(delegate.apply(sessionId, map)) - } catch (ex: IllegalStateException) { - // handle exception: delete session from Redis and return empty Mono - redisOperations.delete("${springSessionProperties.redis?.sessionNamespace}:$sessionId") - .then(Mono.empty()) - } - } - } - - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionControl.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionControl.kt deleted file mode 100644 index 99e2bf6..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionControl.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import com.frontiers.bff.props.SpringSessionProperties -import org.slf4j.LoggerFactory -import org.springframework.data.redis.core.ReactiveRedisOperations -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository -import org.springframework.session.web.server.session.SpringSessionWebSessionStore -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/************************************************** SESSION CONFIGURATION *********************************************/ -/**********************************************************************************************************************/ - -// see more here: -// https://docs.spring.io/spring-security/reference/reactive/authentication/concurrent-sessions-control.html#reactive-concurrent-sessions-control-manually-invalidating-sessions - -/** - * Session control class for invalidation session(s) and then removing them - */ -@Component -internal class SessionControl( - private val reactiveSessionRedisOperations: ReactiveRedisOperations, - private val reactiveSessionRegistry: CustomSpringSessionReactiveSessionRegistry, - private val webSessionStore: SpringSessionWebSessionStore, - private val springSessionProperties: SpringSessionProperties -) { - - private val logger = LoggerFactory.getLogger(SessionControl::class.java) - - fun invalidateSessions(username: String): Mono { - return reactiveSessionRegistry.getAllSessions(username) - .flatMap { session -> - logger.info("Invalidating sessions of user {}", username) - // handle the sessions invalidation process - session.invalidate() - .then(webSessionStore.removeSession(session.sessionId)) - .then(Mono.just(session)) // ensure the session object is returned for logging or further processing if needed - } - .then() - .onErrorResume { e -> - logger.error("Error invalidating sessions: ${e.message}") - Mono.empty() // return empty Mono to signify completion even if an error occurred - } - } - - fun invalidateSession(sessionId: String): Mono { - logger.info("Invalidating sessionId: ${sessionId}") - // handle the session invalidation process - return reactiveSessionRegistry.getSessionInformation(sessionId) - .flatMap { session -> - // invalidate session - session.invalidate() - .then( - webSessionStore.removeSession(sessionId) - ) - .doOnSuccess { - logger.info("Session invalidated and removed: ${sessionId}") - } - .doOnError { error -> - logger.error("Error invalidating session: ${sessionId}", error) - } - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionListenerConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionListenerConfig.kt deleted file mode 100644 index fdf397e..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionListenerConfig.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import org.slf4j.LoggerFactory -import org.springframework.context.event.EventListener -import org.springframework.session.events.SessionCreatedEvent -import org.springframework.session.events.SessionDeletedEvent -import org.springframework.session.events.SessionExpiredEvent -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/************************************************** SESSION CONFIGURATION *********************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/reactive-redis-indexed.html#listening-session-events - - -/** - * Listens for session-related events, designed to handle events when a session is created, deleted, or expired. - * Useful for tracking session lifecycles for auditing, security, or resource management purposes. - */ -@Component -// session event listener -internal class SessionListenerConfig { - - private val logger = LoggerFactory.getLogger(SessionListenerConfig::class.java) - - // copies attributes from old session when a new session is created - @EventListener - fun processSessionCreatedEvent(event: SessionCreatedEvent): Mono { - return Mono.fromRunnable { - // Log or print information about the created session - println("Session created: ${event.sessionId}") - // Your logic for session created event - } - } - - @EventListener - fun processSessionDeletedEvent(event: SessionDeletedEvent): Mono { - return Mono.fromRunnable { - // Log or print information about the deleted session - logger.info("Session deleted: ${event.sessionId}") - // logic for session deleted event - } - } - - @EventListener - fun processSessionExpiredEvent(event: SessionExpiredEvent): Mono { - return Mono.fromRunnable { - logger.info("Session expired: ${event.sessionId}") - // logic for session expired event - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionRegistryConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionRegistryConfig.kt deleted file mode 100644 index b77b616..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionRegistryConfig.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import com.frontiers.bff.auth.serialisers.RedisSerialiserConfig -import org.slf4j.LoggerFactory -import org.springframework.security.authentication.AbstractAuthenticationToken -import org.springframework.security.core.Authentication -import org.springframework.security.core.authority.AuthorityUtils -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.session.ReactiveSessionInformation -import org.springframework.security.core.session.ReactiveSessionRegistry -import org.springframework.session.ReactiveFindByIndexNameSessionRepository -import org.springframework.session.Session -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository -import org.springframework.session.security.SpringSessionBackedReactiveSessionRegistry -import org.springframework.stereotype.Component -import reactor.core.publisher.Flux -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/************************************************** SESSION CONFIGURATION *********************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/common.html#spring-session-backed-reactive-session-registry -// https://docs.spring.io/spring-session/reference/spring-security.html#spring-security-concurrent-sessions - -/** - * Implements a custom version of SpringSessionBackedReactiveSessionRegistry registry - */ -@Component -internal class CustomSpringSessionReactiveSessionRegistry( - private val reactiveRedisIndexedSessionRepository: ReactiveRedisIndexedSessionRepository, - private val redisSerialiserConfig: RedisSerialiserConfig -) : ReactiveSessionRegistry { - - /* NOTE - NOT REALLY PROPERLY IMPLEMENTED BY SPRING SECURITY - HERE FOR REFERENCE ONLY*/ - private val delegate = SpringSessionBackedReactiveSessionRegistry( - reactiveRedisIndexedSessionRepository, - reactiveRedisIndexedSessionRepository - ) - - private val logger = LoggerFactory.getLogger(CustomSpringSessionReactiveSessionRegistry::class.java) - - override fun getAllSessions(principal: Any?): Flux { - logger.info("Running getAllSessions logic") - val authenticationToken = principal?.let { getAuthenticationToken(it) } - return reactiveRedisIndexedSessionRepository.findByPrincipalName(authenticationToken?.name) - .flatMapMany { sessionMap -> Flux.fromIterable(sessionMap.entries) } - .map { entry -> SpringSessionBackedReactiveSessionInformation(entry.value) } - } - - override fun saveSessionInformation(information: ReactiveSessionInformation): Mono { - logger.info("Running saveSessionInformation logic") - return Mono.empty() - } - - override fun getSessionInformation(sessionId: String?): Mono { - logger.info("Running getSessionInformation logic") - return reactiveRedisIndexedSessionRepository.findById(sessionId) - .map { session -> SpringSessionBackedReactiveSessionInformation(session) } - } - - override fun removeSessionInformation(sessionId: String): Mono { - logger.info("Running removeSessionInformation logic") - return Mono.empty() - } - - override fun updateLastAccessTime(sessionId: String): Mono { - logger.info("Running updateLastAccessTime logic") - return Mono.empty() - } - - // get SpringSessionBackedReactiveSessionInformation - inner class SpringSessionBackedReactiveSessionInformation( - session: S - ) : ReactiveSessionInformation( - ResolvePrincipalName().resolvePrincipalName(session), - session.id, - session.lastAccessedTime - ) { - - override fun invalidate(): Mono { - return super.invalidate() - .then(Mono.defer { - reactiveRedisIndexedSessionRepository.deleteById(sessionId) - }) - } - } - - // helper class to resolve principalName - inner class ResolvePrincipalName() { - - private val logger = LoggerFactory.getLogger(ResolvePrincipalName::class.java) - fun resolvePrincipalName(session: Session): String { - val principalName: String? = session.getAttribute(ReactiveFindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME) - if (principalName != null) { - return principalName - } - val contextAttr = session.getAttribute>("SPRING_SECURITY_CONTEXT") - val map = contextAttr as? Map - return try { - val securityContext = redisSerialiserConfig.redisObjectMapper() - .convertValue(map, SecurityContext::class.java) - logger.info("Successfully deserialized SecurityContext: $securityContext") - securityContext?.authentication?.name ?: "" - } catch (e: Exception) { - logger.error("Error deserializing SecurityContext: ${e.message}", e) - "" - } - } - } - - // get Authentication Token - private fun getAuthenticationToken(principal: Any): Authentication { - return object : AbstractAuthenticationToken(AuthorityUtils.NO_AUTHORITIES) { - override fun getCredentials(): Any? { - return null - } - override fun getPrincipal(): Any { - return principal - } - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionService.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionService.kt deleted file mode 100644 index d2e168c..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/SessionService.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import org.slf4j.LoggerFactory -import org.springframework.session.ReactiveFindByIndexNameSessionRepository -import org.springframework.session.Session -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository.RedisSession -import org.springframework.stereotype.Service -import reactor.core.publisher.Flux -import reactor.core.publisher.Mono -import java.security.Principal - -/**********************************************************************************************************************/ -/************************************************ SESSION CONFIGURATION ***********************************************/ -/**********************************************************************************************************************/ - -// more here: -// https://docs.spring.io/spring-session/reference/configuration/redis.html#finding-all-user-sessions - -/** - * Service to retrieve all sessions of a particular user, and remove a session of a particular use - */ -@Service -internal class SessionService( - private val sessions: ReactiveFindByIndexNameSessionRepository, - private val redisIndexedSessionRepository: ReactiveRedisIndexedSessionRepository -) { - - private val logger = LoggerFactory.getLogger(SessionService::class.java) - - /** - * Retrieves all sessions for a specific user. - * @param principal the principal whose sessions need to be retrieved - * @return a Flux of sessions for the specified user - */ - fun getSessions(principal: Principal): Flux { - - logger.info("Getting all sessions for: ${principal.name}") - - return sessions.findByPrincipalName(principal.name) - .flatMapMany { sessionsMap -> - Flux.fromIterable(sessionsMap.values) - } - } - - /** - * Removes a specific session for a user. - * @param principal the principal whose session needs to be removed - * @param sessionIdToDelete the ID of the session to be removed - * @return a Mono indicating completion or error - */ - fun removeSession(principal: Principal, sessionIdToDelete: String): Mono { - - logger.info("Removing session for: ${principal.name}, with session id: $sessionIdToDelete") - - return sessions.findByPrincipalName(principal.name) - .flatMap { userSessions -> - if (userSessions.containsKey(sessionIdToDelete)) { - redisIndexedSessionRepository.deleteById(sessionIdToDelete) - } else { - Mono.empty() - } - } - } -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/WebSessionStoreConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/WebSessionStoreConfig.kt deleted file mode 100644 index fe79624..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/sessions/WebSessionStoreConfig.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.frontiers.bff.auth.sessions - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository -import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository.RedisSession -import org.springframework.session.web.server.session.SpringSessionWebSessionStore -import org.springframework.web.server.session.CookieWebSessionIdResolver -import org.springframework.web.server.session.DefaultWebSessionManager -import org.springframework.web.server.session.WebSessionManager - -/**********************************************************************************************************************/ -/************************************************** SESSION CONFIGURATION *********************************************/ -/**********************************************************************************************************************/ - -// see more here: -// https://docs.spring.io/spring-session/reference/web-session.html -// https://docs.spring.io/spring-session/reference/web-session.html#websession-how - -/** - * Configures the session management setup for a Spring WebFlux application, integrating Spring Session with Redis. - */ -@Configuration -internal class WebSessionStoreConfig { - - /** - * Adapts ReactiveRedisIndexedSessionRepository (which stores sessions in Redis) to be usable - * as a WebSessionStore in WebFlux. - */ - @Bean - fun webSessionStore( - reactiveRedisIndexedSessionRepository: ReactiveRedisIndexedSessionRepository - ): SpringSessionWebSessionStore { - return SpringSessionWebSessionStore(reactiveRedisIndexedSessionRepository) - } - - /** - * Configures how sessions are managed in WebFlux, using cookies to store session IDs - * and Redis to store session data - */ - @Bean - fun webSessionManager( - cookieWebSessionIdResolver: CookieWebSessionIdResolver, - webSessionStore: SpringSessionWebSessionStore - ): WebSessionManager { - val sessionManager = DefaultWebSessionManager() - sessionManager.sessionStore = webSessionStore - sessionManager.sessionIdResolver = cookieWebSessionIdResolver - return sessionManager - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/templates/RedisTemplateConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/templates/RedisTemplateConfig.kt deleted file mode 100644 index 6da6928..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/auth/templates/RedisTemplateConfig.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.frontiers.bff.auth.templates - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Primary -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory -import org.springframework.data.redis.core.ReactiveRedisOperations -import org.springframework.data.redis.core.ReactiveRedisTemplate -import org.springframework.data.redis.serializer.RedisSerializationContext -import org.springframework.data.redis.serializer.RedisSerializer -import org.springframework.data.redis.serializer.StringRedisSerializer - -/**********************************************************************************************************************/ -/***************************************************** TEMPLATES ******************************************************/ -/**********************************************************************************************************************/ - -/** - * Spring Template and Operations level abstractions for working with Redis, an external key-value store. - */ -@Configuration -internal class RedisTemplateConfig{ - - /** - * RedisTemplate: This is the primary abstraction for interacting with Redis in Spring. It provides various methods - * to perform Redis operations, such as setting and getting values, performing transactions, and working with different - * data structures like strings, lists, sets, and hashes. It is a high-level API that simplifies working with Redis. - */ - - @Bean - @Primary - // reactive Redis Template for sessions - fun reactiveSessionRedisTemplate( - connectionFactory: ReactiveRedisConnectionFactory, - springSessionDefaultRedisSerializer: RedisSerializer - ): ReactiveRedisTemplate { - - val serializationContext = RedisSerializationContext.newSerializationContext() - .key(StringRedisSerializer()) - .value(springSessionDefaultRedisSerializer) - .hashKey(StringRedisSerializer()) - .hashValue(springSessionDefaultRedisSerializer) - .build() - return ReactiveRedisTemplate(connectionFactory, serializationContext) - } - - /** - * RedisOperations: This is a common interface that RedisTemplate implements. It defines the basic operations - * that can be performed on Redis, such as CRUD (Create, Read, Update, Delete) operations and working with - * different Redis data structures. - */ - @Bean - // reactive Redis Operations for sessions - fun reactiveSessionRedisOperations( - connectionFactory: ReactiveRedisConnectionFactory, - springSessionDefaultRedisSerializer: RedisSerializer - ): ReactiveRedisOperations { - val serializationContext = RedisSerializationContext.newSerializationContext() - .key(StringRedisSerializer()) - .value(springSessionDefaultRedisSerializer) - .hashKey(StringRedisSerializer()) - .hashValue(springSessionDefaultRedisSerializer) - .build() - - return ReactiveRedisTemplate(connectionFactory, serializationContext) - } - - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/props/AppPropertiesConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/props/AppPropertiesConfig.kt deleted file mode 100644 index f728b22..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/props/AppPropertiesConfig.kt +++ /dev/null @@ -1,696 +0,0 @@ -package com.frontiers.bff.props - -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.HttpRequest -import org.springframework.http.HttpStatus -import org.springframework.security.oauth2.core.oidc.StandardClaimNames -import org.springframework.stereotype.Component -import org.springframework.util.LinkedMultiValueMap -import org.springframework.util.MultiValueMap -import org.springframework.web.util.UriComponentsBuilder -import java.net.URI - -/**********************************************************************************************************************/ -/**************************************************** PROPERTIES ******************************************************/ -/**********************************************************************************************************************/ - -/**********************************************************************************************************************/ -/* SERVER PROPERTIES */ -/**********************************************************************************************************************/ -@ConfigurationProperties(prefix = "dse-servers") -@Configuration -internal class ServerProperties { - - /*************************/ - /* SERVERS */ - /*************************/ - // server settings - var scheme: String? = null - var hostname: String? = null - - // reverse proxy settings (for internal routing) - var reverseProxyHost: String? = null - var reverseProxyPort: Int? = null - val reverseProxyUri: String - get() = "$scheme://$reverseProxyHost:$reverseProxyPort" - - // bff server settings (for external calls) - var bffServerPort: Int? = null - var bffServerPrefix: String? = null - val bffServerUri: String - get() = "$reverseProxyUri$bffServerPrefix" - val clientUri: String - get() = "$reverseProxyUri$bffServerPrefix" - - // resource server settings (for internal routing) - var resourceServerPort: Int? = null - var resourceServerPrefix: String? = null - val resourceServerUri: String - get() = "$scheme://$hostname:$resourceServerPort" - - /*************************/ - /* ISSUERS */ - /*************************/ - - // auth-0 authorization server (for external calls) - var auth0AuthRegistrationId: String? = null - var auth0IssuerUri: String? = null - - // in-house authorization server (for external calls) - var inHouseAuthServerPrefix: String? = null - var inHouseAuthRegistrationId: String? = null - val inHouseIssuerUri: String - get() = "$reverseProxyUri$inHouseAuthServerPrefix" - -} - -/**********************************************************************************************************************/ -/* CLIENT PROPERTIES */ -/**********************************************************************************************************************/ -@ConfigurationProperties(prefix = "spring.security") -@Configuration -internal class ClientSecurityProperties { - - // nested class for OAuth2 client registrations - var oauth2: OAuth2Properties = OAuth2Properties() - - // inner classes for structured properties - internal class OAuth2Properties { - var client: ClientProperties = ClientProperties() - - internal class ClientProperties { - var registration: RegistrationProperties = RegistrationProperties() - - // client registrations - internal class RegistrationProperties { - var inHouseAuth: InHouseAuthProperties = InHouseAuthProperties() - var auth0: Auth0Properties = Auth0Properties() - - internal class InHouseAuthProperties { - var clientId: String? = null - var clientSecret: String? = null - } - - internal class Auth0Properties { - var clientId: String? = null - var clientSecret: String? = null - } - - } - } - } - - // custom getter methods to provide variable names as needed - val inHouseAuthClientId: String? - get() = oauth2.client.registration.inHouseAuth.clientId - - val inHouseAuthClientSecret: String? - get() = oauth2.client.registration.inHouseAuth.clientSecret - - val auth0ClientId: String? - get() = oauth2.client.registration.auth0.clientId - - val auth0ClientSecret: String? - get() = oauth2.client.registration.auth0.clientSecret - -} - -/**********************************************************************************************************************/ -/* OIDC PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class OidcProviderProperties( - serverProperties: ServerProperties, -) { - - /** - * OpenID Providers configuration: JWK set URI, issuer URI, audience, and authorities mapping configuration - * for each issuer. A minimum of one issuer is required. Properties defined here are a replacement for - * spring.security.oauth2.resourceserver.jwt.* (which will be ignored). The reason for that is it is applicable - * only to single tenant scenarios. Use properties. - * Authorities mapping defined there is used by both client and resource server filter-chains. - */ - internal data class OpenidProviderProperties( - - /** - * Must be exactly the same as in access tokens (even trailing slash, if any, is important). - * In case of doubt, open one of your access tokens with a tool like https://jwt.io. - */ - val iss: URI? = null, - - /** - * Can be omitted if OpenID configuration can be retrieved from ${iss}/.well-known/ openid-configuration - */ - val jwkSetUri: URI? = null, - - /** - * Can be omitted. Will insert an audience validator if not null or empty - */ - val aud: String? = null, - - /** - * Authorities mapping configuration, per claim - */ - val authorities: List = listOf(), - - /** - * Authorities mapping configuration, per claim - */ - val usernameClaim: String = StandardClaimNames.SUB - ) - - internal data class SimpleAuthoritiesMappingProperties( - /** - * JSON path of the claim(s) to map with this properties - */ - val path: String = "$.realm_access.roles", - - /** - * What to prefix authorities with (for instance "ROLE_" or "SCOPE_") - */ - val prefix: String = "", - - /** - * Whether to transform authorities to uppercase, lowercase, or to leave it unchanged - */ - val case: SimpleAuthoritiesMappingProperties.Case = SimpleAuthoritiesMappingProperties.Case.UNCHANGED - ) { - enum class Case { - UNCHANGED, - UPPER, - LOWER - } - } - - // id-provider 2 - final val auth0AuthProvider = - OpenidProviderProperties( - iss = URI.create("${serverProperties.auth0IssuerUri}/"), - jwkSetUri = URI.create("${serverProperties.auth0IssuerUri}/.well-known/jwks.json"), - aud = "BFF-Server", - authorities = listOf( - SimpleAuthoritiesMappingProperties( - path = "$.authorities", - prefix = "", - case = SimpleAuthoritiesMappingProperties.Case.UPPER - ) - ), - usernameClaim = "sub" - ) - - // id-provider 1 - final val inHouseAuthProvider = - OpenidProviderProperties( - iss = URI.create(serverProperties.inHouseIssuerUri), - jwkSetUri = URI.create("${serverProperties.inHouseIssuerUri}/oauth2/jwks"), - aud = "BFF-Server", - authorities = listOf( - SimpleAuthoritiesMappingProperties( - path = "$.authorities", - prefix = "", - case = SimpleAuthoritiesMappingProperties.Case.UPPER - ) - ), - usernameClaim = "sub" - ) - - // final list of OPENID Provider (Issuer) Properties - final val openidProviderPropertiesList: List = listOf( - auth0AuthProvider, inHouseAuthProvider - ) -} - -/**********************************************************************************************************************/ -/* SPRING DATA PROPERTIES */ -/**********************************************************************************************************************/ -@ConfigurationProperties(prefix = "spring.data") -@Configuration -internal class SpringDataProperties { - - // Redis properties - var redis: RedisProperties = - RedisProperties() - - class RedisProperties { - var host: String = "" - var password: String = "" - var port: Int = 6800 - } -} - -/**********************************************************************************************************************/ -/* SPRING SESSION PROPERTIES */ -/**********************************************************************************************************************/ -@ConfigurationProperties(prefix = "spring.session") -@Configuration -internal class SpringSessionProperties { - - var redis: RedisProperties? = null - var timeout: Int = 21600 // in seconds (6 hours) - - class RedisProperties { - var namespace: String? = null - var repositoryType: String? = null - var flushMode: String? = null - val sessionNamespace: String - get() = namespace.toString() - var expiredSessionsNamespace: String? = null - } -} - -/**********************************************************************************************************************/ -/* CORS PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class CorsProperties( - serverProperties: ServerProperties, - csrfProperties: CsrfProperties -) { - /** - * Path matcher to which this configuration entry applies - */ - final val path: String = "/**" - - final val allowedOriginPatterns: List = listOf( - serverProperties.reverseProxyUri - ) - - final val allowedMethods: List = listOf( - HttpMethod.GET.toString(), - HttpMethod.POST.toString(), - HttpMethod.PUT.toString(), - HttpMethod.PATCH.toString(), - HttpMethod.DELETE.toString(), - HttpMethod.OPTIONS.toString(), - ) - - final val allowedHeaders: List = listOf( - HttpHeaders.CONTENT_TYPE, - HttpHeaders.AUTHORIZATION, - csrfProperties.CSRF_HEADER_NAME - ) - - final val exposedHeaders: List = listOf( - HttpHeaders.CONTENT_TYPE, - HttpHeaders.AUTHORIZATION, - csrfProperties.CSRF_HEADER_NAME - ) - - final val maxAge: Long = 21600L // in seconds (6 hours) - - /** - * Required if credentials (cookies, authorization headers) are involved - */ - final val allowCredentials: Boolean = true - - /** - * If left to false, OPTIONS requests are added to permit-all for the [path] matchers of this [CorsProperties] - */ - final val disableAnonymousOptions: Boolean = true -} - -/**********************************************************************************************************************/ -/* CSRF PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class CsrfProperties { - - final val CSRF_COOKIE_NAME: String = "XSRF-BFF-TOKEN" - final val CSRF_HEADER_NAME: String = "X-XSRF-BFF-TOKEN" - final val CSRF_PARAMETER_NAME: String = "_csrf" - - final val CSRF_COOKIE_HTTP_ONLY: Boolean = false // so SPA (e.g. Angular) can read it - final val CSRF_COOKIE_SECURE: Boolean = false // scope is not just on secure connections - final val CSRF_COOKIE_SAME_SITE: String = "Strict" - final val CSRF_COOKIE_MAX_AGE: Long = -1 // cookie expires when browser closes - final val CSRF_COOKIE_PATH: String = "/bff/" - final val CSRF_COOKIE_DOMAIN: String = "" - -} - -/**********************************************************************************************************************/ -/* SESSION PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class SessionProperties { - - // needs to be called JSESSIONID - // https://docs.spring.io/spring-security/reference/reactive/oauth2/login/logout.html#oauth2login-advanced-oidc-logout - final val SESSION_COOKIE_NAME: String = "BFF-SESSIONID" - - final val SESSION_COOKIE_HTTP_ONLY: Boolean = true - final val SESSION_COOKIE_SECURE: Boolean = false // scope is not just on secure connections - final val SESSION_COOKIE_SAME_SITE: String = "Lax" - final val SESSION_COOKIE_MAX_AGE: Long = 21600 // in seconds - final val SESSION_COOKIE_PATH: String = "/bff/" - final val SESSION_COOKIE_DOMAIN: String = "" -} - -/**********************************************************************************************************************/ -/* AUTHENTICATION PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class AuthenticationProperties() { - - // securityMatchers for Client security filter chain - final val securityMatchers: List = listOf( - "/api/**", - "/login/**", - "/oauth2/**", - "/logout/**", - "/login-options", - ) -} - - -/**********************************************************************************************************************/ -/* AUTHORIZATION PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class AuthorizationProperties( - serverProperties: ServerProperties -) { - - // allowed permitAlls - final val permitAll: List = listOf( - "/api/**", - "/login/**", - "/oauth2/**", - "/logout", - "/logout/connect/back-channel/${serverProperties.inHouseAuthRegistrationId}", - "/login-options", - ) -} - -/**********************************************************************************************************************/ -/* REQUEST PARAMETER PROPERTIES (AUTHORIZATION & TOKEN ENDPOINTS - NOT LOGOUT) */ -/**********************************************************************************************************************/ -@Component -internal class RequestParameterProperties( - private val serverProperties: ServerProperties -){ - - /** - * Additional parameters to send with authorization request, mapped by client registration IDs - */ - private val authorizationParams: MutableMap>> = mutableMapOf() - - // get authorizationParameters - final fun getExtraAuthorizationParameters(registrationId: String): MultiValueMap { - return getExtraParameters(registrationId, authorizationParams) - } - - /** - * Additional parameters to send with token request, mapped by client registration IDs - */ - private val AUTH0_SERVER_AUDIENCE = "https://dev-ld4xuyx1eigiqoge.uk.auth0.com/api/v2/" - - private var tokenParams: MutableMap>> = mutableMapOf( - (serverProperties.auth0AuthRegistrationId ?: "") to mapOf( - "audience" to listOf(AUTH0_SERVER_AUDIENCE) - ) - ) - - // get tokenParameters - final fun getExtraTokenParameters(registrationId: String): MultiValueMap { - return getExtraParameters(registrationId, tokenParams) - } - - /** - * Converts to LinkedMultiValueMap (can store multiple values against each key) - */ - private fun getExtraParameters( - registrationId: String, - requestParamsMap: Map>> - ): MultiValueMap { - val extraParameters = requestParamsMap[registrationId]?.let { otherMap -> - LinkedMultiValueMap(otherMap) - } ?: LinkedMultiValueMap() - - return extraParameters - } - -} - -/**********************************************************************************************************************/ -/* RE-DIRECTION PROPERTIES */ -/**********************************************************************************************************************/ - -/** - * HTTP status for redirections in OAuth2 login and logout. You might set this to something in 2xx range - * (like OK, ACCEPTED, NO_CONTENT, ...) for single page and mobile applications to handle this redirection as it wishes - * (change the user-agent, clear some headers, ...). 2xx WILL NOT automatically re-direct. - */ - -@Component -internal class OAuth2RedirectionProperties( - private val serverProperties: ServerProperties -) { - - /*************************/ - /* LOGIN REDIRECT HOST */ - /*************************/ - /** - * URI containing scheme, host, and port used for redirection - * (defaults to the client URI). - */ - final val postLoginRedirectHostValue: URI? = null - - fun getPostLoginRedirectHost( - ): URI { - return postLoginRedirectHostValue ?: URI.create(serverProperties.clientUri) - } - - /*************************/ - /* SUCCESSFUL LOGIN */ - /*************************/ - /** - * Path used to redirect the user after successful login. - */ - final val postLoginRedirectPath: String? = null - - /** - * Get final constructed redirect URI. - */ - final fun getPostLoginRedirectUri(): URI? { - if (postLoginRedirectHostValue == null && postLoginRedirectPath == null) { - return null - } - - val uriBuilder = UriComponentsBuilder.fromUri(getPostLoginRedirectHost()) - postLoginRedirectPath?.let { uriBuilder.path(it) } - - return uriBuilder.build().toUri() - } - - /*************************/ - /* LOGIN ERROR */ - /*************************/ - /** - * Path used to redirect the user if unsuccessful login. - */ - final val loginErrorRedirectPath: String? = null - - /** - * Get final constructed redirect URI. - */ - final fun getLoginErrorRedirectUri(): URI? { - if (postLoginRedirectHostValue == null && loginErrorRedirectPath == null) { - return null - } - - val uriBuilder = UriComponentsBuilder.fromUri(getPostLoginRedirectHost()) - loginErrorRedirectPath?.let { uriBuilder.path(it) } - - return uriBuilder.build().toUri() - } - - /*************************/ - /* RESPONSE HEADER */ - /*************************/ - /** - * Header used by OAuth2ServerRedirectStrategy to carry the various response codes - */ - final val RESPONSE_STATUS_HEADER: String = "X-RESPONSE-STATUS" - - /*************************/ - /* STATUS CODES */ - /*************************/ - /** - * Status for the 1st response in authorization code flow, with location to get authorization code from authorization server - */ - final val preAuthorizationCode: HttpStatus = HttpStatus.FOUND - - /** - * Status for the response after authorization code, with location to the UI - */ - final val postAuthorizationCode: HttpStatus = HttpStatus.FOUND - - /** - * Status for the response after BFF logout, with location to authorization server logout endpoint - * ACCEPTED IS code 202 - so does not automatically re-direct. With FOUND, the browser will re-direct - */ - final val rpInitiatedLogout: HttpStatus = HttpStatus.FOUND - - - /** - * Map of logout properties indexed by client registration ID - * (must match a registration in Spring Boot OAuth2 client configuration). - * LogoutProperties are configuration for authorization server not strictly following the RP-Initiated Logout - * standard, but exposing a logout end-point expecting an authorized GET request with following request params: - * "client-id" (required) - * post-logout redirect URI (optional) - */ - - // id-provider 1 - final val Auth0LogoutProperties = OAuth2LogoutProperties( - uri = URI.create("${serverProperties.auth0IssuerUri}/v2/logout"), - clientIdRequestParam = "client_id", - postLogoutUriRequestParam = "returnTo", - rpInitiatedLogoutEnabled = true - ) - - private val oauth2Logout: Map = mapOf( - serverProperties.auth0AuthRegistrationId to Auth0LogoutProperties - ) - - internal fun getLogoutProperties(clientRegistrationId: String?): OAuth2LogoutProperties? { - return oauth2Logout.get(clientRegistrationId) - } - -} - - -/**********************************************************************************************************************/ -/* LOGIN PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class LoginProperties { - - final val LOGIN_URL: String = "/" - - final val POST_AUTHENTICATION_SUCCESS_URI_HEADER: String = "X-POST-LOGIN-SUCCESS-URI" - final val POST_AUTHENTICATION_SUCCESS_URI_PARAM: String = "post_login_success_uri" - final val POST_AUTHENTICATION_SUCCESS_URI_SESSION_ATTRIBUTE = POST_AUTHENTICATION_SUCCESS_URI_PARAM - - final val POST_AUTHENTICATION_FAILURE_URI_HEADER: String = "X-POST-LOGIN-FAILURE-URI" - final val POST_AUTHENTICATION_FAILURE_URI_PARAM: String = "post_login_failure_uri" - final val POST_AUTHENTICATION_FAILURE_URI_SESSION_ATTRIBUTE: String = POST_AUTHENTICATION_FAILURE_URI_PARAM - final val POST_AUTHENTICATION_FAILURE_CAUSE_ATTRIBUTE: String = "error" - -} - -/**********************************************************************************************************************/ -/* LOGOUT PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class LogoutProperties( - private val serverProperties: ServerProperties -) { - - final val LOGOUT_URL: String = "/logout" - - final val POST_LOGOUT_SUCCESS_URI_HEADER: String = "X-POST-LOGOUT-SUCCESS-URI" - final val POST_LOGOUT_SUCCESS_URI_PARAM: String = "post_logout_success_uri" - - /*************************/ - /* LOGOUT REDIRECT HOST */ - /*************************/ - /** - * URI containing scheme, host, and port used for redirection - * (defaults to the client URI). - */ - final val postLogoutRedirectHostValue: URI? = URI.create("http://www.google.com") - - fun getPostLogoutRedirectHost( - ): URI { - return postLogoutRedirectHostValue ?: URI.create(serverProperties.clientUri) - } - - /*************************/ - /* LOGOUT */ - /*************************/ - /** - * Path used to redirect the user on logout - */ - final val postLogoutRedirectPath: String? = null - - /** - * Get final constructed redirect URI. - */ - final fun getPostLogoutRedirectUri(): URI? { - if (postLogoutRedirectHostValue == null && postLogoutRedirectPath == null) { - return null - } - - val uriBuilder = UriComponentsBuilder.fromUri(getPostLogoutRedirectHost()) - postLogoutRedirectPath?.let { uriBuilder.path(it) } - - return uriBuilder.build().toUri() - } - -} - -/**********************************************************************************************************************/ -/* OAUTH2 LOGOUT PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal data class OAuth2LogoutProperties( - /** - * URI on the authorization server where to redirect the user for logout (usually the endsession endpoint) - */ - val uri: URI? = null, - - /** - * Request param name for client-id - */ - val clientIdRequestParam: String? = null, - - /** - * Request param name for post-logout redirect URI - * (where the user should be redirected after their session is closed on the authorization server) - */ - val postLogoutUriRequestParam: String? = null, - - /** - * Request param name for setting an ID-Token hint - */ - val idTokenHintRequestParam: String? = null, - - /** - * RP-Initiated Logout is enabled by default. Setting this to false disables it. - */ - val rpInitiatedLogoutEnabled: Boolean = true -) - -/**********************************************************************************************************************/ -/* BACK CHANNEL LOGOUT PROPERTIES */ -/**********************************************************************************************************************/ -@Component -internal class BackChannelLogoutProperties( - serverProperties: ServerProperties, - logoutProperties: LogoutProperties -) { - - /** - * Enabled by default. - */ - final val enabled = true - - /** - * The URI for a loop of the Spring client to itself in which it actually ends the user session. - * Overriding this can be useful to force the scheme and port in the case where the client is behind a revers proxy - * with different scheme and port (default URI uses the original Back-Channel Logout request scheme and ports). - */ - // logout-uri: ${reverse-proxy-uri}${bff-prefix}/logout - final val backChannelLogoutUri: String - = "${serverProperties.bffServerUri}${logoutProperties.LOGOUT_URL}" - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/RoutingConfig.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/RoutingConfig.kt deleted file mode 100644 index 947fafc..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/RoutingConfig.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.frontiers.bff.routing - -import com.frontiers.bff.props.ServerProperties -import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory -import org.springframework.cloud.gateway.route.RouteLocator -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -/**********************************************************************************************************************/ -/***************************************************** GATEWAY ROUTING ************************************************/ -/**********************************************************************************************************************/ - -@Configuration -internal class RoutingConfig( - private val serverProperties: ServerProperties, -) { - - @Bean - fun routeLocator( - builder: RouteLocatorBuilder, - tokenRelayGatewayFilterFactory: TokenRelayGatewayFilterFactory - ): RouteLocator { - return builder.routes() - - // routing for Resource Server - .route("resource-server") { r -> - r.path("/api${serverProperties.resourceServerPrefix}/**") - .filters { f -> - f.filter { exchange, chain -> - chain.filter(exchange) - } - f.filter(tokenRelayGatewayFilterFactory.apply()) - .removeRequestHeader("Cookie") - } - .uri(serverProperties.resourceServerUri) - } - .build() - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/circuitbreaker/CircuitBreaker.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/circuitbreaker/CircuitBreaker.kt deleted file mode 100644 index 13b635c..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/circuitbreaker/CircuitBreaker.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.bff.gateway.circuitbreaker - -import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig -import io.github.resilience4j.timelimiter.TimeLimiterConfig -import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory -import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder -import org.springframework.cloud.client.circuitbreaker.Customizer -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import java.time.Duration - -/**********************************************************************************************************************/ -/***************************************************** CIRCUIT BREAKER ************************************************/ -/**********************************************************************************************************************/ - -@Configuration -class Resilience4JConfig { - - @Bean - fun defaultCustomizer(): Customizer { - return Customizer { factory -> - factory.configureDefault { id -> - Resilience4JConfigBuilder(id) - .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()) - .timeLimiterConfig( - TimeLimiterConfig.custom() - .timeoutDuration( - // 4.5 seconds - Duration.ofSeconds(4, 500_000_000L) - ) - .build() - ) - .build() - } - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/filters/GlobalFilters.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/filters/GlobalFilters.kt deleted file mode 100644 index 10a9b4b..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/filters/GlobalFilters.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.frontiers.bff.routing.filters - -import org.springframework.cloud.gateway.filter.GlobalFilter -import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpHeaders -import reactor.core.publisher.Mono - -/**********************************************************************************************************************/ -/******************************************************* FILTER *******************************************************/ -/**********************************************************************************************************************/ - -@Configuration -internal class DedupeResponseFilterConfig { - - @Bean - fun dedupeResponseHeaderFilter(): GlobalFilter { - return GlobalFilter { exchange, chain -> - chain.filter(exchange).then( - Mono.fromRunnable { - val headers = exchange.response.headers - headers.dedupeResponseHeader( - "Access-Control-Allow-Credentials", - "RETAIN_UNIQUE" - ) - headers.dedupeResponseHeader( - "Access-Control-Allow-Origin", - "RETAIN_UNIQUE" - ) - } - ) - } - } -} - -private fun HttpHeaders.dedupeResponseHeader(headerName: String, strategy: String) { - val headerValues = this[headerName] ?: return - - val uniqueValues = when (strategy) { - "RETAIN_UNIQUE" -> headerValues.toSet().toList() - else -> headerValues - } - - this[headerName] = uniqueValues -} - -@Configuration -internal class SaveSessionFilterConfig( - private val saveSessionGatewayFilterFactory: SaveSessionGatewayFilterFactory -) { - - @Bean - fun saveSessionGlobalFilter(): GlobalFilter { - return GlobalFilter { exchange, chain -> - saveSessionGatewayFilterFactory.apply { - // optionally configure futher if needed - }.filter(exchange, chain) - } - } -} - -@Configuration -internal class CustomHeaderFilterConfig { - - @Bean - fun customHeaderFilter(): GlobalFilter { - return GlobalFilter { exchange, chain -> - chain.filter(exchange).then( - Mono.fromRunnable { - exchange.response.headers.add( - "X-Powered-By", - "DreamStar Enterprises" - ) - } - ) - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/filters/IgnoreFilter.kt b/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/filters/IgnoreFilter.kt deleted file mode 100644 index 74451ec..0000000 --- a/Spring BFF/BFF/src/main/kotlin/com/frontiers/bff/routing/filters/IgnoreFilter.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.frontiers.bff.routing.filters - -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Configuration - -/**********************************************************************************************************************/ -/*********************************************** DEFAULT SECURITY CONFIGURATION ***************************************/ -/**********************************************************************************************************************/ - -/** - * Configures endpoints considered that should be skipped - */ -@Configuration -internal class IgnoreFilter { - - private val logger = LoggerFactory.getLogger(IgnoreFilter::class.java) - - // define base paths and file extensions for security context loading - private val skipRequestPath: Map> = mapOf( - "/login-options" to listOf(), - ) - - // main function that checks if the request path should skip static resources - fun shouldSkipRequestPath(requestPath: String): Boolean { - val isSkipped = getRequestPathMatchers().any { it.matches(requestPath) } - if (isSkipped) { - logger.info("Request path '$requestPath' is skipped as a skippable path.") - } - return isSkipped - } - - // function that generates matchers for request path - private fun getRequestPathMatchers(): List { - return skipRequestPath.flatMap { (basePath, extensions) -> - if (extensions.isEmpty()) { - listOf(RequestPathMatcher(basePath)) - } else { - extensions.map { extension -> RequestPathMatcher(basePath, extension) } - } - } - } - - // data class to encapsulate the logic for matching request paths - private data class RequestPathMatcher(val basePath: String, val extension: String? = null) { - fun matches(requestPath: String): Boolean { - return requestPath.startsWith(basePath) && - (extension == null || requestPath.endsWith(".$extension", ignoreCase = true)) - } - } - -} - -/**********************************************************************************************************************/ -/**************************************************** END OF KOTLIN ***************************************************/ -/**********************************************************************************************************************/ \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/resources/application-dev.yaml b/Spring BFF/BFF/src/main/resources/application-dev.yaml deleted file mode 100644 index 135aea9..0000000 --- a/Spring BFF/BFF/src/main/resources/application-dev.yaml +++ /dev/null @@ -1,20 +0,0 @@ -#**********************************************************************************************************************# -#************************************************* PROFILE SETTINGS ***************************************************# -#**********************************************************************************************************************# - -# spring profile settings ---- -spring: - config: - activate: - on-profile: dev -server: - ssl: - enabled: false - reactive: - session: - timeout: ${TIMEOUT_SESSION} - -#**********************************************************************************************************************# -#************************************************** END OF YAML *******************************************************# -#**********************************************************************************************************************# \ No newline at end of file diff --git a/Spring BFF/BFF/src/main/resources/application.yaml b/Spring BFF/BFF/src/main/resources/application.yaml deleted file mode 100644 index f894d72..0000000 --- a/Spring BFF/BFF/src/main/resources/application.yaml +++ /dev/null @@ -1,137 +0,0 @@ -# Custom properties to ease configuration overrides -# on command-line or IDE launch configurations -# more here: -# https://github.com/eugenp/tutorials/blob/master/spring-security-modules/spring-security-oauth2-bff/backend/bff/src/main/resources/application.yml - -#**********************************************************************************************************************# -#***************************************************** VARIABLES ******************************************************# -#**********************************************************************************************************************# - -dse-servers: - # server settings - scheme: ${SCHEME} - hostname: ${HOST} - - # reverse proxy server - reverse-proxy-host: ${REVERSE_PROXY_HOST} - reverse-proxy-port: ${REVERSE_PROXY_PORT} - - # bff server - bff-server-port: ${BFF_SERVER_PORT} - bff-server-prefix: ${BFF_SERVER_PREFIX} - - # resource server - resource-server-port: ${RESOURCE_SERVER_PORT} - resource-server-prefix: ${RESOURCE_SERVER_PREFIX} - - # auth-0 authorization server - auth0-auth-registration-id: ${AUTH0_SERVER_REG_ID} - auth0-issuer-uri: ${AUTH0_SERVER_ISSUER_URI} - - # in-house authorization server - in-house-auth-server-prefix: ${IN_HOUSE_AUTH_SERVER_PREFIX} - in-house-auth-registration-id: ${IN_HOUSE_AUTH_SERVER_REG_ID} - -#**********************************************************************************************************************# -#************************************************** SPRING SETTINGS ***************************************************# -#**********************************************************************************************************************# - -# default spring settings -spring: - # application settings - application: - name: BFFApplication - # profile settings - profiles: - active: dev - # lifecycle settings - lifecycle: - timeout-per-shutdown-phase: ${TIMEOUT_SHUTDOWN} - # main settings - main: - allow-bean-definition-overriding: true - # session redis configurations - session: - redis: - namespace: ${REDIS_NAMESPACE} - repository-type: indexed - flush-mode: on-save - expired-sessions-namespace: ${REDIS_EXPIRED_SESSIONS_NAMESPACE} - timeout: ${TIMEOUT_SESSION} - # data configurations - data: - # azure redis cache settings - redis: - host: ${REDIS_HOST} - password: ${REDIS_PASSWORD} - port: ${REDIS_PORT} - # security configurations - security: - oauth2: - client: - registration: - # oauth2.0 client registrations - (for auth0 auth server) - auth0: - client-id: ${AUTH0_SERVER_CLIENT_ID} - client-secret: ${AUTH0_SERVER_CLIENT_SECRET} - # oauth2.0 client registrations - (for in-house auth server) - in-house-auth: - client-id: ${IN_HOUSE_AUTH_SERVER_CLIENT_ID} - client-secret: ${IN_HOUSE_AUTH_SERVER_CLIENT_SECRET} - -#**********************************************************************************************************************# -#************************************************** SERVER SETTINGS ***************************************************# -#**********************************************************************************************************************# - -# default server settings -server: - address: ${LOCALHOST} - port: ${BFF_SERVER_PORT} - ssl: - enabled: false - reactive: - session: - timeout: ${TIMEOUT_SESSION} - -#**********************************************************************************************************************# -#*********************************************** MANAGEMENT SETTINGS **************************************************# -#**********************************************************************************************************************# - -# endpoint settings -management: - endpoint: - health: - probes: - enabled: true - endpoints: - web: - exposure: - include: health,info - health: - livenessstate: - enabled: true - readinessstate: - enabled: true - -#**********************************************************************************************************************# -#************************************************ LOGGING SETTINGS ****************************************************# -#**********************************************************************************************************************# - -# logging configurations -logging: - level: - root: INFO - org: - springframework: - boot: INFO - security: - root: TRACE - oauth2: DEBUG - web: INFO - io: - lettuce: - core: INFO - -#**********************************************************************************************************************# -#************************************************** END OF YAML *******************************************************# -#**********************************************************************************************************************# \ No newline at end of file diff --git a/Spring BFF/BFF/src/test/kotlin/com/frontiers/bff/BffApplicationTests.kt b/Spring BFF/BFF/src/test/kotlin/com/frontiers/bff/BffApplicationTests.kt deleted file mode 100644 index c72581b..0000000 --- a/Spring BFF/BFF/src/test/kotlin/com/frontiers/bff/BffApplicationTests.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.frontiers.bff - -//import org.junit.jupiter.api.Test -//import org.springframework.boot.test.context.SpringBootTest -// -//@SpringBootTest -//class BffApplicationTests { -// -// @Test -// fun contextLoads() { -// } -// -//}