-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
50435a8
commit b45190d
Showing
56 changed files
with
5,250 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.example.bff | ||
|
||
import org.springframework.boot.autoconfigure.SpringBootApplication | ||
import org.springframework.boot.runApplication | ||
|
||
@SpringBootApplication | ||
class BFFApplication | ||
|
||
fun main(args: Array<String>) { | ||
|
||
// run SpringBoot application | ||
runApplication<BFFApplication>(*args) | ||
|
||
} |
31 changes: 31 additions & 0 deletions
31
Spring BFF/bff/api/controllers/BackChannelLogoutController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.example.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 ******************************************************/ | ||
/**********************************************************************************************************************/ | ||
|
||
@RestController | ||
@RequestMapping("/logout/connect/back-channel/in-house-auth-server") | ||
class BackChannelLogoutController { | ||
|
||
@PostMapping | ||
fun handleLogout(@RequestBody logoutRequest: Map<String, Any>): ResponseEntity<Void> { | ||
// 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 ***************************************************/ | ||
/**********************************************************************************************************************/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.example.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<String, String> { | ||
return mapOf("message" to "Gateway fallback occured") | ||
} | ||
} | ||
|
||
/**********************************************************************************************************************/ | ||
/**************************************************** END OF KOTLIN ***************************************************/ | ||
/**********************************************************************************************************************/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.example.bff.api.controllers | ||
|
||
import com.c4_soft.springaddons.security.oidc.starter.reactive.client.SpringAddonsOauth2ServerRedirectStrategy | ||
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties | ||
import com.example.bff.props.ServerProperties | ||
import com.fasterxml.jackson.annotation.JsonProperty | ||
import jakarta.validation.constraints.NotEmpty | ||
import org.springframework.beans.factory.annotation.Value | ||
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<LoginOptionDto>? = null | ||
|
||
@GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) | ||
fun getLoginOptions(): Mono<List<LoginOptionDto>> { | ||
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 } | ||
.map { registration -> | ||
// client name | ||
val label = registration.clientName | ||
// internal oauth2 request redirection filter | ||
val oauth2Redirection = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI | ||
// internal endpoint that redirects to the auth server | ||
val loginUri = "${serverProperties.bffUri}$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 ***************************************************/ | ||
/**********************************************************************************************************************/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
package com.example.bff.auth | ||
|
||
import com.example.bff.auth.configurations.ClientConfigurationSupport | ||
import com.example.bff.auth.configurations.postprocessors.ClientAuthorizeExchangeSpecPostProcessor | ||
import com.example.bff.auth.configurations.postprocessors.ClientReactiveHttpSecurityPostProcessor | ||
import com.example.bff.auth.cors.CORSConfig | ||
import com.example.bff.auth.csrf.CsrfProtectionMatcher | ||
import com.example.bff.auth.filters.csrf.CsrfWebCookieFilter | ||
import com.example.bff.auth.handlers.DelegatingAuthenticationSuccessHandler | ||
import com.example.bff.auth.handlers.csrf.SPACsrfTokenRequestHandler | ||
import com.example.bff.auth.handlers.oauth2.OAuth2ServerAuthenticationFailureHandler | ||
import com.example.bff.auth.handlers.oauth2.OAuth2ServerLogoutSuccessHandler | ||
import com.example.bff.auth.handlers.oauth2.PreAuthorizationCodeServerRedirectStrategy | ||
import com.example.bff.auth.handlers.sessions.CustomMaximumSessionsExceededHandler | ||
import com.example.bff.auth.repositories.RedisAuthorizationRequestRepository | ||
import com.example.bff.auth.repositories.authclients.RedisReactiveOAuth2AuthorizedClientService | ||
import com.example.bff.auth.repositories.authclients.RedisServerOAuth2AuthorizedClientRepository | ||
import com.example.bff.auth.repositories.securitycontext.RedisSecurityContextRepository | ||
import com.example.bff.auth.requestcache.ReactiveRequestCache | ||
import com.example.bff.props.* | ||
import com.example.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.security.config.annotation.web.builders.WebSecurity | ||
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer | ||
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.config.web.server.ServerHttpSecurity.LogoutSpec | ||
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.authentication.logout.ServerLogoutHandler | ||
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.session.data.redis.config.annotation.web.server.EnableRedisIndexedWebSession | ||
import org.springframework.web.util.UriComponentsBuilder | ||
import java.net.URI | ||
import java.util.* | ||
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 webSecurityCustomizer(): WebSecurityCustomizer { | ||
return WebSecurityCustomizer { web: WebSecurity -> | ||
web.debug(false) | ||
.ignoring() | ||
.requestMatchers("/favicon.ico") | ||
} | ||
} | ||
|
||
@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, | ||
redisReactiveOAuth2AuthorizedClientService: RedisReactiveOAuth2AuthorizedClientService, | ||
|
||
logoutProperties: LogoutProperties, | ||
logoutHandler: Optional<ServerLogoutHandler>, | ||
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<ServerWebExchangeMatcher> = 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) | ||
} | ||
|
||
// session management | ||
// this is also handled in the success handler, delegatingAuthenticationSuccessHandler | ||
|
||
// 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.authorizedClientService(redisReactiveOAuth2AuthorizedClientService) | ||
} | ||
|
||
// oauth2.0 client | ||
http.oauth2Client {} | ||
|
||
// logout configuration (with relying-party initiated logout) | ||
http.logout { logout: LogoutSpec -> | ||
logoutHandler.ifPresent { handler: ServerLogoutHandler -> | ||
logout.logoutHandler(handler) | ||
} | ||
logout.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.internalLogoutUri) | ||
} | ||
// 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 ***************************************************/ | ||
/**********************************************************************************************************************/ |
Oops, something went wrong.