From 2b5e12b27c81353e680256c793703f3c4944c2c4 Mon Sep 17 00:00:00 2001
From: ch4mpy
Date: Tue, 21 Nov 2023 22:29:31 -1000
Subject: [PATCH] gh-155 Configurable HTTP status in OAuth2 flows responses
---
README.MD | 22 ++-
.../official_bff/SecurityConf.java | 149 +++++++++++++++---
...itional-spring-configuration-metadata.json | 12 ++
.../src/main/resources/application.yml | 16 +-
.../src/main/resources/application.yml | 4 +-
.../c4_bff/SecurityConf.java | 28 ----
.../bff-c4/src/main/resources/application.yml | 27 +++-
.../src/main/resources/application.yml | 4 +-
.../SpringAddonsOidcClientProperties.java | 27 ++++
.../C4Oauth2ServerRedirectStrategy.java | 53 +++++++
.../ReactiveSpringAddonsOidcClientBeans.java | 50 +++++-
...pringAddonsServerLogoutSuccessHandler.java | 17 +-
.../client/C4Oauth2RedirectStrategy.java | 45 ++++++
.../SpringAddonsLogoutSuccessHandler.java | 23 ++-
.../client/SpringAddonsOidcClientBeans.java | 66 +++++++-
15 files changed, 452 insertions(+), 91 deletions(-)
create mode 100644 spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/C4Oauth2ServerRedirectStrategy.java
create mode 100644 spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/C4Oauth2RedirectStrategy.java
diff --git a/README.MD b/README.MD
index 8b4961543..a7f5a7203 100644
--- a/README.MD
+++ b/README.MD
@@ -1,8 +1,6 @@
You can now **test your OAuth2 / OpenID knowledge with a dedicated quiz** available at [https://quiz.c4-soft.com/ui/quizzes](https://quiz.c4-soft.com/ui/quizzes) before you rush into configuring your applications.
-7.x is a break through in usability: all 6 `spring-addons` Boot starters are merged into a single one: [`com.c4-soft.springaddons:spring-addons-starter-oidc`](https://repo1.maven.org/maven2/com/c4-soft/springaddons/spring-addons-starter-oidc/), and so are 4 of the test libs: [`com.c4-soft.springaddons:spring-addons-starter-oidc-test`](https://repo1.maven.org/maven2/com/c4-soft/springaddons/spring-addons-starter-oidc-test/). To use the test annotations without the starter, the dependency is unchanged: [`com.c4-soft.springaddons:spring-addons-oauth2-test`](https://repo1.maven.org/maven2/com/c4-soft/springaddons/spring-addons-oauth2-test/).
-
-Please follow the [migration guide](https://github.com/ch4mpy/spring-addons/blob/master/7.0.0-migration-guide.md) to move from `6.x` to `7.1.1`. There is no urge to do so on existing projects as 6.2.x patches should be published until the end of 2023.
+Please follow the [migration guide](https://github.com/ch4mpy/spring-addons/blob/master/7.0.0-migration-guide.md) to move from `6.x` to `7.x`.
All samples and tutorials sources are migrated to latest starter and test annotations, but some READMEs might still need a refresh. Please make sure you refer to source code for up-to-date configuration.
@@ -426,7 +424,7 @@ These starters are designed to push auto-configuration one step further. In most
I could forget to update README before releasing, so please refer to [maven central](https://repo1.maven.org/maven2/com/c4-soft/springaddons/spring-addons/) to pick latest available release
```xml
- 7.1.14
+ 7.1.15
@@ -462,6 +460,22 @@ I could forget to update README before releasing, so please refer to [maven cent
### 5.1. `7.x` Branch
+#### `7.1.15`
+- [gh-155](https://github.com/ch4mpy/spring-addons/issues/155) Configurable HTTP status for responses to authorization_code flow initiation, authorization-code callback and logout. This makes BFF configuration easier for single page and mobile applications. Default OAuth2 response status (`302 Found`) can be overriden with:
+```yaml
+com:
+ c4-soft:
+ springaddons:
+ oidc:
+ ops:
+ client:
+ oauth2-redirections:
+ pre-authorization-code: FOUND
+ post-authorization-code: FOUND
+ rp-initiated-logout: ACCEPTED
+```
+A per-request override can be done by setting `X-RESPONSE-STATUS` header with either a status code or label (for instance, both `201` and `ACCEPTED` are accepted as value).
+
#### `7.1.14`
- update CSRF configuration for SPAs as instructed by spring-security team in https://github.com/spring-projects/spring-security/issues/14125
diff --git a/samples/tutorials/bff/backend/official/bff-official/src/main/java/com/c4_soft/dzone_oauth2_spring/official_bff/SecurityConf.java b/samples/tutorials/bff/backend/official/bff-official/src/main/java/com/c4_soft/dzone_oauth2_spring/official_bff/SecurityConf.java
index b0438d79b..064b079f6 100644
--- a/samples/tutorials/bff/backend/official/bff-official/src/main/java/com/c4_soft/dzone_oauth2_spring/official_bff/SecurityConf.java
+++ b/samples/tutorials/bff/backend/official/bff-official/src/main/java/com/c4_soft/dzone_oauth2_spring/official_bff/SecurityConf.java
@@ -2,7 +2,11 @@
import static org.springframework.security.config.Customizer.withDefaults;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Value;
@@ -14,26 +18,34 @@
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.WebFilterExchange;
-import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
-import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
+import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;
import org.springframework.security.web.server.csrf.CsrfToken;
+import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
import org.springframework.security.web.server.csrf.XorServerCsrfTokenRequestAttributeHandler;
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.util.StringUtils;
+import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
+import org.springframework.web.util.UriComponentsBuilder;
+import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;
@Configuration
@@ -42,18 +54,18 @@ public class SecurityConf {
/**
*
- * Security filter-chain for resources needing sessions with CSRF protection enabled and CSRF token cookie accessible to Angular
- * application.
+ * Security filter-chain for resources needing sessions with CSRF protection enabled and CSRF token cookie accessible to Angular application.
*
*
* It is defined with low order (high precedence) and security-matcher to limit the resources it applies to.
*
- *
+ *
* @param http
* @param clientRegistrationRepository
* @param securityMatchers
* @param permitAll
* @return
+ * @throws URISyntaxException
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
@@ -61,31 +73,40 @@ SecurityWebFilterChain clientFilterCHain(
ServerHttpSecurity http,
ServerProperties serverProperties,
ReactiveClientRegistrationRepository clientRegistrationRepository,
+ @Value("${gateway-uri}") URI gatewayUri,
@Value("${client-security-matchers:[]}") String[] securityMatchers,
@Value("${client-permit-all:[]}") String[] permitAll,
- @Value("${post-logout-redirect-uri}") String postLogoutRedirectUri) {
+ @Value("${pre-authorization-status:FOUND}") HttpStatus preAuthorizationStatus,
+ @Value("${post-authorization-status:FOUND}") HttpStatus postAuthorizationStatus,
+ @Value("${post-logout-redirect-uri}") String postLogoutRedirectUri)
+ throws URISyntaxException {
// Apply this filter-chain only to resources needing sessions
final var clientRoutes =
Stream.of(securityMatchers).map(PathPatternParserServerWebExchangeMatcher::new).map(ServerWebExchangeMatcher.class::cast).toList();
http.securityMatcher(new OrServerWebExchangeMatcher(clientRoutes));
- // Set post-login URI to Angular app (login being successful or not)
+ // The following handlers answer with NO_CONTENT HTTP status so that single page and mobile apps can handle the redirection by themselves
http.oauth2Login(login -> {
- login.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/ui/"));
- login.authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/ui/"));
+ login.authorizationRedirectStrategy(new C4OAuth2ServerRedirectStrategy(preAuthorizationStatus));
+
+ // Set post-login URI to Angular app (login being successful or not)
+ final var uiUri = UriComponentsBuilder.fromUri(gatewayUri).path("/ui/").build().toUri();
+ login.authenticationSuccessHandler(new C4Oauth2ServerAuthenticationSuccessHandler(postAuthorizationStatus, uiUri));
+ login.authenticationFailureHandler(new C4Oauth2ServerAuthenticationFailureHandler(postAuthorizationStatus, uiUri));
});
- // Keycloak fully complies with RP-Initiated Logout
+ // Keycloak fully complies with RP-Initiated Logout but we need an answer in the 2xx range for single page and mobile apps to handle the redirection by
+ // themselves
+ // The following is a wrapper around the OidcClientInitiatedServerLogoutSuccessHandler to change the response status.
http.logout(logout -> {
- logout.logoutSuccessHandler(new AngularLogoutSucessHandler(clientRegistrationRepository, postLogoutRedirectUri));
+ logout.logoutSuccessHandler(new SpaLogoutSucessHandler(clientRegistrationRepository, postLogoutRedirectUri));
});
// Sessions being necessary, configure CSRF protection to work with Angular.
// Note the csrfCookieWebFilter below which actually attaches the CSRF token cookie to responses
http.csrf(csrf -> {
- var delegate = new XorServerCsrfTokenRequestAttributeHandler();
- csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(delegate::handle);
+ csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler());
});
// If SSL enabled, disable http (https only)
@@ -102,6 +123,9 @@ SecurityWebFilterChain clientFilterCHain(
return http.build();
}
+ /**
+ * @return second half of CSRF handling for SPAs
+ */
@Bean
WebFilter csrfCookieWebFilter() {
return (exchange, chain) -> {
@@ -115,10 +139,10 @@ WebFilter csrfCookieWebFilter() {
* Security filter-chain for resources for which sessions are not needed.
*
*
- * It is defined with lower precedence (higher order) than the client filter-chain and no security matcher => this one acts as default for
- * all requests that do not match the client filter-chain secutiy-matcher.
+ * It is defined with lower precedence (higher order) than the client filter-chain and no security matcher => this one acts as default for all requests that
+ * do not match the client filter-chain secutiy-matcher.
*
- *
+ *
* @param http
* @param serverProperties
* @param permitAll
@@ -166,23 +190,102 @@ SecurityWebFilterChain resourceServerFilterCHain(
return http.build();
}
- static class AngularLogoutSucessHandler implements ServerLogoutSuccessHandler {
+ @RequiredArgsConstructor
+ static class C4OAuth2ServerRedirectStrategy implements ServerRedirectStrategy {
+ private final HttpStatus defaultStatus;
+
+ @Override
+ public Mono sendRedirect(ServerWebExchange exchange, URI location) {
+ return Mono.fromRunnable(() -> {
+ ServerHttpResponse response = exchange.getResponse();
+ // @formatter:off
+ final var status = Optional.ofNullable(exchange.getRequest().getHeaders().get("X-RESPONSE-STATUS"))
+ .map(List::stream)
+ .orElse(Stream.empty())
+ .filter(StringUtils::hasLength)
+ .findAny()
+ .map(statusStr -> {
+ try {
+ final var statusCode = Integer.parseInt(statusStr);
+ return HttpStatus.valueOf(statusCode);
+ } catch(NumberFormatException e) {
+ return HttpStatus.valueOf(statusStr.toUpperCase());
+ }
+ })
+ .orElse(defaultStatus);
+ // @formatter:on
+ response.setStatusCode(status);
+ response.getHeaders().setLocation(location);
+ });
+ }
+
+ }
+
+ static class C4Oauth2ServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
+ private final URI redirectUri;
+ private final C4OAuth2ServerRedirectStrategy redirectStrategy;
+
+ public C4Oauth2ServerAuthenticationSuccessHandler(HttpStatus status, URI location) {
+ this.redirectUri = location;
+ this.redirectStrategy = new C4OAuth2ServerRedirectStrategy(status);
+ }
+
+ @Override
+ public Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
+ return redirectStrategy.sendRedirect(webFilterExchange.getExchange(), redirectUri);
+ }
+
+ }
+
+ static class C4Oauth2ServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
+ private final URI redirectUri;
+ private final C4OAuth2ServerRedirectStrategy redirectStrategy;
+
+ public C4Oauth2ServerAuthenticationFailureHandler(HttpStatus status, URI location) {
+ this.redirectUri = location;
+ this.redirectStrategy = new C4OAuth2ServerRedirectStrategy(status);
+ }
+
+ @Override
+ public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
+ return redirectStrategy.sendRedirect(webFilterExchange.getExchange(), redirectUri);
+ }
+ }
+
+ static class SpaLogoutSucessHandler implements ServerLogoutSuccessHandler {
private final OidcClientInitiatedServerLogoutSuccessHandler delegate;
-
- public AngularLogoutSucessHandler(ReactiveClientRegistrationRepository clientRegistrationRepository, String postLogoutRedirectUri) {
+
+ public SpaLogoutSucessHandler(ReactiveClientRegistrationRepository clientRegistrationRepository, String postLogoutRedirectUri) {
this.delegate = new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository);
this.delegate.setPostLogoutRedirectUri(postLogoutRedirectUri);
}
@Override
- public
- Mono<
- Void>
- onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
+ public Mono onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
return delegate.onLogoutSuccess(exchange, authentication).then(Mono.fromRunnable(() -> {
exchange.getExchange().getResponse().setStatusCode(HttpStatus.ACCEPTED);
}));
}
+ }
+ /**
+ * Adapted from https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa
+ */
+ static final class SpaCsrfTokenRequestHandler extends ServerCsrfTokenRequestAttributeHandler {
+ private final ServerCsrfTokenRequestAttributeHandler delegate = new XorServerCsrfTokenRequestAttributeHandler();
+
+ @Override
+ public void handle(ServerWebExchange exchange, Mono csrfToken) {
+ /*
+ * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of the CsrfToken when it is rendered in the response body.
+ */
+ this.delegate.handle(exchange, csrfToken);
+ }
+
+ @Override
+ public Mono resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) {
+ final var hasHeader = exchange.getRequest().getHeaders().get(csrfToken.getHeaderName()).stream().filter(StringUtils::hasText).count() > 0;
+ return hasHeader ? super.resolveCsrfTokenValue(exchange, csrfToken) : this.delegate.resolveCsrfTokenValue(exchange, csrfToken);
+ }
}
}
diff --git a/samples/tutorials/bff/backend/official/bff-official/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/samples/tutorials/bff/backend/official/bff-official/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index 7929e786d..455fb2b4a 100644
--- a/samples/tutorials/bff/backend/official/bff-official/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/samples/tutorials/bff/backend/official/bff-official/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -58,5 +58,17 @@
"name": "post-logout-redirect-uri",
"type": "java.lang.String",
"description": "Where to redirect the user after he logged out from the authorization server (probably the UI URI on the gateway)"
+ },
+ {
+ "name": "pre-authorization-status",
+ "type": "org.springframework.http.HttpStatus",
+ "description": "HTTP status for the 1st response in authorization_code flow, with location to the authorization server authorization endpoint",
+ "defaultValue": "FOUND"
+ },
+ {
+ "name": "post-authorization-status",
+ "type": "org.springframework.http.HttpStatus",
+ "description": "HTTP status for the last response in authorization_code flow, with location back to the UI",
+ "defaultValue": "FOUND"
}
]}
\ No newline at end of file
diff --git a/samples/tutorials/bff/backend/official/bff-official/src/main/resources/application.yml b/samples/tutorials/bff/backend/official/bff-official/src/main/resources/application.yml
index 43d1db7ee..108acaef7 100644
--- a/samples/tutorials/bff/backend/official/bff-official/src/main/resources/application.yml
+++ b/samples/tutorials/bff/backend/official/bff-official/src/main/resources/application.yml
@@ -88,13 +88,23 @@ logging:
boot: DEBUG
---
+spring:
+ config:
+ activate:
+ on-profile: ssl
+
+scheme: https
server:
ssl:
enabled: true
-
+
+---
spring:
config:
activate:
- on-profile: ssl
+ on-profile: mobile
+
+pre-authorization-status: NO_CONTENT
+post-authorization-status: NO_CONTENT
-scheme: https
\ No newline at end of file
+# gateway-uri: ${scheme}://10.0.2.2:${server.port}
\ No newline at end of file
diff --git a/samples/tutorials/bff/backend/official/greeting-api-official/src/main/resources/application.yml b/samples/tutorials/bff/backend/official/greeting-api-official/src/main/resources/application.yml
index b25c4d7c7..2345125f4 100644
--- a/samples/tutorials/bff/backend/official/greeting-api-official/src/main/resources/application.yml
+++ b/samples/tutorials/bff/backend/official/greeting-api-official/src/main/resources/application.yml
@@ -1,3 +1,5 @@
+issuer: https://oidc.c4-soft.com/auth/realms/master
+
server:
port: 7084
ssl:
@@ -13,7 +15,7 @@ spring:
oauth2:
resourceserver:
jwt:
- issuer-uri: https://oidc.c4-soft.com/auth/realms/master
+ issuer-uri: ${issuer}
logging:
level:
diff --git a/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/java/com/c4_soft/dzone_oauth2_spring/c4_bff/SecurityConf.java b/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/java/com/c4_soft/dzone_oauth2_spring/c4_bff/SecurityConf.java
index 52d855f24..6124525d1 100644
--- a/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/java/com/c4_soft/dzone_oauth2_spring/c4_bff/SecurityConf.java
+++ b/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/java/com/c4_soft/dzone_oauth2_spring/c4_bff/SecurityConf.java
@@ -1,35 +1,7 @@
package com.c4_soft.dzone_oauth2_spring.c4_bff;
import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
-import org.springframework.security.web.server.WebFilterExchange;
-import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
-import org.springframework.stereotype.Component;
-
-import com.c4_soft.springaddons.security.oidc.starter.LogoutRequestUriBuilder;
-import com.c4_soft.springaddons.security.oidc.starter.reactive.client.SpringAddonsServerLogoutSuccessHandler;
-
-import reactor.core.publisher.Mono;
@Configuration
public class SecurityConf {
-
- @Component
- static class AngularLogoutSucessHandler implements ServerLogoutSuccessHandler {
- private final SpringAddonsServerLogoutSuccessHandler delegate;
-
- public AngularLogoutSucessHandler(LogoutRequestUriBuilder logoutUriBuilder, ReactiveClientRegistrationRepository clientRegistrationRepo) {
- this.delegate = new SpringAddonsServerLogoutSuccessHandler(logoutUriBuilder, clientRegistrationRepo);
- }
-
- @Override
- public Mono onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
- return delegate.onLogoutSuccess(exchange, authentication).then(Mono.fromRunnable(() -> {
- exchange.getExchange().getResponse().setStatusCode(HttpStatus.ACCEPTED);
- }));
- }
-
- }
}
diff --git a/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/resources/application.yml b/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/resources/application.yml
index 00ad79b39..bcd1bc2e1 100644
--- a/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/resources/application.yml
+++ b/samples/tutorials/bff/backend/with-c4-soft/bff-c4/src/main/resources/application.yml
@@ -88,6 +88,8 @@ com:
login-path: /ui/
post-login-redirect-path: /ui/
post-logout-redirect-path: /ui/
+ oauth2-redirections:
+ rp-initiated-logout: NO_CONTENT
resourceserver:
permit-all:
- /
@@ -106,16 +108,31 @@ logging:
level:
org:
springframework:
- security: DEBUG
+ security: TRACE
---
+spring:
+ config:
+ activate:
+ on-profile: ssl
+
+scheme: https
server:
ssl:
enabled: true
-
+
+---
spring:
config:
activate:
- on-profile: ssl
-
-scheme: https
\ No newline at end of file
+ on-profile: mobile
+com:
+ c4-soft:
+ springaddons:
+ oidc:
+ client:
+ oauth2-redirections:
+ pre-authorization-code: NO_CONTENT
+ post-authorization-code: NO_CONTENT
+
+# gateway-uri: ${scheme}://10.0.2.2:${server.port}
\ No newline at end of file
diff --git a/samples/tutorials/bff/backend/with-c4-soft/greeting-api-c4/src/main/resources/application.yml b/samples/tutorials/bff/backend/with-c4-soft/greeting-api-c4/src/main/resources/application.yml
index b646dff5d..e4a85fbf1 100644
--- a/samples/tutorials/bff/backend/with-c4-soft/greeting-api-c4/src/main/resources/application.yml
+++ b/samples/tutorials/bff/backend/with-c4-soft/greeting-api-c4/src/main/resources/application.yml
@@ -1,3 +1,5 @@
+issuer: https://oidc.c4-soft.com/auth/realms/master
+
server:
port: 7084
ssl:
@@ -8,7 +10,7 @@ com:
springaddons:
oidc:
ops:
- - iss: https://oidc.c4-soft.com/auth/realms/master
+ - iss: ${issuer}
authorities:
- path: $.realm_access.roles
username-claim: preferred_username
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcClientProperties.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcClientProperties.java
index a82fcc0d0..37c0e2a7f 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcClientProperties.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcClientProperties.java
@@ -7,6 +7,7 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.http.HttpStatus;
import org.springframework.web.util.UriComponentsBuilder;
import lombok.Data;
@@ -56,6 +57,12 @@ public class SpringAddonsOidcClientProperties {
*/
private Optional postLoginRedirectPath = Optional.empty();
+ /**
+ * 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, ...).
+ */
+ private OAuth2RedirectionProperties oauth2Redirections = new OAuth2RedirectionProperties();
+
public URI getPostLoginRedirectHost() {
return postLoginRedirectHost.orElse(clientUri);
}
@@ -192,6 +199,26 @@ public static class RequestParam {
private String value;
}
+ @ConfigurationProperties
+ @Data
+ public static class OAuth2RedirectionProperties {
+
+ /**
+ * Status for the 1st response in authorization code flow, with location to get authorization code from authorization server
+ */
+ private HttpStatus preAuthorizationCode = HttpStatus.FOUND;
+
+ /**
+ * Status for the response after authorization code, with location to the UI
+ */
+ private HttpStatus postAuthorizationCode = HttpStatus.FOUND;
+
+ /**
+ * Status for the response after BFF logout, with location to authorization server logout endpoint
+ */
+ private HttpStatus rpInitiatedLogout = HttpStatus.FOUND;
+ }
+
public Optional getLogoutProperties(String clientRegistrationId) {
return Optional.ofNullable(oauth2Logout.get(clientRegistrationId));
}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/C4Oauth2ServerRedirectStrategy.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/C4Oauth2ServerRedirectStrategy.java
new file mode 100644
index 000000000..6985c67c1
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/C4Oauth2ServerRedirectStrategy.java
@@ -0,0 +1,53 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.client;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.security.web.server.ServerRedirectStrategy;
+import org.springframework.util.StringUtils;
+import org.springframework.web.server.ServerWebExchange;
+
+import lombok.RequiredArgsConstructor;
+import reactor.core.publisher.Mono;
+
+/**
+ * A redirect strategy that might not actually redirect: the HTTP status is taken from com.c4-soft.springaddons.oidc.client.oauth2-redirect-status property.
+ * User-agents will auto redirect only if the status is in 3xx range. 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 potentially by clearing some headers.
+ *
+ * @author Jerome Wacongne ch4mp@c4-soft.com
+ */
+@RequiredArgsConstructor
+public class C4Oauth2ServerRedirectStrategy implements ServerRedirectStrategy {
+ private final HttpStatus defaultStatus;
+
+ @Override
+ public Mono sendRedirect(ServerWebExchange exchange, URI location) {
+ return Mono.fromRunnable(() -> {
+ ServerHttpResponse response = exchange.getResponse();
+ // @formatter:off
+ final var status = Optional.ofNullable(exchange.getRequest().getHeaders().get("X-RESPONSE-STATUS"))
+ .map(List::stream)
+ .orElse(Stream.empty())
+ .filter(StringUtils::hasLength)
+ .findAny()
+ .map(statusStr -> {
+ try {
+ final var statusCode = Integer.parseInt(statusStr);
+ return HttpStatus.valueOf(statusCode);
+ } catch(NumberFormatException e) {
+ return HttpStatus.valueOf(statusStr.toUpperCase());
+ }
+ })
+ .orElse(defaultStatus);
+ // @formatter:on
+ response.setStatusCode(status);
+ response.getHeaders().setLocation(location);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientBeans.java
index c1beb306a..dc165e02d 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientBeans.java
@@ -1,5 +1,6 @@
package com.c4_soft.springaddons.security.oidc.starter.reactive.client;
+import java.net.URI;
import java.util.stream.Stream;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -12,13 +13,16 @@
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
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.WebFilterExchange;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
-import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
-import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
+import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
@@ -134,10 +138,9 @@ SecurityWebFilterChain clientFilterChain(
http.oauth2Login(oauth2 -> {
oauth2.authorizationRequestResolver(authorizationRequestResolver);
addonsProperties.getClient().getPostLoginRedirectUri().ifPresent(postLoginRedirectUri -> {
- oauth2.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler(postLoginRedirectUri.toString()));
- });
- addonsProperties.getClient().getLoginPath().ifPresent(loginPath -> {
- oauth2.authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler(UriComponentsBuilder.fromUri(addonsProperties.getClient().getClientUri()).path(loginPath).build().toString()));
+ oauth2.authorizationRedirectStrategy(new C4Oauth2ServerRedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getPreAuthorizationCode()));
+ oauth2.authenticationSuccessHandler(new C4Oauth2ServerAuthenticationSuccessHandler(addonsProperties, postLoginRedirectUri));
+ oauth2.authenticationFailureHandler(new C4Oauth2ServerAuthenticationFailureHandler(addonsProperties, postLoginRedirectUri));
});
});
@@ -182,8 +185,8 @@ LogoutRequestUriBuilder logoutRequestUriBuilder(SpringAddonsOidcProperties addon
@ConditionalOnMissingBean
@Bean
ServerLogoutSuccessHandler logoutSuccessHandler(LogoutRequestUriBuilder logoutUriBuilder,
- ReactiveClientRegistrationRepository clientRegistrationRepo) {
- return new SpringAddonsServerLogoutSuccessHandler(logoutUriBuilder, clientRegistrationRepo);
+ ReactiveClientRegistrationRepository clientRegistrationRepo, SpringAddonsOidcProperties addonsProperties) {
+ return new SpringAddonsServerLogoutSuccessHandler(logoutUriBuilder, clientRegistrationRepo, addonsProperties);
}
/**
@@ -240,4 +243,35 @@ ServerLogoutHandler logoutHandler() {
new WebSessionServerLogoutHandler(),
new SecurityContextServerLogoutHandler());
}
+
+ static class C4Oauth2ServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
+ private final URI redirectUri;
+ private final C4Oauth2ServerRedirectStrategy redirectStrategy;
+
+ public C4Oauth2ServerAuthenticationSuccessHandler(SpringAddonsOidcProperties addonsProperties, URI redirectUri) {
+ this.redirectUri = redirectUri;
+ this.redirectStrategy = new C4Oauth2ServerRedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getPostAuthorizationCode());
+ }
+
+ @Override
+ public Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
+ return redirectStrategy.sendRedirect(webFilterExchange.getExchange(), redirectUri);
+ }
+
+ }
+
+ static class C4Oauth2ServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
+ private final URI redirectUri;
+ private final C4Oauth2ServerRedirectStrategy redirectStrategy;
+
+ public C4Oauth2ServerAuthenticationFailureHandler(SpringAddonsOidcProperties addonsProperties, URI redirectUri) {
+ this.redirectUri = redirectUri;
+ this.redirectStrategy = new C4Oauth2ServerRedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getPostAuthorizationCode());
+ }
+
+ @Override
+ public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
+ return redirectStrategy.sendRedirect(webFilterExchange.getExchange(), redirectUri);
+ }
+ }
}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerLogoutSuccessHandler.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerLogoutSuccessHandler.java
index 01a93e159..0f07f1cce 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerLogoutSuccessHandler.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerLogoutSuccessHandler.java
@@ -7,7 +7,6 @@
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.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
@@ -15,9 +14,8 @@
import com.c4_soft.springaddons.security.oidc.starter.LogoutRequestUriBuilder;
import com.c4_soft.springaddons.security.oidc.starter.SpringAddonsOAuth2LogoutRequestUriBuilder;
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
-import lombok.Data;
-import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;
/**
@@ -44,12 +42,19 @@
* @see SpringAddonsOAuth2LogoutRequestUriBuilder
* @see SpringAddonsOidcClientProperties
*/
-@Data
-@RequiredArgsConstructor
public class SpringAddonsServerLogoutSuccessHandler implements ServerLogoutSuccessHandler {
private final LogoutRequestUriBuilder uriBuilder;
private final ReactiveClientRegistrationRepository clientRegistrationRepo;
- private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
+ private final ServerRedirectStrategy redirectStrategy;
+
+ public SpringAddonsServerLogoutSuccessHandler(
+ LogoutRequestUriBuilder uriBuilder,
+ ReactiveClientRegistrationRepository clientRegistrationRepo,
+ SpringAddonsOidcProperties addonsProperties) {
+ this.uriBuilder = uriBuilder;
+ this.clientRegistrationRepo = clientRegistrationRepo;
+ this.redirectStrategy = new C4Oauth2ServerRedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getRpInitiatedLogout());
+ }
@Override
public Mono onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/C4Oauth2RedirectStrategy.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/C4Oauth2RedirectStrategy.java
new file mode 100644
index 000000000..3711f5692
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/C4Oauth2RedirectStrategy.java
@@ -0,0 +1,45 @@
+package com.c4_soft.springaddons.security.oidc.starter.synchronised.client;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.web.RedirectStrategy;
+import org.springframework.util.StringUtils;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * A redirect strategy that might not actually redirect: the HTTP status is taken from com.c4-soft.springaddons.oidc.client.oauth2-redirect-status property.
+ * User-agents will auto redirect only if the status is in 3xx range. 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 potentially by clearing some headers.
+ *
+ * @author Jerome Wacongne ch4mp@c4-soft.com
+ */
+@RequiredArgsConstructor
+public class C4Oauth2RedirectStrategy implements RedirectStrategy {
+ private final HttpStatus defaultStatus;
+
+ @Override
+ public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String location) throws IOException {
+ // @formatter:off
+ final var status = Optional.ofNullable(request.getHeader("X-RESPONSE-STATUS"))
+ .filter(StringUtils::hasLength)
+ .map(statusStr -> {
+ try {
+ final var statusCode = Integer.parseInt(statusStr);
+ return HttpStatus.valueOf(statusCode);
+ } catch(NumberFormatException e) {
+ return HttpStatus.valueOf(statusStr.toUpperCase());
+ }
+ })
+ .orElse(defaultStatus);
+ // @formatter:on
+ response.setStatus(status.value());
+ response.setHeader(HttpHeaders.LOCATION, location);
+ }
+
+}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java
index 1e3b0a541..b0595d51f 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java
@@ -1,5 +1,7 @@
package com.c4_soft.springaddons.security.oidc.starter.synchronised.client;
+import java.io.IOException;
+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@@ -10,12 +12,12 @@
import com.c4_soft.springaddons.security.oidc.starter.LogoutRequestUriBuilder;
import com.c4_soft.springaddons.security.oidc.starter.SpringAddonsOAuth2LogoutRequestUriBuilder;
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
+import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
-import lombok.Data;
import lombok.EqualsAndHashCode;
-import lombok.RequiredArgsConstructor;
/**
*
@@ -41,12 +43,20 @@
* @see SpringAddonsOAuth2LogoutRequestUriBuilder
* @see SpringAddonsOidcClientProperties
*/
-@Data
-@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SpringAddonsLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
private final LogoutRequestUriBuilder uriBuilder;
private final ClientRegistrationRepository clientRegistrationRepository;
+ private final C4Oauth2RedirectStrategy redirectStrategy;
+
+ public SpringAddonsLogoutSuccessHandler(
+ LogoutRequestUriBuilder uriBuilder,
+ ClientRegistrationRepository clientRegistrationRepository,
+ SpringAddonsOidcProperties addonsProperties) {
+ this.uriBuilder = uriBuilder;
+ this.clientRegistrationRepository = clientRegistrationRepository;
+ this.redirectStrategy = new C4Oauth2RedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getRpInitiatedLogout());
+ }
@Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
@@ -56,4 +66,9 @@ protected String determineTargetUrl(HttpServletRequest request, HttpServletRespo
}
return null;
}
+
+ @Override
+ public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+ this.redirectStrategy.sendRedirect(request, response, determineTargetUrl(request, response));
+ }
}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientBeans.java
index 0c556745a..ccb5672e3 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientBeans.java
@@ -1,5 +1,8 @@
package com.c4_soft.springaddons.security.oidc.starter.synchronised.client;
+import java.io.IOException;
+import java.net.URI;
+
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -12,12 +15,15 @@
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
-import org.springframework.web.util.UriComponentsBuilder;
import com.c4_soft.springaddons.security.oidc.starter.ClaimSetAuthoritiesConverter;
import com.c4_soft.springaddons.security.oidc.starter.ConfigurableClaimSetAuthoritiesConverter;
@@ -29,6 +35,9 @@
import com.c4_soft.springaddons.security.oidc.starter.synchronised.ServletConfigurationSupport;
import com.c4_soft.springaddons.security.oidc.starter.synchronised.SpringAddonsOidcBeans;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
/**
@@ -111,12 +120,14 @@ SecurityFilterChain springAddonsClientFilterChain(
http.securityMatcher(addonsProperties.getClient().getSecurityMatchers());
http.oauth2Login(login -> {
- login.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint.authorizationRequestResolver(authorizationRequestResolver));
- addonsProperties.getClient().getLoginPath().ifPresent(loginPath -> {
- login.loginPage(UriComponentsBuilder.fromUri(addonsProperties.getClient().getClientUri()).path(loginPath).build().toString());
- });
+ login.authorizationEndpoint(authorizationEndpoint -> {
+ authorizationEndpoint.authorizationRedirectStrategy(new C4Oauth2RedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getPreAuthorizationCode()));
+ authorizationEndpoint.authorizationRequestResolver(authorizationRequestResolver);
+ });
+ addonsProperties.getClient().getLoginPath().ifPresent(login::loginPage);
addonsProperties.getClient().getPostLoginRedirectUri().ifPresent(postLoginRedirectUri -> {
- login.defaultSuccessUrl(postLoginRedirectUri.toString(), true);
+ login.successHandler(new C4Oauth2AuthenticationSuccessHandler(addonsProperties, postLoginRedirectUri));
+ login.failureHandler(new C4Oauth2AuthenticationFailureHandler(addonsProperties, postLoginRedirectUri));
});
});
@@ -168,12 +179,16 @@ LogoutRequestUriBuilder logoutRequestUriBuilder(SpringAddonsOidcProperties addon
*
* @param logoutRequestUriBuilder delegate doing the smart job
* @param clientRegistrationRepository
+ * @param addonsProperties
* @return {@link SpringAddonsLogoutSuccessHandler}
*/
@ConditionalOnMissingBean
@Bean
- LogoutSuccessHandler logoutSuccessHandler(LogoutRequestUriBuilder logoutRequestUriBuilder, ClientRegistrationRepository clientRegistrationRepository) {
- return new SpringAddonsLogoutSuccessHandler(logoutRequestUriBuilder, clientRegistrationRepository);
+ LogoutSuccessHandler logoutSuccessHandler(
+ LogoutRequestUriBuilder logoutRequestUriBuilder,
+ ClientRegistrationRepository clientRegistrationRepository,
+ SpringAddonsOidcProperties addonsProperties) {
+ return new SpringAddonsLogoutSuccessHandler(logoutRequestUriBuilder, clientRegistrationRepository, addonsProperties);
}
/**
@@ -194,4 +209,39 @@ ClientExpressionInterceptUrlRegistryPostProcessor clientAuthorizePostProcessor()
ClientHttpSecurityPostProcessor clientHttpPostProcessor() {
return http -> http;
}
+
+ static class C4Oauth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
+ private final String redirectUri;
+ private final C4Oauth2RedirectStrategy redirectStrategy;
+
+ public C4Oauth2AuthenticationSuccessHandler(SpringAddonsOidcProperties addonsProperties, URI redirectUri) {
+ this.redirectUri = redirectUri.toString();
+ this.redirectStrategy = new C4Oauth2RedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getPostAuthorizationCode());
+ }
+
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+ throws IOException,
+ ServletException {
+ redirectStrategy.sendRedirect(request, response, redirectUri);
+
+ }
+ }
+
+ static class C4Oauth2AuthenticationFailureHandler implements AuthenticationFailureHandler {
+ private final String redirectUri;
+ private final C4Oauth2RedirectStrategy redirectStrategy;
+
+ public C4Oauth2AuthenticationFailureHandler(SpringAddonsOidcProperties addonsProperties, URI redirectUri) {
+ this.redirectUri = redirectUri.toString();
+ this.redirectStrategy = new C4Oauth2RedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getPostAuthorizationCode());
+ }
+
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
+ throws IOException,
+ ServletException {
+ redirectStrategy.sendRedirect(request, response, redirectUri);
+ }
+ }
}
\ No newline at end of file