Skip to content

Commit

Permalink
Handle the SAML SLO (#1204)
Browse files Browse the repository at this point in the history
* receive POST logout request

* supports Logout request/response from shibboleth

* handle http-redirect for logout

* add LogoutHandler in SAML support

* return logout response + refactoring

* doc + fix bug

* more tests

* support SOAP incoming logout request

* test logout with CAS 6

* add deprecated constructor

* fix typos
  • Loading branch information
leleuj committed Nov 12, 2018
1 parent 86296bf commit 4e01e03
Show file tree
Hide file tree
Showing 63 changed files with 2,386 additions and 1,869 deletions.
4 changes: 2 additions & 2 deletions documentation/docs/clients/cas.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ To correlate proxy information, the `CasProxyReceptor` uses an internal [`Store`

### d) Logout configuration

To handle CAS logout requests, a [`DefaultCasLogoutHandler`](https://github.com/pac4j/pac4j/blob/master/pac4j-cas/src/main/java/org/pac4j/cas/logout/DefaultCasLogoutHandler.java) is automatically created. Unless you specify your own implementation of the [`CasLogoutHandler`](https://github.com/pac4j/pac4j/blob/master/pac4j-cas/src/main/java/org/pac4j/cas/logout/CasLogoutHandler.java) interface.
To handle CAS logout requests, a [`DefaultLogoutHandler`](https://github.com/pac4j/pac4j/blob/master/pac4j-core/src/main/java/org/pac4j/core/logout/handler/DefaultLogoutHandler.java) is automatically created. Unless you specify your own implementation of the [`LogoutHandler`](https://github.com/pac4j/pac4j/blob/master/pac4j-core/src/main/java/org/pac4j/core/logout/handler/LogoutHandler.java) interface.

The `DefaultCasLogoutHandler`:
The `DefaultLogoutHandler`:

- relies on the capabilities of the `SessionStore` (`destroySession`, `getTrackableSession` and `buildFromTrackableSession` methods)
- stores data in a [`Store`](../store.html) that you can change via the `setStore` method (by default, Guava is used).
Expand Down
32 changes: 24 additions & 8 deletions documentation/docs/clients/saml.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: SAML

*pac4j* allows you to login with any SAML identity provider using the SAML v2.0 protocol.

It has been tested with various SAML 2 providers: Okta, testshib.org, CAS SAML2 IdP, ...
It has been tested with various SAML 2 providers: Okta, testshib.org, CAS SAML2 IdP, Shibboleth v3...

## 1) Dependency

Expand Down Expand Up @@ -34,10 +34,10 @@ keytool -genkeypair -alias pac4j-demo -keypass pac4j-demo-passwd -keystore samlK

Alternatively, you can also let pac4j create the keystore for you. If the keystore resource does not exist and is writable, *pac4j* will attempt to generate a keystore and produce the relevant key pairs inside it.

Then, you must define a [`SAML2ClientConfiguration`](https://github.com/pac4j/pac4j/blob/master/pac4j-saml/src/main/java/org/pac4j/saml/client/SAML2ClientConfiguration.java):
Then, you must define a [`SAML2Configuration`](https://github.com/pac4j/pac4j/blob/master/pac4j-saml/src/main/java/org/pac4j/saml/config/SAML2Configuration.java):

```java
SAML2ClientConfiguration cfg = new SAML2ClientConfiguration(new ClassPathResource("samlKeystore.jks"),
SAML2Configuration cfg = new SAML2Configuration(new ClassPathResource("samlKeystore.jks"),
"pac4j-demo-passwd",
"pac4j-demo-passwd",
new ClassPathResource("testshib-providers.xml"));
Expand All @@ -55,7 +55,7 @@ The fourth parameter (`identityProviderMetadataResource`) should point to your I
Or you can also use the "prefix mechanism" to define the `Resource`:

```java
SAML2ClientConfiguration cfg = new SAML2ClientConfiguration("resource:samlKeystore.jks",
SAML2Configuration cfg = new SAML2Configuration("resource:samlKeystore.jks",
"pac4j-demo-passwd",
"pac4j-demo-passwd",
"resource:testshib-providers.xml");
Expand Down Expand Up @@ -157,18 +157,33 @@ You can generate the SP metadata in two ways:
- or by defining the appropriate configuration: `cfg.setServiceProviderMetadata(new FileSystemResource("/tmp/sp-metadata.xml"));`


## 4) Logout

## 4) Authentication Attributes
The SAML support handles the HTTP-POST and the HTTP-Redirect bindings for logout requests/responses (and the SOAP binding for incoming logout requests).

The `SAML2Client` can participate in the central logout and send a logout request to the IdP.
The binding of this request is controlled by the `spLogoutRequestBindingType` property and
the request can be signed using the `spLogoutRequestSigned` property of the `SAML2Configuration`.

When calling the IdP, the SAML *pac4j* application locally removes the user profiles and optionally destroys the web session based on the [`DefaultLogoutHandler`](https://github.com/pac4j/pac4j/blob/master/pac4j-core/src/main/java/org/pac4j/core/logout/handler/DefaultLogoutHandler.java).
You may use your own logout handler by implementing the [`LogoutHandler`](https://github.com/pac4j/pac4j/blob/master/pac4j-core/src/main/java/org/pac4j/core/logout/handler/LogoutHandler.java) interface
and define it in the SAML configuration.

When called by the IdP, the SAML *pac4j* application also removes the user profiles based on the logout handler and returns a logout response with a binding defined by the `spLogoutResponseBindingType` property (in the `SAML2Configuration`).


## 5) Authentication Attributes

The following authentication attributes are populated by this client:

- The entityID of the IdP (`getAuthenticationAttribute("issuerId")` or `SAML2Profile.getIssuerId()`)
- The authentication method(s) asserted by the IdP (`getAuthenticationAttribute("authnContext")` or `SAML2Profile.getAuthnContexts()`)
- The NotBefore SAML Condition (`getAuthenticationAttribute("notBefore")` or `SAML2Profile.getNotBefore()`)
- The NotOnOrAfter SAML Condition (`getAuthenticationAttribute("notOnOrAfter")` or `SAML2Profile.getNotOnOrAfter()`)
- the session index.


## 5) ADFS subtilities
## 6) ADFS subtilities

You must follow these rules to successfully authenticate using Microsoft ADFS 2.0/3.0.

Expand Down Expand Up @@ -200,7 +215,8 @@ Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files c

### c) Disable Name Qualifier for format urn:oasis:names:tc:SAML:2.0:nameid-format:entity

ADFS 3.0 does not accept NameQualifier when using urn:oasis:names:tc:SAML:2.0:nameid-format:entity. In SAML2ClientConfiguration you can use setUseNameQualifier to disable the NameQualifier from SAML Request.
ADFS 3.0 does not accept NameQualifier when using urn:oasis:names:tc:SAML:2.0:nameid-format:entity. In the `SAML2Configuration`, you can use setUseNameQualifier to disable the NameQualifier from SAML Request.


# Integration with various IdPs

Expand All @@ -211,7 +227,7 @@ SimpleSAMLphp is a commonly used IdP. To integrate PAC4J with SimpleSAMLphp use
### DemoConfigFactory.java

```java
final SAML2ClientConfiguration cfg = new SAML2ClientConfiguration("resource:samlKeystore.jks",
final SAML2Configuration cfg = new SAML2Configuration("resource:samlKeystore.jks",
"pac4j-demo-passwd",
"pac4j-demo-passwd",
"resource:idp-metadata.xml"); //the id-metadata.xml contains IdP metadata, you will have to create this
Expand Down
3 changes: 2 additions & 1 deletion documentation/docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ title: Release notes:

**v3.4.0**:

- Added ability to create a composition of authorizers (conjunction or disjunction).
- Added ability to create a composition of authorizers (conjunction or disjunction)
- SAML SLO support with SOAP (ingoing only), HTTP-POST and HTTP-Redirect bindings

**v3.3.0**:

Expand Down
5 changes: 3 additions & 2 deletions pac4j-cas/src/main/java/org/pac4j/cas/client/CasClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.cas.credentials.authenticator.CasAuthenticator;
import org.pac4j.cas.credentials.extractor.TicketAndLogoutRequestExtractor;
import org.pac4j.cas.logout.CasLogoutHandler;
import org.pac4j.core.logout.handler.LogoutHandler;
import org.pac4j.cas.redirect.CasRedirectActionBuilder;
import org.pac4j.core.client.IndirectClient;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.TokenCredentials;
import org.pac4j.core.http.callback.QueryParameterCallbackUrlResolver;
import org.pac4j.core.logout.CasLogoutActionBuilder;
import org.pac4j.core.logout.handler.DefaultLogoutHandler;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.util.CommonHelper;

Expand All @@ -21,7 +22,7 @@
*
* <p>The configuration can be defined via the {@link #configuration} object.</p>
*
* <p>By default, the {@link CasLogoutHandler} will be a {@link org.pac4j.cas.logout.DefaultCasLogoutHandler}. Use <code>null</code> to
* <p>By default, the {@link LogoutHandler} will be a {@link DefaultLogoutHandler}. Use <code>null</code> to
* disable logout support.</p>
*
* <p>For proxy support, a {@link CasProxyReceptor} must be defined in the configuration (the corresponding "callback filter" must be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import org.jasig.cas.client.validation.*;
import org.pac4j.cas.client.CasProxyReceptor;
import org.pac4j.cas.logout.CasLogoutHandler;
import org.pac4j.cas.logout.DefaultCasLogoutHandler;
import org.pac4j.core.logout.handler.LogoutHandler;
import org.pac4j.core.logout.handler.DefaultLogoutHandler;
import org.pac4j.cas.store.ProxyGrantingTicketStore;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.exception.TechnicalException;
Expand Down Expand Up @@ -54,7 +54,7 @@ public class CasConfiguration extends InitializableObject {

private ProxyList allowedProxyChains = new ProxyList();

private CasLogoutHandler logoutHandler;
private LogoutHandler logoutHandler;

private TicketValidator defaultTicketValidator;

Expand Down Expand Up @@ -117,7 +117,7 @@ protected void initializeClientConfiguration() {

protected void initializeLogoutHandler() {
if (this.logoutHandler == null) {
this.logoutHandler = new DefaultCasLogoutHandler();
this.logoutHandler = new DefaultLogoutHandler();
}
}

Expand Down Expand Up @@ -298,17 +298,17 @@ public void setAllowedProxyChains(final ProxyList allowedProxyChains) {
this.allowedProxyChains = allowedProxyChains;
}

public CasLogoutHandler getLogoutHandler() {
public LogoutHandler getLogoutHandler() {
return logoutHandler;
}

public CasLogoutHandler findLogoutHandler() {
public LogoutHandler findLogoutHandler() {
init();

return logoutHandler;
}

public void setLogoutHandler(final CasLogoutHandler logoutHandler) {
public void setLogoutHandler(final LogoutHandler logoutHandler) {
this.logoutHandler = logoutHandler;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.Base64;
import org.jasig.cas.client.util.CommonUtils;
import org.pac4j.cas.config.CasConfiguration;
import org.pac4j.cas.logout.CasLogoutHandler;
import org.pac4j.core.logout.handler.LogoutHandler;
import org.pac4j.core.context.ContextHelper;
import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.WebContext;
Expand Down Expand Up @@ -38,14 +38,12 @@ public TicketAndLogoutRequestExtractor(final CasConfiguration configuration) {

@Override
public TokenCredentials extract(final WebContext context) {
final CasLogoutHandler logoutHandler = configuration.findLogoutHandler();
final LogoutHandler logoutHandler = configuration.findLogoutHandler();

// like the SingleSignOutFilter from the Apereo CAS client:
if (isTokenRequest(context)) {
final String ticket = context.getRequestParameter(CasConfiguration.TICKET_PARAMETER);
if (logoutHandler != null) {
logoutHandler.recordSession(context, ticket);
}
logoutHandler.recordSession(context, ticket);
final TokenCredentials casCredentials = new TokenCredentials(ticket);
logger.debug("casCredentials: {}", casCredentials);
return casCredentials;
Expand All @@ -55,7 +53,7 @@ public TokenCredentials extract(final WebContext context) {
logger.trace("Logout request:\n{}", logoutMessage);

final String ticket = CommonHelper.substringBetween(logoutMessage, CasConfiguration.SESSION_INDEX_TAG + ">", "</");
if (CommonUtils.isNotBlank(ticket) && logoutHandler != null) {
if (CommonUtils.isNotBlank(ticket)) {
logoutHandler.destroySessionBack(context, ticket);
}
logger.debug("back logout request: no credential returned");
Expand All @@ -66,7 +64,7 @@ public TokenCredentials extract(final WebContext context) {
logger.trace("Logout request:\n{}", logoutMessage);

final String ticket = CommonHelper.substringBetween(logoutMessage, CasConfiguration.SESSION_INDEX_TAG + ">", "</");
if (CommonUtils.isNotBlank(ticket) && logoutHandler != null) {
if (CommonUtils.isNotBlank(ticket)) {
logoutHandler.destroySessionFront(context, ticket);
}
logger.debug("front logout request: no credential returned");
Expand Down
41 changes: 4 additions & 37 deletions pac4j-cas/src/main/java/org/pac4j/cas/logout/CasLogoutHandler.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,11 @@
package org.pac4j.cas.logout;

import org.pac4j.core.context.WebContext;
import org.pac4j.core.logout.handler.LogoutHandler;

/**
* This interface defines how to handle CAS logout request on client side.
*
* @author Jerome Leleu
* @since 1.9.2
* Use the {@link LogoutHandler} instead.
*/
public interface CasLogoutHandler<C extends WebContext> {

/**
* Associates a ticket with the current web session.
*
* @param context the web context
* @param ticket the ticket
*/
void recordSession(C context, String ticket);

/**
* Destroys the current web session for the given ticket for a front channel logout.
*
* @param context the web context
* @param ticket the ticket
*/
void destroySessionFront(C context, String ticket);

/**
* Destroys the current web session for the given ticket for a back channel logout.
*
* @param context the web context
* @param ticket the ticket
*/
void destroySessionBack(C context, String ticket);

/**
* Renew the web session.
*
* @param oldSessionId the old session identifier
* @param context the web context
*/
void renewSession(String oldSessionId, C context);
@Deprecated
public interface CasLogoutHandler<C extends WebContext> extends LogoutHandler<C> {
}
Loading

0 comments on commit 4e01e03

Please sign in to comment.