You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/08/11 12:56:04 UTC
[syncope] 04/04: [SYNCOPE-1579] Finalizing SAML 2.0 logout
This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
commit 8ce9aa6141336ba22044270e60030875dd598b23
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Tue Aug 11 14:55:35 2020 +0200
[SYNCOPE-1579] Finalizing SAML 2.0 logout
---
.travis.yml | 2 +-
.../org/apache/syncope/fit/sra/SAML2SRAITCase.java | 13 +-
.../test/resources/application-saml2.properties | 2 +-
.../org/apache/syncope/sra/SecurityConfig.java | 4 +-
.../NoOpLogoutHandler.java} | 14 +-
...{WebSessionStore.java => NoOpSessionStore.java} | 23 ++--
.../RedirectionActionUtils.java} | 18 +--
.../security/pac4j/ServerWebExchangeContext.java | 29 +---
.../security/saml2/SAML2AnonymousWebFilter.java | 3 +-
.../saml2/SAML2LogoutResponseWebFilter.java | 152 +++++++++++++++++++++
.../sra/security/saml2/SAML2MetadataEndpoint.java | 23 +++-
...r.java => SAML2RequestServerLogoutHandler.java} | 15 +-
.../security/saml2/SAML2SecurityConfigUtils.java | 12 +-
.../saml2/SAML2ServerLogoutSuccessHandler.java | 3 +-
.../SAML2WebSsoAuthenticationRequestWebFilter.java | 28 ++--
.../saml2/SAML2WebSsoAuthenticationWebFilter.java | 25 +---
.../resources/debug/application-debug.properties | 28 ++--
17 files changed, 257 insertions(+), 137 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index f44f586..2beae45 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -120,6 +120,6 @@ jobs:
name: "Integration Tests: SRA and WA"
script: mvn -f fit/wa-reference/pom.xml verify -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Dianal.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true
after_failure:
+ - cat fit/wa-reference/target/log/*
- cat fit/core-reference/target/log/*
- - cat fit/core-reference/target/failsafe-reports/org.apache.syncope.fit.*-output.txt
#####################################################
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
index e4e11f2..2f1ff7c 100644
--- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
@@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -265,17 +264,7 @@ public class SAML2SRAITCase extends AbstractITCase {
get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
response = httpclient.execute(get, context);
- // 3b. post SAML response
- // this is missing as currently WA does not responde with form for SP's SingleLogoutService
+ // 3b. check logout
checkLogout(response);
}
-
- @Override
- protected void checkLogout(final CloseableHttpResponse response) throws IOException {
- assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
-
- String responseBody = EntityUtils.toString(response.getEntity());
- assertTrue(responseBody.contains("Logout successful"));
- assertTrue(responseBody.contains("have successfully logged out of the Central Authentication Service"));
- }
}
diff --git a/fit/wa-reference/src/test/resources/application-saml2.properties b/fit/wa-reference/src/test/resources/application-saml2.properties
index fa9a8e4..70fd4d6 100644
--- a/fit/wa-reference/src/test/resources/application-saml2.properties
+++ b/fit/wa-reference/src/test/resources/application-saml2.properties
@@ -17,7 +17,7 @@
am.type=SAML2
am.saml2.sp.authnrequest.binding=POST
am.saml2.sp.logout.request.binding=POST
-am.saml2.sp.logout.response.binding=POST
+am.saml2.sp.logout.response.binding=REDIRECT
am.saml2.sp.entityId=http://localhost:8080
am.saml2.sp.skew=300
am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
diff --git a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
index 9d9fda1..eeacaef 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
@@ -28,6 +28,7 @@ import org.apache.syncope.sra.security.LogoutRouteMatcher;
import org.apache.syncope.sra.security.oauth2.OAuth2SecurityConfigUtils;
import org.apache.syncope.sra.security.PublicRouteMatcher;
import org.apache.syncope.sra.security.cas.CASSecurityConfigUtils;
+import org.apache.syncope.sra.security.pac4j.NoOpLogoutHandler;
import org.apache.syncope.sra.security.saml2.SAML2BindingType;
import org.apache.syncope.sra.security.saml2.SAML2MetadataEndpoint;
import org.apache.syncope.sra.security.saml2.SAML2SecurityConfigUtils;
@@ -229,11 +230,12 @@ public class SecurityConfig {
cfg.setAuthnRequestSigned(true);
cfg.setSpLogoutRequestSigned(true);
cfg.setAcceptedSkew(env.getProperty("am.saml2.sp.skew", int.class));
+ cfg.setLogoutHandler(new NoOpLogoutHandler());
SAML2Client saml2Client = new SAML2Client(cfg);
saml2Client.setName(AMType.SAML2.name());
saml2Client.setCallbackUrl(env.getProperty("am.saml2.sp.entityId")
- + SAML2WebSsoAuthenticationWebFilter.DEFAULT_FILTER_PROCESSES_URI);
+ + SAML2WebSsoAuthenticationWebFilter.FILTER_PROCESSES_URI);
saml2Client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
saml2Client.init();
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpLogoutHandler.java
similarity index 55%
copy from sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
copy to sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpLogoutHandler.java
index 3557eb5..e370826 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpLogoutHandler.java
@@ -16,18 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.sra.security.saml2;
+package org.apache.syncope.sra.security.pac4j;
-import org.apache.syncope.sra.security.AbstractServerLogoutSuccessHandler;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.web.server.WebFilterExchange;
-import reactor.core.publisher.Mono;
+import org.pac4j.core.logout.handler.LogoutHandler;
-public class SAML2ServerLogoutSuccessHandler extends AbstractServerLogoutSuccessHandler {
+public class NoOpLogoutHandler implements LogoutHandler<ServerWebExchangeContext> {
- @Override
- public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final Authentication authentication) {
- return Mono.just(authentication).
- flatMap(auth -> redirectStrategy.sendRedirect(exchange.getExchange(), getPostLogout(exchange)));
- }
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpSessionStore.java
similarity index 72%
rename from sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
rename to sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpSessionStore.java
index c74336c..e9629f4 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/NoOpSessionStore.java
@@ -20,43 +20,44 @@ package org.apache.syncope.sra.security.pac4j;
import java.util.Optional;
import org.pac4j.core.context.session.SessionStore;
-import org.springframework.web.server.WebSession;
-public class WebSessionStore implements SessionStore<ServerWebExchangeContext> {
+public final class NoOpSessionStore implements SessionStore<ServerWebExchangeContext> {
- private final WebSession webSession;
+ public static final NoOpSessionStore INSTANCE = new NoOpSessionStore();
- public WebSessionStore(final WebSession webSession) {
- this.webSession = webSession;
+ private NoOpSessionStore() {
+ // private constructor for singleton
}
@Override
public String getOrCreateSessionId(final ServerWebExchangeContext context) {
- return this.webSession.getId();
+ return "<NO_KEY>";
}
@Override
public Optional<Object> get(final ServerWebExchangeContext context, final String key) {
- return Optional.ofNullable(this.webSession.getAttribute(key));
+ return Optional.empty();
}
@Override
public void set(final ServerWebExchangeContext context, final String key, final Object value) {
+ // nothing to do
}
@Override
public boolean destroySession(final ServerWebExchangeContext context) {
- return false;
+ return true;
}
@Override
- public Optional<WebSession> getTrackableSession(final ServerWebExchangeContext context) {
- return Optional.ofNullable(this.webSession);
+ public Optional<?> getTrackableSession(final ServerWebExchangeContext context) {
+ return Optional.empty();
}
@Override
public Optional<SessionStore<ServerWebExchangeContext>> buildFromTrackableSession(
- final ServerWebExchangeContext context, final Object trackableSession) {
+ final ServerWebExchangeContext context,
+ final Object trackableSession) {
return Optional.empty();
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/RedirectionActionUtils.java
similarity index 86%
rename from sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
rename to sra/src/main/java/org/apache/syncope/sra/security/pac4j/RedirectionActionUtils.java
index b595d57..c56c23f 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/RedirectionActionUtils.java
@@ -16,27 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.sra.security.saml2;
+package org.apache.syncope.sra.security.pac4j;
-import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
import java.net.URI;
import org.pac4j.core.exception.http.RedirectionAction;
import org.pac4j.core.exception.http.WithContentAction;
import org.pac4j.core.exception.http.WithLocationAction;
-import org.pac4j.saml.client.SAML2Client;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import reactor.core.publisher.Mono;
-abstract class SAML2RequestGenerator {
+public final class RedirectionActionUtils {
- protected final SAML2Client saml2Client;
-
- protected SAML2RequestGenerator(final SAML2Client saml2Client) {
- this.saml2Client = saml2Client;
- }
-
- protected Mono<Void> handle(
+ public static Mono<Void> handle(
final RedirectionAction action,
final ServerWebExchangeContext swec) {
@@ -62,4 +54,8 @@ abstract class SAML2RequestGenerator {
throw new IllegalArgumentException("Unsupported Action: " + action.getClass().getName());
}
}
+
+ private RedirectionActionUtils() {
+ // private constructor for static utility class
+ }
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
index bf22e69..a5ed233 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
@@ -33,52 +33,29 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
-import org.springframework.web.server.WebSession;
import org.springframework.web.util.UriComponentsBuilder;
public class ServerWebExchangeContext implements WebContext {
private final ServerWebExchange exchange;
- private WebSessionStore sessionStore;
-
private MultiValueMap<String, String> form;
private String body;
/**
- * Build a WebFlux context from the current exchange and web session.
- *
- * @param exchange the current exchange
- * @param webSession the current web session
- */
- public ServerWebExchangeContext(final ServerWebExchange exchange, final WebSession webSession) {
- this(exchange, new WebSessionStore(webSession));
- }
-
- /**
- * Build a WebFlux context from the current exhange and from a session store.
+ * Build a WebFlux context from the current exchange.
*
* @param exchange the current exchange
- * @param sessionStore the session store to use
*/
- public ServerWebExchangeContext(
- final ServerWebExchange exchange,
- final WebSessionStore sessionStore) {
-
+ public ServerWebExchangeContext(final ServerWebExchange exchange) {
CommonHelper.assertNotNull("exchange", exchange);
- CommonHelper.assertNotNull("sessionStore", sessionStore);
this.exchange = exchange;
- this.sessionStore = sessionStore;
- }
-
- public WebSessionStore getNativeSessionStore() {
- return this.sessionStore;
}
@Override
public SessionStore<ServerWebExchangeContext> getSessionStore() {
- return this.sessionStore;
+ return NoOpSessionStore.INSTANCE;
}
@Override
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
index 6cf7be5..b5f10c4 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
@@ -36,7 +36,8 @@ public class SAML2AnonymousWebFilter implements WebFilter {
public SAML2AnonymousWebFilter(final PublicRouteMatcher publicRouteMatcher) {
this.matcher = ServerWebExchangeMatchers.matchers(
publicRouteMatcher,
- SessionUtils.authInSession());
+ SessionUtils.authInSession(),
+ SAML2LogoutResponseWebFilter.MATCHER);
}
@Override
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2LogoutResponseWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2LogoutResponseWebFilter.java
new file mode 100644
index 0000000..79733ef
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2LogoutResponseWebFilter.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+package org.apache.syncope.sra.security.saml2;
+
+import java.util.Optional;
+import org.apache.syncope.sra.SessionConfig;
+import org.apache.syncope.sra.security.pac4j.RedirectionActionUtils;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
+import org.pac4j.core.exception.http.OkAction;
+import org.pac4j.core.exception.http.RedirectionAction;
+import org.pac4j.core.util.Pac4jConstants;
+import org.pac4j.saml.client.SAML2Client;
+import org.pac4j.saml.context.SAML2MessageContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.web.server.WebFilterExchange;
+import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+public class SAML2LogoutResponseWebFilter implements WebFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SAML2LogoutResponseWebFilter.class);
+
+ public static final ServerWebExchangeMatcher MATCHER =
+ ServerWebExchangeMatchers.pathMatchers("/logout/saml2/sso");
+
+ private static class ServerWebExchangeLogoutContext extends ServerWebExchangeContext {
+
+ ServerWebExchangeLogoutContext(final ServerWebExchange exchange) {
+ super(exchange);
+ }
+
+ @Override
+ public Optional<String> getRequestParameter(final String name) {
+ return Pac4jConstants.LOGOUT_ENDPOINT_PARAMETER.equals(name)
+ ? Optional.of("true")
+ : super.getRequestParameter(name);
+ }
+ }
+
+ private final SAML2Client saml2Client;
+
+ private final ServerLogoutSuccessHandler logoutSuccessHandler;
+
+ private final CacheManager cacheManager;
+
+ public SAML2LogoutResponseWebFilter(
+ final SAML2Client saml2Client,
+ final SAML2ServerLogoutSuccessHandler logoutSuccessHandler,
+ final CacheManager cacheManager) {
+
+ this.saml2Client = saml2Client;
+ this.logoutSuccessHandler = logoutSuccessHandler;
+ this.cacheManager = cacheManager;
+ }
+
+ private Mono<Void> handleLogoutResponse(
+ final ServerWebExchange exchange, final WebFilterChain chain, final ServerWebExchangeContext swec) {
+
+ try {
+ SAML2MessageContext ctx = saml2Client.getContextProvider().buildContext(swec);
+ saml2Client.getLogoutProfileHandler().receive(ctx);
+ } catch (OkAction e) {
+ LOG.debug("LogoutResponse was actually validated but no postLogoutURL was set", e);
+ } catch (Exception e) {
+ LOG.error("Could not validate LogoutResponse", e);
+ }
+
+ return logoutSuccessHandler.onLogoutSuccess(new WebFilterExchange(exchange, chain), null);
+ }
+
+ private Mono<Void> handleLogoutRequest(
+ final ServerWebExchange exchange, final WebFilterChain chain, final ServerWebExchangeContext swec) {
+
+ return exchange.getSession().
+ switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+ flatMap(session -> {
+ cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
+
+ return session.invalidate().then(Mono.defer(() -> {
+ try {
+ saml2Client.getCredentialsExtractor().extract(swec);
+ } catch (RedirectionAction action) {
+ return RedirectionActionUtils.handle(action, swec);
+ }
+
+ return chain.filter(exchange).then(Mono.empty());
+ }));
+ });
+ }
+
+ private Mono<Void> handleGET(final ServerWebExchange exchange, final WebFilterChain chain) {
+ if (exchange.getRequest().getQueryParams().getFirst("SAMLResponse") != null) {
+ return handleLogoutResponse(exchange, chain, new ServerWebExchangeContext(exchange));
+ } else if (exchange.getRequest().getQueryParams().getFirst("SAMLRequest") != null) {
+ return handleLogoutRequest(exchange, chain, new ServerWebExchangeLogoutContext(exchange));
+ }
+
+ return chain.filter(exchange).then(Mono.empty());
+ }
+
+ private Mono<Void> handlePOST(final ServerWebExchange exchange, final WebFilterChain chain) {
+ return exchange.getFormData().flatMap(form -> {
+ if (form.containsKey("SAMLResponse")) {
+ return handleLogoutResponse(exchange, chain, new ServerWebExchangeContext(exchange).setForm(form));
+ } else if (form.containsKey("SAMLRequest")) {
+ return handleLogoutRequest(exchange, chain, new ServerWebExchangeLogoutContext(exchange).setForm(form));
+ }
+
+ return chain.filter(exchange).then(Mono.empty());
+ });
+ }
+
+ @Override
+ public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
+ return MATCHER.matches(exchange).
+ filter(MatchResult::isMatch).
+ switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+ flatMap(matchResult -> {
+ return exchange.getRequest().getMethod() == HttpMethod.GET
+ ? handleGET(exchange, chain)
+ : exchange.getRequest().getMethod() == HttpMethod.POST
+ ? handlePOST(exchange, chain)
+ : Mono.error(() -> new UnsupportedOperationException(
+ "Unsupported HTTP method: " + exchange.getRequest().getMethod()));
+ });
+ }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
index 229d232..6c8efa6 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
@@ -19,8 +19,12 @@
package org.apache.syncope.sra.security.saml2;
import com.google.common.net.HttpHeaders;
+import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.sra.SecurityConfig;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.pac4j.saml.client.SAML2Client;
+import org.pac4j.saml.exceptions.SAMLException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -38,10 +42,23 @@ public class SAML2MetadataEndpoint {
public static final String METADATA_URL = "/saml2/metadata";
- private final SAML2Client saml2Client;
+ private final String metadata;
public SAML2MetadataEndpoint(final SAML2Client saml2Client) {
- this.saml2Client = saml2Client;
+ EntityDescriptor entityDescriptor = (EntityDescriptor) saml2Client.getServiceProviderMetadataResolver().
+ getEntityDescriptorElement();
+ entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().
+ removeIf(slo -> !saml2Client.getConfiguration().
+ getSpLogoutResponseBindingType().equals(slo.getBinding()));
+ entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS).getSingleLogoutServices().
+ forEach(slo -> slo.setLocation(
+ StringUtils.substringBefore(slo.getLocation(), "?").replace("login", "logout")));
+
+ try {
+ this.metadata = saml2Client.getConfiguration().toMetadataGenerator().getMetadata(entityDescriptor);
+ } catch (Exception e) {
+ throw new SAMLException("Unable to fetch metadata", e);
+ }
}
@GetMapping(produces = { MediaType.APPLICATION_XML_VALUE })
@@ -49,6 +66,6 @@ public class SAML2MetadataEndpoint {
public Mono<ResponseEntity<String>> metadata(final ServerHttpRequest request) {
return Mono.just(ResponseEntity.ok().
header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).
- body(saml2Client.getServiceProviderMetadataResolver().getMetadata()));
+ body(metadata));
}
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestServerLogoutHandler.java
similarity index 83%
rename from sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
rename to sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestServerLogoutHandler.java
index 0959065..973bd20 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestServerLogoutHandler.java
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.sra.security.saml2;
+import org.apache.syncope.sra.security.pac4j.RedirectionActionUtils;
import org.apache.syncope.sra.SessionConfig;
import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
import org.pac4j.saml.client.SAML2Client;
@@ -30,14 +31,16 @@ import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import reactor.core.publisher.Mono;
-public class SAML2ServerLogoutHandler extends SAML2RequestGenerator implements ServerLogoutHandler {
+public class SAML2RequestServerLogoutHandler implements ServerLogoutHandler {
- private static final Logger LOG = LoggerFactory.getLogger(SAML2ServerLogoutHandler.class);
+ private static final Logger LOG = LoggerFactory.getLogger(SAML2RequestServerLogoutHandler.class);
+
+ private final SAML2Client saml2Client;
private final CacheManager cacheManager;
- public SAML2ServerLogoutHandler(final SAML2Client saml2Client, final CacheManager cacheManager) {
- super(saml2Client);
+ public SAML2RequestServerLogoutHandler(final SAML2Client saml2Client, final CacheManager cacheManager) {
+ this.saml2Client = saml2Client;
this.cacheManager = cacheManager;
}
@@ -50,12 +53,12 @@ public class SAML2ServerLogoutHandler extends SAML2RequestGenerator implements S
LOG.debug("Creating SAML2 SP Logout Request for IDP[{}] and Profile[{}]",
saml2Client.getIdentityProviderResolvedEntityId(), credentials.getUserProfile());
- ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange.getExchange(), session);
+ ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange.getExchange());
cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
return session.invalidate().then(
saml2Client.getLogoutAction(swec, credentials.getUserProfile(), null).
- map(action -> handle(action, swec)).
+ map(action -> RedirectionActionUtils.handle(action, swec)).
orElseThrow(() -> new IllegalStateException("No action generated")));
}).onErrorResume(Mono::error);
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
index 63001da..a7be69c 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
@@ -74,14 +74,18 @@ public final class SAML2SecurityConfigUtils {
LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
-
- logoutWebFilter.setLogoutHandler(new SAML2ServerLogoutHandler(saml2Client, cacheManager));
+ logoutWebFilter.setLogoutHandler(new SAML2RequestServerLogoutHandler(saml2Client, cacheManager));
+ logoutWebFilter.setLogoutSuccessHandler((exchange, authentication) -> Mono.empty());
try {
- SAML2ServerLogoutSuccessHandler handler = ApplicationContextUtils.getOrCreateBean(ctx,
+ SAML2ServerLogoutSuccessHandler logoutSuccessHandler = ApplicationContextUtils.getOrCreateBean(
+ ctx,
SAML2ServerLogoutSuccessHandler.class.getName(),
SAML2ServerLogoutSuccessHandler.class);
- logoutWebFilter.setLogoutSuccessHandler(handler);
+
+ SAML2LogoutResponseWebFilter logoutResponseWebFilter =
+ new SAML2LogoutResponseWebFilter(saml2Client, logoutSuccessHandler, cacheManager);
+ builder.and().addFilterAt(logoutResponseWebFilter, SecurityWebFiltersOrder.LOGOUT);
} catch (ClassNotFoundException e) {
LOG.error("While creating instance of {}", SAML2ServerLogoutSuccessHandler.class.getName(), e);
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
index 3557eb5..f10e388 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
@@ -27,7 +27,6 @@ public class SAML2ServerLogoutSuccessHandler extends AbstractServerLogoutSuccess
@Override
public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final Authentication authentication) {
- return Mono.just(authentication).
- flatMap(auth -> redirectStrategy.sendRedirect(exchange.getExchange(), getPostLogout(exchange)));
+ return redirectStrategy.sendRedirect(exchange.getExchange(), getPostLogout(exchange));
}
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
index ab1819c..14a5b8d 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
@@ -18,49 +18,47 @@
*/
package org.apache.syncope.sra.security.saml2;
+import org.apache.syncope.sra.security.pac4j.RedirectionActionUtils;
import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
import org.pac4j.saml.client.SAML2Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
-import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
-public class SAML2WebSsoAuthenticationRequestWebFilter extends SAML2RequestGenerator implements WebFilter {
+public class SAML2WebSsoAuthenticationRequestWebFilter implements WebFilter {
private static final Logger LOG = LoggerFactory.getLogger(SAML2WebSsoAuthenticationRequestWebFilter.class);
public static final String AUTHENTICATE_URL = "/saml2/authenticate";
- private ServerWebExchangeMatcher redirectMatcher = ServerWebExchangeMatchers.pathMatchers(AUTHENTICATE_URL);
+ private static final ServerWebExchangeMatcher MATCHER =
+ ServerWebExchangeMatchers.pathMatchers(AUTHENTICATE_URL);
- public SAML2WebSsoAuthenticationRequestWebFilter(final SAML2Client saml2Client) {
- super(saml2Client);
- }
+ private final SAML2Client saml2Client;
- public void setRedirectMatcher(final ServerWebExchangeMatcher redirectMatcher) {
- Assert.notNull(redirectMatcher, "redirectMatcher cannot be null");
- this.redirectMatcher = redirectMatcher;
+ public SAML2WebSsoAuthenticationRequestWebFilter(final SAML2Client saml2Client) {
+ this.saml2Client = saml2Client;
}
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
- return redirectMatcher.matches(exchange).
- filter(matchResult -> matchResult.isMatch()).
+ return MATCHER.matches(exchange).
+ filter(MatchResult::isMatch).
switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
- flatMap(matchResult -> exchange.getSession()).
- flatMap(session -> {
+ flatMap(matchResult -> {
LOG.debug("Creating SAML2 SP Authentication Request for IDP[{}]",
saml2Client.getIdentityProviderResolvedEntityId());
- ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange, session);
+ ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange);
return saml2Client.getRedirectionAction(swec).
- map(action -> handle(action, swec)).
+ map(action -> RedirectionActionUtils.handle(action, swec)).
orElseThrow(() -> new IllegalStateException("No action generated"));
}).onErrorResume(Mono::error);
}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
index 0cbd9f7..5dc59b3 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
@@ -22,7 +22,6 @@ import java.net.URI;
import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
import org.apache.syncope.sra.session.SessionUtils;
-import org.pac4j.core.util.Pac4jConstants;
import org.pac4j.saml.client.SAML2Client;
import org.pac4j.saml.credentials.SAML2Credentials;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
@@ -32,7 +31,6 @@ import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
-import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.server.ServerWebExchange;
@@ -41,16 +39,12 @@ import reactor.core.publisher.Mono;
public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter {
- public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/saml2/sso";
+ public static final String FILTER_PROCESSES_URI = "/login/saml2/sso";
- private final SAML2Client saml2Client;
+ private static final ServerWebExchangeMatcher MATCHER =
+ ServerWebExchangeMatchers.pathMatchers(FILTER_PROCESSES_URI);
- private ServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher(
- ServerWebExchangeMatchers.pathMatchers(DEFAULT_FILTER_PROCESSES_URI),
- exchange -> exchange.getRequest().getQueryParams().
- containsKey(Pac4jConstants.LOGOUT_ENDPOINT_PARAMETER)
- ? ServerWebExchangeMatcher.MatchResult.notMatch()
- : ServerWebExchangeMatcher.MatchResult.match());
+ private final SAML2Client saml2Client;
public SAML2WebSsoAuthenticationWebFilter(
final ReactiveAuthenticationManager authenticationManager,
@@ -67,10 +61,6 @@ public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter
setAuthenticationSuccessHandler(redirectToInitialRequestURI());
}
- public void setMatcher(final ServerWebExchangeMatcher matcher) {
- this.matcher = matcher;
- }
-
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
return super.filter(exchange, chain).then(Mono.defer(exchange.getResponse()::setComplete));
@@ -85,10 +75,9 @@ public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter
private ServerAuthenticationConverter convertSamlResponse() {
return exchange -> exchange.getFormData().
- flatMap(form -> this.matcher.matches(exchange).
- flatMap(matchResult -> exchange.getSession()).
- flatMap(session -> {
- ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange, session).setForm(form);
+ flatMap(form -> this.MATCHER.matches(exchange).
+ flatMap(matchResult -> {
+ ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange).setForm(form);
SAML2Credentials credentials = saml2Client.getCredentialsExtractor().extract(swec).
orElseThrow(() -> new IllegalStateException("No AuthnResponse found"));
diff --git a/sra/src/test/resources/debug/application-debug.properties b/sra/src/test/resources/debug/application-debug.properties
index b74d3a4..c029337 100644
--- a/sra/src/test/resources/debug/application-debug.properties
+++ b/sra/src/test/resources/debug/application-debug.properties
@@ -30,20 +30,20 @@
#am.oauth2.client.id=oauth2TestClientId
#am.oauth2.client.secret=oauth2TestClientSecret
-#am.type=SAML2
-#am.saml2.sp.authnrequest.binding=POST
-#am.saml2.sp.logout.request.binding=POST
-#am.saml2.sp.logout.response.binding=POST
-#am.saml2.sp.entityId=http://localhost:8080
-#am.saml2.sp.skew=300
-#am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
-#am.saml2.keystore=classpath:/saml.keystore.jks
-#am.saml2.keystore.type=jks
-#am.saml2.keystore.storepass=changeit
-#am.saml2.keystore.keypass=changeit
+am.type=SAML2
+am.saml2.sp.authnrequest.binding=POST
+am.saml2.sp.logout.request.binding=POST
+am.saml2.sp.logout.response.binding=REDIRECT
+am.saml2.sp.entityId=http://localhost:8080
+am.saml2.sp.skew=300
+am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
+am.saml2.keystore=classpath:/saml.keystore.jks
+am.saml2.keystore.type=jks
+am.saml2.keystore.storepass=changeit
+am.saml2.keystore.keypass=changeit
-am.type=CAS
-am.cas.server.name=http://localhost:80
-am.cas.url.prefix=http://localhost:9080/syncope-wa/
+#am.type=CAS
+#am.cas.server.name=http://localhost:80
+#am.cas.url.prefix=http://localhost:9080/syncope-wa/
global.postLogout=http://localhost:8080/logout