Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delegated authentication: fix the DistributedJ2ESessionStore #4426

Merged
merged 10 commits into from Nov 16, 2019
Expand Up @@ -33,6 +33,7 @@
import org.apereo.cas.configuration.model.support.geo.maxmind.MaxmindProperties;
import org.apereo.cas.configuration.model.support.interrupt.InterruptProperties;
import org.apereo.cas.configuration.model.support.jpa.DatabaseProperties;
import org.apereo.cas.configuration.model.support.replication.SessionReplicationProperties;
import org.apereo.cas.configuration.model.support.saml.SamlCoreProperties;
import org.apereo.cas.configuration.model.support.saml.googleapps.GoogleAppsProperties;
import org.apereo.cas.configuration.model.support.saml.mdui.SamlMetadataUIProperties;
Expand Down Expand Up @@ -318,4 +319,10 @@ public class CasConfigurationProperties implements Serializable {
*/
@NestedConfigurationProperty
private SpringCloudConfigurationProperties spring = new SpringCloudConfigurationProperties();

/**
* Session replication properties.
*/
@NestedConfigurationProperty
private SessionReplicationProperties sessionReplication = new SessionReplicationProperties();
}
@@ -0,0 +1,33 @@
package org.apereo.cas.configuration.model.support.replication;

import org.apereo.cas.configuration.support.RequiresModule;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
* This is the properties for the session replication.
*
* @author Jerome LELEU
* @since 6.1.2
*/
@RequiresModule(name = "cas-server-support-pac4j-api", automated = true)
@Getter
@Setter
public class SessionReplicationProperties implements Serializable {

private static final long serialVersionUID = -3839399712674610962L;

/**
* The name of the session cookie.
*
* When using a distributed session store specially backed by CAS to primarily replicate CAS tokens and tickets across a cluster of CAS servers,
* the distribution mechanism needs to be made aware of session cookies. If a session is generated on a first node, when you reach the second node,
* retrieving the session via the Tomcat session manager does not work. A {@code JSESSION_ID} cookie value is actually retrieved, but as there is no HTTP session
* associated, it is discarded and a new session with a new identifier is created. The only way to deal with that is to directly read the cookie value.
* The setting below allows one to customize the cookie name in such scenarios.
*/
private String sessionCookieName = "JSESSIONID";
}
Expand Up @@ -582,6 +582,14 @@ If none is specified, one is automatically detected and used by CAS.
# cas.host.name=
```

## Session replication

The `sessionCookieName` property defines the session cookie name for the session replication.

```properties
# cas.sessionReplication.sessionCookieName=JSESSIONID
```

## CAS Banner

On startup, CAS will display a banner along with some diagnostics info.
Expand Down
Expand Up @@ -157,7 +157,8 @@ public CasWebflowExecutionPlanConfigurer mfaAccepttoCasWebflowExecutionPlanConfi
@ConditionalOnMissingBean(name = "mfaAccepttoDistributedSessionStore")
@Bean
public SessionStore<JEEContext> mfaAccepttoDistributedSessionStore() {
return new DistributedJ2ESessionStore(ticketRegistry.getIfAvailable(), ticketFactory.getIfAvailable());
return new DistributedJ2ESessionStore(ticketRegistry.getIfAvailable(), ticketFactory.getIfAvailable(),
casProperties.getSessionReplication().getSessionCookieName());
}

@ConditionalOnMissingBean(name = "mfaAccepttoMultifactorFetchChannelAction")
Expand Down
Expand Up @@ -779,7 +779,8 @@ public CipherExecutor oauthAccessTokenJwtCipherExecutor() {
public SessionStore<JEEContext> oauthDistributedSessionStore() {
val replicate = casProperties.getAuthn().getOauth().isReplicateSessions();
if (replicate) {
return new DistributedJ2ESessionStore(ticketRegistry.getObject(), ticketFactory.getObject());
return new DistributedJ2ESessionStore(ticketRegistry.getObject(), ticketFactory.getObject(),
casProperties.getSessionReplication().getSessionCookieName());
}
return new JEESessionStore();
}
Expand Down
Expand Up @@ -33,6 +33,7 @@
public class DistributedJ2ESessionStore extends JEESessionStore implements HttpSessionListener, LogoutPostProcessor {
private final TicketRegistry ticketRegistry;
private final TicketFactory ticketFactory;
private final String sessionCookieName;

@Override
public Optional get(final JEEContext context, final String key) {
Expand Down Expand Up @@ -70,8 +71,17 @@ public void set(final JEEContext context, final String key, final Object value)
}

private TransientSessionTicket getTransientSessionTicketForSession(final JEEContext context) {
val id = getOrCreateSessionId(context);
LOGGER.trace("Session identifier is set to [{}]", id);
String id = null;
val cookies = context.getRequestCookies();
if (cookies != null && !cookies.isEmpty()) {
id = cookies.stream().filter(c -> this.sessionCookieName.equals(c.getName())).map(c -> c.getValue()).findFirst().orElse(null);
}
if (id != null) {
LOGGER.trace("Session identifier found from {} cookie [{}]", this.sessionCookieName, id);
} else {
id = getOrCreateSessionId(context);
LOGGER.trace("Session identifier created for HTTP session [{}]", id);
}
val ticketId = TransientSessionTicketFactory.normalizeTicketId(id);

LOGGER.trace("Fetching session ticket via identifier [{}]", ticketId);
Expand Down
Expand Up @@ -63,7 +63,7 @@ public class DistributedJ2ESessionStoreTests {
public void verifyOperation() {
val request = new MockHttpServletRequest();
val response = new MockHttpServletResponse();
val store = new DistributedJ2ESessionStore(this.ticketRegistry, this.ticketFactory);
val store = new DistributedJ2ESessionStore(this.ticketRegistry, this.ticketFactory, "JSESSIONID");
val context = new JEEContext(request, response, store);

assertNotNull(request.getSession());
Expand Down
Expand Up @@ -185,6 +185,7 @@ public LogoutExecutionPlanConfigurer delegatedAuthenticationLogoutExecutionPlanC
}

private DistributedJ2ESessionStore getDistributedSessionStore() {
return new DistributedJ2ESessionStore(ticketRegistry.getObject(), ticketFactory.getObject());
return new DistributedJ2ESessionStore(ticketRegistry.getObject(), ticketFactory.getObject(),
casProperties.getSessionReplication().getSessionCookieName());
}
}