Skip to content

Commit

Permalink
feat: add new 'uPortal-session' submodule which adds support for sess…
Browse files Browse the repository at this point in the history
…ion clustering/replication/failover with Redis
  • Loading branch information
groybal committed Oct 24, 2023
1 parent 07ed2f8 commit a134862
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 16 deletions.
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
7 changes: 7 additions & 0 deletions uPortal-session/build.gradle
Original file line number Diff line number Diff line change
@@ -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}"
}
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions uPortal-webapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
@@ -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
*
* <p>This replaces the the following, which were previously defined in web.xml file.
*
* <pre>{@code
* <context-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>classpath:/properties/contexts/*.xml,classpath:/properties/contextOverrides/*.xml</param-value>
* </context-param>
*
* <!--
* | Loads/Unloads the Spring WebApplicationContext
* +-->
* <listener>
* <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
* </listener>
*
* }</pre>
*
* 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<context:exclude-filter type="regex" expression="org\.apereo\.portal\.rest\..+"/>
<context:exclude-filter type="regex" expression="org\.apereo\.portal\.security\.mvc\..+"/>
<context:exclude-filter type="regex" expression="org\.apereo\.portal\.security\.remoting\..+"/>
<context:exclude-filter type="regex" expression="org\.apereo\.portal\.session\..+"/>
<context:exclude-filter type="regex" expression="org\.apereo\.portal\.health\..+"/>
</context:component-scan>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to Apereo under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Apereo 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 the following location:
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.
-->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<context:annotation-config/>
<context:component-scan base-package="org.apereo.portal.session"/>

</beans>
12 changes: 0 additions & 12 deletions uPortal-webapp/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@

<display-name>uPortal</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/properties/contexts/*.xml,classpath:/properties/contextOverrides/*.xml</param-value>
</context-param>

<!-- Needed to remove JMX registration and allow for classloader GC. Should be first listener per http://logback.qos.ch/manual/loggingSeparation.html#hotDeploy -->
<listener>
<listener-class>ch.qos.logback.classic.selector.servlet.ContextDetachingSCL</listener-class>
Expand All @@ -45,13 +40,6 @@
<listener-class>org.apereo.portal.utils.PortalApplicationContextLocator</listener-class>
</listener>

<!--
| Loads/Unloads the Spring WebApplicationContext
+-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!--
| Passes Session init/destroy events to the WebApplicationContext
+-->
Expand Down

0 comments on commit a134862

Please sign in to comment.