diff --git a/gradle.properties b/gradle.properties index 794836c7907..ca26a4c8bd1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ # TODO: Change to org.apereo.portal group=org.jasig.portal -version=5.15.2-SNAPSHOT +version=5.15.2.1-SNAPSHOT # Project Metadata (NB: copied from CAS; may or may not be used by us; review later) projectUrl=https://www.apereo.org/projects/uPortal @@ -97,6 +97,7 @@ plutoVersion=2.1.0-M3 resourceServerVersion=1.3.1 slf4jVersion=1.7.36 springVersion=4.3.30.RELEASE +springSessionVersion=1.3.5.RELEASE spockVersion=2.1-groovy-3.0 springfoxSwaggerVersion=2.9.2 springLdapVersion=2.3.4.RELEASE diff --git a/settings.gradle b/settings.gradle index 279a84a7d95..64ae3b90527 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ include 'uPortal-persondir' include 'uPortal-portlets' include 'uPortal-rendering' include 'uPortal-rdbm' +include 'uPortal-session' include 'uPortal-spring' include 'uPortal-tenants' include 'uPortal-tools' diff --git a/uPortal-content/uPortal-content-portlet/src/main/java/org/apereo/portal/portlet/registry/SubscribeKey.java b/uPortal-content/uPortal-content-portlet/src/main/java/org/apereo/portal/portlet/registry/SubscribeKey.java index 74816ec1608..8790a7560f9 100644 --- a/uPortal-content/uPortal-content-portlet/src/main/java/org/apereo/portal/portlet/registry/SubscribeKey.java +++ b/uPortal-content/uPortal-content-portlet/src/main/java/org/apereo/portal/portlet/registry/SubscribeKey.java @@ -14,7 +14,12 @@ */ package org.apereo.portal.portlet.registry; -final class SubscribeKey { +import java.io.Serializable; + +final class SubscribeKey implements Serializable { + + private static final long serialVersionUID = 1L; + private final int userId; private final String layoutNodeId; diff --git a/uPortal-security/uPortal-security-mvc/src/main/java/org/apereo/portal/url/RequireValidSessionFilter.java b/uPortal-security/uPortal-security-mvc/src/main/java/org/apereo/portal/url/RequireValidSessionFilter.java index c611f4fb0a8..30ad3a386aa 100644 --- a/uPortal-security/uPortal-security-mvc/src/main/java/org/apereo/portal/url/RequireValidSessionFilter.java +++ b/uPortal-security/uPortal-security-mvc/src/main/java/org/apereo/portal/url/RequireValidSessionFilter.java @@ -40,8 +40,8 @@ protected boolean shouldNotFilter(HttpServletRequest request) { // (1) You have a valid session (original method) final HttpSession session = request.getSession(false); - if (session != null && !session.isNew()) { - // Session exists and is not new, don't bother filtering + if (session != null) { + // Session exists, don't bother filtering log.debug("User {} has a session: {}", request.getRemoteUser(), session.getId()); log.debug("Max inactive interval: {}", session.getMaxInactiveInterval()); if (log.isDebugEnabled()) { diff --git a/uPortal-session/build.gradle b/uPortal-session/build.gradle new file mode 100644 index 00000000000..7fa45a0d8d4 --- /dev/null +++ b/uPortal-session/build.gradle @@ -0,0 +1,7 @@ +description = "Apereo uPortal Session" + +dependencies { + compile "org.springframework:spring-web:${springVersion}" + compile "org.springframework.session:spring-session-data-redis:${springSessionVersion}" + compileOnly "${servletApiDependency}" +} diff --git a/uPortal-session/src/main/java/org/apereo/portal/session/PortalSessionConstants.java b/uPortal-session/src/main/java/org/apereo/portal/session/PortalSessionConstants.java new file mode 100644 index 00000000000..e6148c00cae --- /dev/null +++ b/uPortal-session/src/main/java/org/apereo/portal/session/PortalSessionConstants.java @@ -0,0 +1,10 @@ +package org.apereo.portal.session; + +public class PortalSessionConstants { + + private PortalSessionConstants() {} + + public static final String REDIS_STORE_TYPE = "redis"; + public static final String SESSION_STORE_TYPE_ENV_PROPERTY_NAME = "SPRING_SESSION_STORETYPE"; + public static final String SESSION_STORE_TYPE_SYSTEM_PROPERTY_NAME = "spring.session.storetype"; +} diff --git a/uPortal-session/src/main/java/org/apereo/portal/session/redis/RedisSessionConfig.java b/uPortal-session/src/main/java/org/apereo/portal/session/redis/RedisSessionConfig.java new file mode 100644 index 00000000000..5141be1e2e2 --- /dev/null +++ b/uPortal-session/src/main/java/org/apereo/portal/session/redis/RedisSessionConfig.java @@ -0,0 +1,24 @@ +package org.apereo.portal.session.redis; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; + +@Configuration +@Conditional(SpringSessionRedisEnabledCondition.class) +@EnableRedisHttpSession +public class RedisSessionConfig { + + // @Bean + // @Primary + // public RedisProperties redisProperties() { + // return new RedisProperties(); + // } + + @Bean + public JedisConnectionFactory redisConnectionFactory() { + return new JedisConnectionFactory(); + } +} diff --git a/uPortal-session/src/main/java/org/apereo/portal/session/redis/RedisSessionInitializer.java b/uPortal-session/src/main/java/org/apereo/portal/session/redis/RedisSessionInitializer.java new file mode 100644 index 00000000000..785c95012ec --- /dev/null +++ b/uPortal-session/src/main/java/org/apereo/portal/session/redis/RedisSessionInitializer.java @@ -0,0 +1,41 @@ +package org.apereo.portal.session.redis; + +import static org.apereo.portal.session.PortalSessionConstants.REDIS_STORE_TYPE; +import static org.apereo.portal.session.PortalSessionConstants.SESSION_STORE_TYPE_ENV_PROPERTY_NAME; +import static org.apereo.portal.session.PortalSessionConstants.SESSION_STORE_TYPE_SYSTEM_PROPERTY_NAME; + +import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; + +/** + * This class is needed to enable Spring Session Redis support in uPortal. It registers the filter + * that is needed by Spring Session to manage the session with Redis. It also ensures that the + * filter is only registered if the session store-type is configured for Redis. The filter could + * have instead been added to web.xml, but that would not have allowed for the feature to be + * enabled/disabled via configuration. Note that the application properties are not available during + * initialization, and therefore we instead check for an environment variable or system property. + */ +public class RedisSessionInitializer extends AbstractHttpSessionApplicationInitializer { + + public RedisSessionInitializer() { + // MUST pass null here to avoid having Spring Session create a root WebApplicationContext + // that does not work + // with the current uPortal setup. + super((Class>[]) null); + } + + @Override + public void onStartup(javax.servlet.ServletContext servletContext) + throws javax.servlet.ServletException { + if (REDIS_STORE_TYPE.equals(this.getStoreTypeConfiguredValue())) { + super.onStartup(servletContext); + } + } + + private String getStoreTypeConfiguredValue() { + String result = System.getProperty(SESSION_STORE_TYPE_SYSTEM_PROPERTY_NAME); + if (result == null) { + result = System.getenv(SESSION_STORE_TYPE_ENV_PROPERTY_NAME); + } + return result; + } +} diff --git a/uPortal-session/src/main/java/org/apereo/portal/session/redis/SpringSessionRedisEnabledCondition.java b/uPortal-session/src/main/java/org/apereo/portal/session/redis/SpringSessionRedisEnabledCondition.java new file mode 100644 index 00000000000..ca69220d943 --- /dev/null +++ b/uPortal-session/src/main/java/org/apereo/portal/session/redis/SpringSessionRedisEnabledCondition.java @@ -0,0 +1,29 @@ +package org.apereo.portal.session.redis; + +import static org.apereo.portal.session.PortalSessionConstants.REDIS_STORE_TYPE; +import static org.apereo.portal.session.PortalSessionConstants.SESSION_STORE_TYPE_ENV_PROPERTY_NAME; +import static org.apereo.portal.session.PortalSessionConstants.SESSION_STORE_TYPE_SYSTEM_PROPERTY_NAME; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class SpringSessionRedisEnabledCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return REDIS_STORE_TYPE.equals(this.getSessionStoreTypeValue(context)); + } + + private String getSessionStoreTypeValue(ConditionContext context) { + String result = + context.getEnvironment() + .getProperty(SESSION_STORE_TYPE_SYSTEM_PROPERTY_NAME, String.class, null); + if (result == null) { + result = + context.getEnvironment() + .getProperty(SESSION_STORE_TYPE_ENV_PROPERTY_NAME, String.class, null); + } + return result; + } +} diff --git a/uPortal-webapp/build.gradle b/uPortal-webapp/build.gradle index 7529684ed0a..8f39085856c 100644 --- a/uPortal-webapp/build.gradle +++ b/uPortal-webapp/build.gradle @@ -39,6 +39,7 @@ dependencies { api project(':uPortal-security:uPortal-security-authn') api project(':uPortal-security:uPortal-security-xslt') api project(':uPortal-security:uPortal-security-filters') + api project(':uPortal-session') api project(':uPortal-soffit:uPortal-soffit-connector') api project(':uPortal-utils:uPortal-utils-jmx') api project(':uPortal-utils:uPortal-utils-url') diff --git a/uPortal-webapp/src/main/java/org/apereo/portal/PortalWebAppInitializer.java b/uPortal-webapp/src/main/java/org/apereo/portal/PortalWebAppInitializer.java new file mode 100644 index 00000000000..5053c795d90 --- /dev/null +++ b/uPortal-webapp/src/main/java/org/apereo/portal/PortalWebAppInitializer.java @@ -0,0 +1,41 @@ +package org.apereo.portal; + +import org.springframework.web.context.AbstractContextLoaderInitializer; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.XmlWebApplicationContext; + +/** + * This class does the following: 1. creates the root application context using the specified config + * locations 2. initializes the context loader + * + *
This replaces the the following, which were previously defined in web.xml file. + * + *
{@code + *+ * + * This new approach allows us to dynamically update the servlet context programatically with + * Spring, which was needed in order support Spring Session handling as a feature that could be + * enabled/disabled with configuration. + */ +public class PortalWebAppInitializer extends AbstractContextLoaderInitializer { + + @Override + protected WebApplicationContext createRootApplicationContext() { + XmlWebApplicationContext context = new XmlWebApplicationContext(); + context.setConfigLocation( + "classpath:/properties/contexts/*.xml,classpath:/properties/contextOverrides/*.xml"); + return context; + } +} diff --git a/uPortal-webapp/src/main/resources/properties/contexts/applicationContext.xml b/uPortal-webapp/src/main/resources/properties/contexts/applicationContext.xml index 1e418b66b06..a90eb9f6064 100644 --- a/uPortal-webapp/src/main/resources/properties/contexts/applicationContext.xml +++ b/uPortal-webapp/src/main/resources/properties/contexts/applicationContext.xml @@ -53,6 +53,7 @@+ * + * + * + *contextConfigLocation + *classpath:/properties/contexts/*.xml,classpath:/properties/contextOverrides/*.xml + *+ * + * + * }org.springframework.web.context.ContextLoaderListener + *