diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index bf1367934..46042589e 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -22,6 +22,7 @@ # identityProviderMetadataPath: "/etc/shibui/idp-metadata.xml" # forceServiceProviderMetadataGeneration: false # callbackUrl: "https://localhost:8443/callback" +# postLogoutURL: "https://idp.example.com/idp/profile/Logout" # Must set this to get IDP logout # maximumAuthenticationLifetime: 3600000 # requireAssertedRoleForNewUsers: false # saml2ProfileMapping: diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java index fd018d4b6..5853da065 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfiguration.java @@ -8,6 +8,7 @@ import net.unicon.shibui.pac4j.authenticator.ShibuiSAML2Authenticator; import org.pac4j.core.client.Clients; import org.pac4j.core.config.Config; +import org.pac4j.core.engine.LogoutLogic; import org.pac4j.core.matching.matcher.PathMatcher; import org.pac4j.core.profile.definition.CommonProfileDefinition; import org.pac4j.http.client.direct.HeaderClient; @@ -69,32 +70,39 @@ public Config config(final Pac4jConfigurationProperties pac4jConfigProps, case "SAML2": default: log.info("**** Configuring PAC4J SAML2"); - final SAML2Configuration saml2Config = new SAML2Configuration(); - saml2Config.setKeystorePath(pac4jConfigProps.getKeystorePath()); - saml2Config.setKeystorePassword(pac4jConfigProps.getKeystorePassword()); - saml2Config.setPrivateKeyPassword(pac4jConfigProps.getPrivateKeyPassword()); - saml2Config.setIdentityProviderMetadataPath(pac4jConfigProps.getIdentityProviderMetadataPath()); - saml2Config.setMaximumAuthenticationLifetime(pac4jConfigProps.getMaximumAuthenticationLifetime()); - saml2Config.setServiceProviderEntityId(pac4jConfigProps.getServiceProviderEntityId()); - saml2Config.setServiceProviderMetadataPath(pac4jConfigProps.getServiceProviderMetadataPath()); - saml2Config.setForceServiceProviderMetadataGeneration(pac4jConfigProps.isForceServiceProviderMetadataGeneration()); - saml2Config.setWantsAssertionsSigned(pac4jConfigProps.isWantAssertionsSigned()); - saml2Config.setAttributeAsId(pac4jConfigProps.getSimpleProfileMapping().getUsername()); + final SAML2Configuration saml2Config = buildSaml2ConfigFromPac4JConfiguration(pac4jConfigProps); + final SAML2Client saml2Client = new SAML2Client(saml2Config); + saml2Client.setName(PAC4J_CLIENT_NAME); saml2Client.addAuthorizationGenerator(saml2ModelAuthorizationGenerator); SAML2Authenticator saml2Authenticator = new ShibuiSAML2Authenticator(saml2Config.getAttributeAsId(), saml2Config.getMappedAttributes(), userService); saml2Authenticator.setProfileDefinition(new CommonProfileDefinition(p -> new BetterSAML2Profile(pac4jConfigProps.getSimpleProfileMapping()))); saml2Client.setAuthenticator(saml2Authenticator); - saml2Client.setName(PAC4J_CLIENT_NAME); clients.setClients(saml2Client); break; } config.setClients(clients); return config; } - + + private SAML2Configuration buildSaml2ConfigFromPac4JConfiguration(Pac4jConfigurationProperties pac4jConfigProps) { + SAML2Configuration saml2Config = new SAML2Configuration(); + saml2Config.setKeystorePath(pac4jConfigProps.getKeystorePath()); + saml2Config.setKeystorePassword(pac4jConfigProps.getKeystorePassword()); + saml2Config.setPrivateKeyPassword(pac4jConfigProps.getPrivateKeyPassword()); + saml2Config.setIdentityProviderMetadataPath(pac4jConfigProps.getIdentityProviderMetadataPath()); + saml2Config.setMaximumAuthenticationLifetime(pac4jConfigProps.getMaximumAuthenticationLifetime()); + saml2Config.setServiceProviderEntityId(pac4jConfigProps.getServiceProviderEntityId()); + saml2Config.setServiceProviderMetadataPath(pac4jConfigProps.getServiceProviderMetadataPath()); + saml2Config.setForceServiceProviderMetadataGeneration(pac4jConfigProps.isForceServiceProviderMetadataGeneration()); + saml2Config.setWantsAssertionsSigned(pac4jConfigProps.isWantAssertionsSigned()); + saml2Config.setAttributeAsId(pac4jConfigProps.getSimpleProfileMapping().getUsername()); + saml2Config.setPostLogoutURL(pac4jConfigProps.getPostLogoutURL()); + return saml2Config; + } + @Bean public ErrorPageRegistrar errorPageRegistrar() { return this::registerErrorPages; diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java index 30311ba84..19507d1c0 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/Pac4jConfigurationProperties.java @@ -29,6 +29,7 @@ public class Pac4jConfigurationProperties { private String serviceProviderEntityId = "https://unicon.net/shibui"; private String serviceProviderMetadataPath = "/tmp/sp-metadata.xml"; private String typeOfAuth = "SAML2"; + private String postLogoutURL; private boolean wantAssertionsSigned = true; diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java index 884569ac7..cc5ce8e25 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/WebSecurity.java @@ -5,10 +5,12 @@ import edu.internet2.tier.shibboleth.admin.ui.security.service.IRolesService; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import edu.internet2.tier.shibboleth.admin.ui.service.EmailService; +import org.jadira.usertype.spi.utils.lang.StringUtils; import org.pac4j.core.authorization.authorizer.DefaultAuthorizers; import org.pac4j.core.config.Config; import org.pac4j.core.matching.matcher.Matcher; import org.pac4j.springframework.security.web.CallbackFilter; +import org.pac4j.springframework.security.web.LogoutFilter; import org.pac4j.springframework.security.web.SecurityFilter; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureOrder; @@ -64,12 +66,23 @@ public Pac4jWebSecurityConfigurerAdapter(final Config config, UserService userSe protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/unsecured/**/*").permitAll(); - // adding the authorizor bypasses the default behavior of checking CSRF in Pac4J's default securitylogic+defaultauthorizationchecker + // adding the authorizer bypasses the default behavior of checking CSRF in Pac4J's default securitylogic+defaultauthorizationchecker final SecurityFilter securityFilter = new SecurityFilter(this.config, PAC4J_CLIENT_NAME, DefaultAuthorizers.IS_AUTHENTICATED); - // add filter based on auth type - http.antMatcher("/**").addFilterBefore(getFilter(config, pac4jConfigurationProperties.getTypeOfAuth()), BasicAuthenticationFilter.class); + // If the post logout URL is configured, setup the logout filter + if (StringUtils.isNotEmpty(pac4jConfigurationProperties.getPostLogoutURL())){ + final LogoutFilter logoutFilter = new LogoutFilter(config); + logoutFilter.setLocalLogout(Boolean.TRUE); + logoutFilter.setSuffix("login"); // "logout" is redirected before we ever hit the filters - sent to /login?logout + logoutFilter.setCentralLogout(Boolean.TRUE); + logoutFilter.setDefaultUrl(pac4jConfigurationProperties.getPostLogoutURL()); + http.antMatcher("/**").addFilterBefore(logoutFilter, BasicAuthenticationFilter.class); + } + + // add filters + http.antMatcher("/**").addFilterBefore(getFilter(pac4jConfigurationProperties.getTypeOfAuth()), BasicAuthenticationFilter.class); http.antMatcher("/**").addFilterBefore(securityFilter, BasicAuthenticationFilter.class); + // add the new user filter http.addFilterAfter(new AddNewUserFilter(pac4jConfigurationProperties, userService, rolesService, getPathMatcher("exclude-paths-matcher"), groupService, emailService), SecurityFilter.class); @@ -84,7 +97,7 @@ private Matcher getPathMatcher(String name) { return config.getMatchers().get(name); } - private Filter getFilter(Config config2, String typeOfAuth) { + private Filter getFilter(String typeOfAuth) { switch (typeOfAuth) { case "SAML2": return new CallbackFilter(this.config); diff --git a/testbed/authentication/shibui/application.yml b/testbed/authentication/shibui/application.yml index 14085a9b7..cb789f06c 100644 --- a/testbed/authentication/shibui/application.yml +++ b/testbed/authentication/shibui/application.yml @@ -18,6 +18,7 @@ shibui: forceServiceProviderMetadataGeneration: true callbackUrl: "https://shibui.unicon.local/callback" maximumAuthenticationLifetime: 3600000 + postLogoutURL: "https://idp.unicon.local/idp/profile/Logout" simpleProfileMapping: username: urn:oid:0.9.2342.19200300.100.1.1 firstName: urn:oid:2.5.4.42