Compare commits

...

32 Commits

Author SHA1 Message Date
Ranjith Manickam d0de8bb6e2
Update README.md 2020-12-23 13:00:58 +05:30
Ranjith Manickam 8593f88243 update downloads badge 2020-12-23 12:50:14 +05:30
Ranjith Manickam 9da24f2328 Add maven repo 2020-12-23 12:38:24 +05:30
Ranjith Manickam e31ea59636 Add maven repo 2020-12-23 12:28:11 +05:30
Ranjith Manickam 45b0b98725
Update README.md 2020-11-08 09:58:03 +05:30
Ranjith Manickam 2e505deee1
Update README.md 2020-05-15 16:11:45 +05:30
Ranjith Manickam df80fa0da3
Update README.md 2020-05-13 11:10:57 +05:30
Ranjith Manickam f4c6322793
Update pom.xml 2020-05-13 11:08:04 +05:30
Ranjith Manickam a0eab9407e version update 2020-05-13 11:03:04 +05:30
Ranjith Manickam 1f6e57c164 Fix for - NPE in SessionHandlerValve.java accessing Tomcat's /manager app 2020-05-13 11:02:21 +05:30
Ranjith Manickam 5d0c7aa7a1 Update README.md 2020-05-02 14:53:18 +05:30
Ranjith Manickam 3916e217a5
Update pom.xml 2020-05-02 14:45:13 +05:30
Ranjith Manickam 39df08832a changes to support single-sign-on (sso) 2020-05-02 14:39:17 +05:30
Ranjith Manickam 372332e8dc changes to support single-sign-on (sso) 2020-04-10 07:23:00 +05:30
Ranjith Manickam 7ace9f2b7b changes to support single-sign-on (sso) 2020-04-08 20:59:24 +05:30
Ranjith Manickam 0ecdfc2661 changes to support single-sign-on (sso) 2020-04-08 20:57:07 +05:30
Ranjith Manickam 80acc01418 changes to support single-sign-on (sso) 2020-04-02 12:44:40 +05:30
Ranjith Manickam ab04189b93
Update pom.xml 2020-03-22 00:27:41 +05:30
Ranjith Manickam 246b64def9
Merge pull request #52 from ran-jit/sso
changes to support single-sign-on (sso)
2020-03-22 00:25:18 +05:30
Ranjith Manickam 2b13fdadc9 changes to support single-sign-on (sso) 2020-03-20 23:51:51 +05:30
Ranjith Manickam 16fffca534 updated download summary 2020-02-09 16:23:54 +05:30
Ranjith Manickam b1d33afd0a update 2020-02-09 16:20:57 +05:30
Ranjith Manickam 920662654e
Update README.md 2020-02-09 16:17:36 +05:30
Ranjith Manickam 905ae577c5
Update README.md 2020-02-08 19:13:06 +05:30
Ranjith Manickam 45b1c5a9b0
Update README.md 2020-02-08 19:10:26 +05:30
Ranjith Manickam 9a409e43a4
Update README.md 2020-02-06 10:28:09 +05:30
Ranjith Manickam 113933975c application config system property support changes 2020-02-05 19:37:31 +05:30
Ranjith Manickam f45e1b23a9 application config system property support changes 2020-02-05 17:29:47 +05:30
Ranjith Manickam b73c1b78c2
Update README.md 2019-10-03 12:42:21 +05:30
Ranjith Manickam 094f612138
Update README.md 2019-09-04 22:03:25 +05:30
Ranjith Manickam 3d5afda53e
Update README.md 2019-08-15 13:14:55 +05:30
Ranjith Manickam 8902534782
Update README.md 2019-08-12 19:41:01 +05:30
25 changed files with 1126 additions and 244 deletions

View File

@ -14,15 +14,31 @@ Going forward, we no need to enable sticky session (JSESSIONID) in Load Balancer
- Apache Tomcat 7 - Apache Tomcat 7
- Apache Tomcat 8 - Apache Tomcat 8
- Apache Tomcat 9 - Apache Tomcat 9
- Apache Tomcat 10
## Downloads: ## Downloads: [![Total Downloads](https://img.shields.io/github/downloads/ran-jit/tomcat-cluster-redis-session-manager/total.svg)](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wiki)
- [latest version (3.0.2)](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/releases/tag/3.0.2) - [latest version (4.0)](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/releases/tag/4.0)
- [older versions](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wiki) - [older versions](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wiki)
<p align="center"> <p align="center">
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6M9A39423UMYS&source=https://github.com/ran-jit/tomcat-cluster-redis-session-manager" target="_blank"><img alt="Donate" height="30%" width="30%" src="https://github.com/ran-jit/tomcat-cluster-redis-session-manager/blob/master/src/main/resources/donate.png"></a> <a href="https://www.buymeacoffee.com/ranmanic" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
</p> </p>
## Maven configuration
```
<repository>
<id>repsy</id>
<name>tomcat-cluster-redis-session-manager-repo</name>
<url>https://repo.repsy.io/mvn/ranmanic/tomcat-session-manager</url>
</repository>
<dependency>
<groupId>tomcat-session-manager</groupId>
<artifactId>redis</artifactId>
<version>4.0</version>
</dependency>
```
#### Pre-requisite: #### Pre-requisite:
1. jedis.jar 1. jedis.jar
2. commons-pool2.jar 2. commons-pool2.jar
@ -33,23 +49,33 @@ more details.. https://github.com/ran-jit/tomcat-cluster-redis-session-manager/w
#### Steps to be done, #### Steps to be done,
1. Copy the downloaded jars to your tomcat/lib directory. 1. Copy the downloaded jars to your tomcat/lib directory.
- **tomcat/lib/** ```
tomcat/lib/
```
2. Add tomcat system property "catalina.base". 2. Add tomcat system property "catalina.base".
- **catalina.base="TOMCAT_LOCATION"** ```
* example: export catalina.base=/opt/tomcat catalina.base="TOMCAT_LOCATION"
example: env "catalina.base=/opt/tomcat" bash
```
3. Copy the redis-data-cache.properties file to your tomcat/conf directory and update your Redis server details. 3. Copy the redis-data-cache.properties file to your tomcat/conf directory and update your Redis server details.
- **tomcat/conf/redis-data-cache.properties** ```
tomcat/conf/redis-data-cache.properties
```
4. Add the below two lines in your tomcat/conf/context.xml file. 4. Add the below two lines in your tomcat/conf/context.xml file.
- **&#60;Valve className="tomcat.request.session.redis.SessionHandlerValve" &#47;&#62;** ```
- **&#60;Manager className="tomcat.request.session.redis.SessionManager" &#47;&#62;** <Valve className="tomcat.request.session.redis.SessionHandlerValve" />
<Manager className="tomcat.request.session.redis.SessionManager" />
```
5. Verify the session expiration time in tomcat/conf/web.xml file. 5. Verify the session expiration time in tomcat/conf/web.xml file.
- **&#60;session-config&#62;** ```
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; **&#60;session-timeout&#62;60&#60;&#47;session-timeout&#62;** <session-config>
- **&#60;&#47;session-config&#62;** <session-timeout>60</session-timeout>
</session-config>
```
### Note: ### Note:
- **All your session attribute values must implement java.io.Serializable.** - **All your session attribute values must implement java.io.Serializable.**
@ -67,6 +93,8 @@ more details.. https://github.com/ran-jit/tomcat-cluster-redis-session-manager/w
<tr><td>redis.sentinel.enabled</td><td>To enable redis sentinel mode<br/>- default: false<br>- supported values: true/false</td></tr> <tr><td>redis.sentinel.enabled</td><td>To enable redis sentinel mode<br/>- default: false<br>- supported values: true/false</td></tr>
<tr><td>redis.sentinel.master</td><td>Redis sentinel master name<br/>- default: mymaster</td></tr> <tr><td>redis.sentinel.master</td><td>Redis sentinel master name<br/>- default: mymaster</td></tr>
<tr><td>lb.sticky-session.enabled</td><td>To enable redis and standard session mode<br><br>If enabled,<ol><li>Must be enabled sticky session in your load balancer configuration. Else this manager may not return the updated session values</li><li>Session values are stored in local jvm and redis</li><li>If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced</li></ol>- default: false</td></tr> <tr><td>lb.sticky-session.enabled</td><td>To enable redis and standard session mode<br><br>If enabled,<ol><li>Must be enabled sticky session in your load balancer configuration. Else this manager may not return the updated session values</li><li>Session values are stored in local jvm and redis</li><li>If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced</li></ol>- default: false</td></tr>
<tr><td>session.persistent.policies</td><td>session persistent policies.<br/><br/>- policies - DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST <br/><ol><li>SAVE_ON_CHANGE: every time session.setAttribute() or session.removeAttribute() is called the session will be saved.</li><li>ALWAYS_SAVE_AFTER_REQUEST: force saving after every request, regardless of whether or not the manager has detected changes to the session.</li></ol>- default: DEFAULT</td></tr>
<tr><td>redis.sso.timeout</td><td>single-sign-on session timeout.<br/>- default: 0 ms (-no expiry)</td></tr>
</table> </table>
</body> </body>
</html> </html>

48
pom.xml
View File

@ -2,9 +2,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>tomcat-cluster-redis-session-manager</groupId> <groupId>tomcat-session-manager</groupId>
<artifactId>tomcat-cluster-redis-session-manager</artifactId> <artifactId>redis</artifactId>
<version>3.0.3</version> <version>4.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>tomcat-cluster-redis-session-manager</name> <name>tomcat-cluster-redis-session-manager</name>
@ -32,12 +32,18 @@
<target-java.version>${source-java.version}</target-java.version> <target-java.version>${source-java.version}</target-java.version>
<!-- For local development properties begins.. --> <!-- For local development properties begins.. -->
<!--<tomcat-catalina.version>apache-tomcat-8.5.32</tomcat-catalina.version>--> <tomcat.version>8.5.32</tomcat.version>
<!--<tomcat-servlet-api.version>apache-tomcat-8.5.32</tomcat-servlet-api.version>-->
<!--<tomcat-api.version>apache-tomcat-8.5.32</tomcat-api.version>-->
<!-- For local development properties end.. --> <!-- For local development properties end.. -->
</properties> </properties>
<distributionManagement>
<repository>
<id>repsy</id>
<name>tomcat-cluster-redis-session-manager-repo</name>
<url>https://repo.repsy.io/mvn/ranmanic/tomcat-session-manager</url>
</repository>
</distributionManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
@ -56,21 +62,21 @@
</dependency> </dependency>
<!-- For local development dependency begins.. --> <!-- For local development dependency begins.. -->
<!--<dependency>--> <dependency>
<!--<groupId>apache-tomcat</groupId>--> <groupId>org.apache.tomcat</groupId>
<!--<artifactId>catalina</artifactId>--> <artifactId>tomcat-catalina</artifactId>
<!--<version>${tomcat-catalina.version}</version>--> <version>${tomcat.version}</version>
<!--</dependency>--> </dependency>
<!--<dependency>--> <dependency>
<!--<groupId>apache-tomcat</groupId>--> <groupId>org.apache.tomcat</groupId>
<!--<artifactId>servlet-api</artifactId>--> <artifactId>tomcat-servlet-api</artifactId>
<!--<version>${tomcat-servlet-api.version}</version>--> <version>${tomcat.version}</version>
<!--</dependency>--> </dependency>
<!--<dependency>--> <dependency>
<!--<groupId>apache-tomcat</groupId>--> <groupId>org.apache.tomcat</groupId>
<!--<artifactId>tomcat-api</artifactId>--> <artifactId>tomcat-api</artifactId>
<!--<version>${tomcat-api.version}</version>--> <version>${tomcat.version}</version>
<!--</dependency>--> </dependency>
<!-- For local development dependency end.. --> <!-- For local development dependency end.. -->
</dependencies> </dependencies>

View File

@ -0,0 +1,24 @@
package tomcat.request.session.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** author: Ranjith Manickam @ 5 Feb' 2020 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Property {
String name() default "";
String defaultValue() default "";
PropertyType type() default PropertyType.STRING;
enum PropertyType {
STRING,
BOOLEAN,
INTEGER,
LONG
}
}

View File

@ -1,11 +1,8 @@
package tomcat.request.session; package tomcat.request.session.constant;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
public interface SessionConstants { public interface SessionConstants {
byte[] NULL_SESSION = "null".getBytes(); byte[] NULL_SESSION = "null".getBytes();
String CATALINA_BASE = "catalina.base";
String CONF = "conf";
String SESSION_PERSISTENT_POLICIES = "session.persistent.policies";
enum SessionPolicy { enum SessionPolicy {
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST; DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;

View File

@ -45,4 +45,12 @@ public interface DataCache {
* @return - Returns the number of keys that were removed. * @return - Returns the number of keys that were removed.
*/ */
Long delete(String key); Long delete(String key);
/**
* Check the key exists in data-cache.
*
* @param key - key with which the specified value is to be associated.
* @return - Returns true, if the key exists.
*/
Boolean exists(String key);
} }

View File

@ -1,40 +0,0 @@
package tomcat.request.session.data.cache;
/** author: Ranjith Manickam @ 3 Dec' 2018 */
public interface DataCacheConstants {
// redis properties file name
String APPLICATION_PROPERTIES_FILE = "redis-data-cache.properties";
// redis properties
String REDIS_HOSTS = "redis.hosts";
String REDIS_CLUSTER_ENABLED = "redis.cluster.enabled:false";
String REDIS_SENTINEL_ENABLED = "redis.sentinel.enabled:false";
String LB_STICKY_SESSION_ENABLED = "lb.sticky-session.enabled:false";
String REDIS_MAX_ACTIVE = "redis.max.active:10";
String REDIS_TEST_ONBORROW = "redis.test.onBorrow:true";
String REDIS_TEST_ONRETURN = "redis.test.onReturn:true";
String REDIS_MAX_IDLE = "redis.max.idle:5";
String REDIS_MIN_IDLE = "redis.min.idle:1";
String REDIS_TEST_WHILEIDLE = "redis.test.whileIdle:true";
String REDIS_TEST_NUMPEREVICTION = "redis.test.numPerEviction:10";
String REDIS_TIME_BETWEENEVICTION = "redis.time.betweenEviction:60000";
String REDIS_PASSWORD = "redis.password";
String REDIS_DATABASE = "redis.database:0";
String REDIS_TIMEOUT = "redis.timeout:2000";
String REDIS_SENTINEL_MASTER = "redis.sentinel.master:mymaster";
String REDIS_CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying...";
String SESSION_EXPIRY_JOB_INTERVAL = "redis.session.expiry.job.interval:60";
String SESSION_DATA_SYNC_JOB_INTERVAL = "redis.session.data-sync.job.interval:10";
enum RedisConfigType {
DEFAULT,
SENTINEL,
CLUSTER
}
}

View File

@ -2,49 +2,24 @@ package tomcat.request.session.data.cache;
import tomcat.request.session.data.cache.impl.StandardDataCache; import tomcat.request.session.data.cache.impl.StandardDataCache;
import tomcat.request.session.data.cache.impl.redis.RedisCache; import tomcat.request.session.data.cache.impl.redis.RedisCache;
import tomcat.request.session.model.Config;
import java.util.Properties;
/** author: Ranjith Manickam @ 3 Dec' 2018 */ /** author: Ranjith Manickam @ 3 Dec' 2018 */
public class DataCacheFactory { public class DataCacheFactory {
private final Properties properties; private final Config config;
private final int sessionExpiryTime; private final int sessionExpiryTime;
public DataCacheFactory(Properties properties, int sessionExpiryTime) { public DataCacheFactory(Config config, int sessionExpiryTime) {
this.properties = properties; this.config = config;
this.sessionExpiryTime = sessionExpiryTime; this.sessionExpiryTime = sessionExpiryTime;
} }
/** To get data cache. */ /** To get data cache. */
public DataCache getDataCache() { public DataCache getDataCache() {
if (Boolean.parseBoolean(getProperty(this.properties, DataCacheConstants.LB_STICKY_SESSION_ENABLED))) { if (this.config.getLbStickySessionEnabled()) {
return new StandardDataCache(this.properties, this.sessionExpiryTime); return new StandardDataCache(this.config, this.sessionExpiryTime);
} }
return new RedisCache(this.properties); return new RedisCache(this.config);
}
/**
* To get property with the specified key in this properties list.
*
* @param properties - properties list.
* @param key - search key.
* @return - Returns the property value.
*/
public static String getProperty(Properties properties, String key) {
return getProperty(properties, key, null);
}
/**
* To get property with the specified key in this properties list.
*
* @param properties - properties list.
* @param key - search key.
* @param defaultValue - default value.
* @return - - Returns the property value.
*/
public static String getProperty(Properties properties, String key, String defaultValue) {
String[] keyValue = key.split(":");
return properties.getProperty(keyValue[0], (keyValue.length > 1 && defaultValue == null) ? keyValue[1] : defaultValue);
} }
} }

View File

@ -3,14 +3,12 @@ package tomcat.request.session.data.cache.impl;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import tomcat.request.session.data.cache.DataCache; import tomcat.request.session.data.cache.DataCache;
import tomcat.request.session.data.cache.DataCacheConstants;
import tomcat.request.session.data.cache.DataCacheFactory;
import tomcat.request.session.data.cache.impl.redis.RedisCache; import tomcat.request.session.data.cache.impl.redis.RedisCache;
import tomcat.request.session.model.Config;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -32,15 +30,15 @@ public class StandardDataCache extends RedisCache {
private final Executor expiryJobExecutor; private final Executor expiryJobExecutor;
private final Executor dataSyncJobExecutor; private final Executor dataSyncJobExecutor;
public StandardDataCache(Properties properties, int sessionExpiryTime) { public StandardDataCache(Config config, int sessionExpiryTime) {
super(properties); super(config);
this.sessionExpiryTime = sessionExpiryTime; this.sessionExpiryTime = sessionExpiryTime;
this.sessionData = new ConcurrentHashMap<>(); this.sessionData = new ConcurrentHashMap<>();
this.expiryJob = new Date().getTime(); this.expiryJob = new Date().getTime();
this.dataSyncJob = new Date().getTime(); this.dataSyncJob = new Date().getTime();
this.processDataSync = false; this.processDataSync = false;
this.expiryJobTriggerInterval = TimeUnit.MINUTES.toMillis(Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.SESSION_EXPIRY_JOB_INTERVAL))); this.expiryJobTriggerInterval = TimeUnit.MINUTES.toMillis(config.getRedisSessionExpiryJobInterval());
this.dataSyncJobTriggerInterval = TimeUnit.MINUTES.toMillis(Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.SESSION_DATA_SYNC_JOB_INTERVAL))); this.dataSyncJobTriggerInterval = TimeUnit.MINUTES.toMillis(config.getRedisSessionDataSyncJobInterval());
this.expiryJobExecutor = Executors.newSingleThreadExecutor(); this.expiryJobExecutor = Executors.newSingleThreadExecutor();
this.dataSyncJobExecutor = Executors.newSingleThreadExecutor(); this.dataSyncJobExecutor = Executors.newSingleThreadExecutor();
} }
@ -112,6 +110,12 @@ public class StandardDataCache extends RedisCache {
return (value == null) ? 0L : 1L; return (value == null) ? 0L : 1L;
} }
/** {@inheritDoc} */
@Override
public Boolean exists(String key) {
return this.sessionData.containsKey(key);
}
/** Session data. */ /** Session data. */
private static class SessionData implements Serializable { private static class SessionData implements Serializable {
private byte[] value; private byte[] value;

View File

@ -2,17 +2,14 @@ package tomcat.request.session.data.cache.impl.redis;
import redis.clients.jedis.HostAndPort; import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
import tomcat.request.session.data.cache.DataCache; import tomcat.request.session.data.cache.DataCache;
import tomcat.request.session.data.cache.DataCacheConstants; import tomcat.request.session.model.Config;
import tomcat.request.session.data.cache.DataCacheConstants.RedisConfigType; import tomcat.request.session.model.Config.RedisConfigType;
import tomcat.request.session.data.cache.DataCacheFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Properties;
import java.util.Set; import java.util.Set;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
@ -20,72 +17,71 @@ public class RedisCache implements DataCache {
private DataCache dataCache; private DataCache dataCache;
public RedisCache(Properties properties) { public RedisCache(Config config) {
initialize(properties); initialize(config);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public byte[] set(String key, byte[] value) { public byte[] set(String key, byte[] value) {
return dataCache.set(key, value); return this.dataCache.set(key, value);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public Long setnx(String key, byte[] value) { public Long setnx(String key, byte[] value) {
return dataCache.setnx(key, value); return this.dataCache.setnx(key, value);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public Long expire(String key, int seconds) { public Long expire(String key, int seconds) {
return dataCache.expire(key, seconds); return this.dataCache.expire(key, seconds);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public byte[] get(String key) { public byte[] get(String key) {
return dataCache.get(key); return this.dataCache.get(key);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public Long delete(String key) { public Long delete(String key) {
return dataCache.delete(key); return this.dataCache.delete(key);
} }
private void initialize(Properties properties) { /** {@inheritDoc} */
RedisConfigType configType; @Override
if (Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_CLUSTER_ENABLED))) { public Boolean exists(String key) {
configType = RedisConfigType.CLUSTER; return this.dataCache.exists(key);
} else if (Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_ENABLED))) {
configType = RedisConfigType.SENTINEL;
} else {
configType = RedisConfigType.DEFAULT;
} }
String hosts = DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_HOSTS, String.format("%s:%s", Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT)); private void initialize(Config config) {
Collection<?> nodes = getJedisNodes(hosts, configType); Collection<?> nodes = getJedisNodes(config.getRedisHosts(), config.getRedisConfigType());
JedisPoolConfig poolConfig = getPoolConfig(config);
String password = DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_PASSWORD); switch (config.getRedisConfigType()) {
password = (password != null && !password.isEmpty()) ? password : null;
int database = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_DATABASE));
int timeout = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIMEOUT));
timeout = Math.max(timeout, Protocol.DEFAULT_TIMEOUT);
JedisPoolConfig poolConfig = getPoolConfig(properties);
switch (configType) {
case CLUSTER: case CLUSTER:
dataCache = new RedisClusterManager((Set<HostAndPort>) nodes, password, timeout, poolConfig); this.dataCache = new RedisClusterManager((Set<HostAndPort>) nodes,
config.getRedisPassword(),
config.getRedisTimeout(),
poolConfig);
break; break;
case SENTINEL: case SENTINEL:
String masterName = String.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_MASTER)); this.dataCache = new RedisSentinelManager((Set<String>) nodes,
dataCache = new RedisSentinelManager((Set<String>) nodes, masterName, password, database, timeout, poolConfig); config.getRedisSentinelMaster(),
config.getRedisPassword(),
config.getRedisDatabase(),
config.getRedisTimeout(),
poolConfig);
break; break;
default: default:
dataCache = new RedisStandardManager(((List<String>) nodes).get(0), Integer.parseInt(((List<String>) nodes).get(1)), password, database, timeout, poolConfig); this.dataCache = new RedisStandardManager(((List<String>) nodes).get(0),
Integer.parseInt(((List<String>) nodes).get(1)),
config.getRedisPassword(),
config.getRedisDatabase(),
config.getRedisTimeout(),
poolConfig);
break; break;
} }
} }
@ -93,34 +89,19 @@ public class RedisCache implements DataCache {
/** /**
* To get redis pool config. * To get redis pool config.
* *
* @param properties - Redis data cache properties. * @param config - Application config.
* @return - Returns the redis pool config. * @return - Returns the redis pool config.
*/ */
private JedisPoolConfig getPoolConfig(Properties properties) { private JedisPoolConfig getPoolConfig(Config config) {
JedisPoolConfig poolConfig = new JedisPoolConfig(); JedisPoolConfig poolConfig = new JedisPoolConfig();
int maxActive = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_ACTIVE)); poolConfig.setMaxTotal(config.getRedisMaxActive());
poolConfig.setMaxTotal(maxActive); poolConfig.setTestOnBorrow(config.getRedisTestOnBorrow());
poolConfig.setTestOnReturn(config.getRedisTestOnReturn());
boolean testOnBorrow = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONBORROW)); poolConfig.setMaxIdle(config.getRedisMaxIdle());
poolConfig.setTestOnBorrow(testOnBorrow); poolConfig.setMinIdle(config.getRedisMinIdle());
poolConfig.setTestWhileIdle(config.getRedisTestWhileIdle());
boolean testOnReturn = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONRETURN)); poolConfig.setNumTestsPerEvictionRun(config.getRedisTestNumPerEviction());
poolConfig.setTestOnReturn(testOnReturn); poolConfig.setTimeBetweenEvictionRunsMillis(config.getRedisTimeBetweenEviction());
int maxIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_IDLE));
poolConfig.setMaxIdle(maxIdle);
int minIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MIN_IDLE));
poolConfig.setMinIdle(minIdle);
boolean testWhileIdle = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_WHILEIDLE));
poolConfig.setTestWhileIdle(testWhileIdle);
int testNumPerEviction = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_NUMPEREVICTION));
poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
long timeBetweenEviction = Long.parseLong(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIME_BETWEENEVICTION));
poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
return poolConfig; return poolConfig;
} }

View File

@ -15,15 +15,15 @@ class RedisClusterManager extends RedisManager {
private final JedisCluster cluster; private final JedisCluster cluster;
private static final int NUM_RETRIES = 30; private static final int NUM_RETRIES = 30;
private static final int DEFAULT_MAX_REDIRECTIONS = 5; private static final int DEFAULT_MAX_RE_DIRECTIONS = 5;
private static final long FAILIURE_WAIT_TIME = 4000L; private static final long FAILURE_WAIT_TIME = 4000L;
RedisClusterManager(Set<HostAndPort> nodes, RedisClusterManager(Set<HostAndPort> nodes,
String password, String password,
int timeout, int timeout,
JedisPoolConfig poolConfig) { JedisPoolConfig poolConfig) {
super(null, FAILIURE_WAIT_TIME); super(null, FAILURE_WAIT_TIME);
this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS, password, poolConfig); this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_RE_DIRECTIONS, password, poolConfig);
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -115,4 +115,22 @@ class RedisClusterManager extends RedisManager {
} while (retry && tries <= NUM_RETRIES); } while (retry && tries <= NUM_RETRIES);
return retVal; return retVal;
} }
/** {@inheritDoc} */
@Override
public Boolean exists(String key) {
int tries = 0;
boolean retry = true;
Boolean retVal = null;
do {
tries++;
try {
retVal = this.cluster.exists(key);
retry = false;
} catch (JedisRedirectionException | JedisConnectionException ex) {
handleException(tries, ex);
}
} while (retry && tries <= NUM_RETRIES);
return retVal;
}
} }

View File

@ -6,7 +6,6 @@ import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.util.Pool; import redis.clients.jedis.util.Pool;
import tomcat.request.session.data.cache.DataCache; import tomcat.request.session.data.cache.DataCache;
import tomcat.request.session.data.cache.DataCacheConstants;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
abstract class RedisManager implements DataCache { abstract class RedisManager implements DataCache {
@ -14,12 +13,14 @@ abstract class RedisManager implements DataCache {
private static final int NUM_RETRIES = 3; private static final int NUM_RETRIES = 3;
private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class); private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
private final Pool<Jedis> pool; private static final String REDIS_CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying...";
private final long failiureWaitTime;
RedisManager(Pool<Jedis> pool, long failiureWaitTime) { private final Pool<Jedis> pool;
private final long failureWaitTime;
RedisManager(Pool<Jedis> pool, long failureWaitTime) {
this.pool = pool; this.pool = pool;
this.failiureWaitTime = failiureWaitTime; this.failureWaitTime = failureWaitTime;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -112,6 +113,24 @@ abstract class RedisManager implements DataCache {
return retVal; return retVal;
} }
/** {@inheritDoc} */
@Override
public Boolean exists(String key) {
int tries = 0;
boolean retry = true;
Boolean retVal = null;
do {
tries++;
try (Jedis jedis = this.pool.getResource()) {
retVal = jedis.exists(key);
retry = false;
} catch (JedisConnectionException ex) {
handleException(tries, ex);
}
} while (retry && tries <= NUM_RETRIES);
return retVal;
}
/** /**
* To handle jedis exception. * To handle jedis exception.
* *
@ -119,12 +138,12 @@ abstract class RedisManager implements DataCache {
* @param ex - jedis exception. * @param ex - jedis exception.
*/ */
void handleException(int tries, RuntimeException ex) { void handleException(int tries, RuntimeException ex) {
LOGGER.error(DataCacheConstants.REDIS_CONN_FAILED_RETRY_MSG + tries); LOGGER.error(REDIS_CONN_FAILED_RETRY_MSG + tries);
if (tries == NUM_RETRIES) { if (tries == NUM_RETRIES) {
throw ex; throw ex;
} }
try { try {
Thread.sleep(this.failiureWaitTime); Thread.sleep(this.failureWaitTime);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }

View File

@ -8,7 +8,7 @@ import java.util.Set;
/** author: Ranjith Manickam @ 3 Dec' 2018 */ /** author: Ranjith Manickam @ 3 Dec' 2018 */
class RedisSentinelManager extends RedisManager { class RedisSentinelManager extends RedisManager {
private static final long FAILIURE_WAIT_TIME = 2000L; private static final long FAILURE_WAIT_TIME = 2000L;
RedisSentinelManager(Set<String> nodes, RedisSentinelManager(Set<String> nodes,
String masterName, String masterName,
@ -16,6 +16,6 @@ class RedisSentinelManager extends RedisManager {
int database, int database,
int timeout, int timeout,
JedisPoolConfig poolConfig) { JedisPoolConfig poolConfig) {
super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILIURE_WAIT_TIME); super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILURE_WAIT_TIME);
} }
} }

View File

@ -6,7 +6,7 @@ import redis.clients.jedis.JedisPoolConfig;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
class RedisStandardManager extends RedisManager { class RedisStandardManager extends RedisManager {
private static final long FAILIURE_WAIT_TIME = 2000L; private static final long FAILURE_WAIT_TIME = 2000L;
RedisStandardManager(String host, RedisStandardManager(String host,
int port, int port,
@ -14,6 +14,6 @@ class RedisStandardManager extends RedisManager {
int database, int database,
int timeout, int timeout,
JedisPoolConfig poolConfig) { JedisPoolConfig poolConfig) {
super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILIURE_WAIT_TIME); super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILURE_WAIT_TIME);
} }
} }

View File

@ -0,0 +1,266 @@
package tomcat.request.session.model;
import redis.clients.jedis.Protocol;
import tomcat.request.session.annotation.Property;
import java.io.Serializable;
import static tomcat.request.session.annotation.Property.PropertyType.BOOLEAN;
import static tomcat.request.session.annotation.Property.PropertyType.INTEGER;
/** author: Ranjith Manickam @ 5 Feb' 2020 */
public class Config implements Serializable {
private static final long serialVersionUID = 3480402257971437776L;
public static final String APPLICATION_PROPERTIES_FILE = "redis-data-cache.properties";
/** Redis config type. */
public enum RedisConfigType {
DEFAULT,
SENTINEL,
CLUSTER
}
@Property(name = "redis.hosts", defaultValue = "127.0.0.1:6379")
private String redisHosts;
@Property(name = "redis.cluster.enabled", type = BOOLEAN, defaultValue = "false")
private Boolean redisClusterEnabled;
@Property(name = "redis.sentinel.enabled", type = BOOLEAN, defaultValue = "false")
private Boolean redisSentinelEnabled;
@Property(name = "lb.sticky-session.enabled", type = BOOLEAN, defaultValue = "false")
private Boolean lbStickySessionEnabled;
@Property(name = "redis.max.active", type = INTEGER, defaultValue = "10")
private Integer redisMaxActive;
@Property(name = "redis.test.onBorrow", type = BOOLEAN, defaultValue = "true")
private Boolean redisTestOnBorrow;
@Property(name = "redis.test.onReturn", type = BOOLEAN, defaultValue = "true")
private Boolean redisTestOnReturn;
@Property(name = "redis.max.idle", type = INTEGER, defaultValue = "5")
private Integer redisMaxIdle;
@Property(name = "redis.min.idle", type = INTEGER, defaultValue = "1")
private Integer redisMinIdle;
@Property(name = "redis.test.whileIdle", type = BOOLEAN, defaultValue = "true")
private Boolean redisTestWhileIdle;
@Property(name = "redis.test.numPerEviction", type = INTEGER, defaultValue = "10")
private Integer redisTestNumPerEviction;
@Property(name = "redis.time.betweenEviction", type = INTEGER, defaultValue = "60000")
private Integer redisTimeBetweenEviction;
@Property(name = "redis.password")
private String redisPassword;
@Property(name = "redis.database", type = INTEGER, defaultValue = "0")
private Integer redisDatabase;
@Property(name = "redis.timeout", type = INTEGER, defaultValue = "2000")
private Integer redisTimeout;
@Property(name = "redis.sentinel.master", defaultValue = "mymaster")
private String redisSentinelMaster;
@Property(name = "redis.session.expiry.job.interval", type = INTEGER, defaultValue = "60")
private Integer redisSessionExpiryJobInterval;
@Property(name = "redis.session.data-sync.job.interval", type = INTEGER, defaultValue = "10")
private Integer redisSessionDataSyncJobInterval;
@Property(name = "session.persistent.policies", defaultValue = "DEFAULT")
private String sessionPersistentPolicies;
@Property(name = "redis.sso.timeout", type = INTEGER, defaultValue = "0")
private Integer redisSSOTimeout;
public Config() {
}
public Config(String redisHosts,
Boolean redisClusterEnabled,
Boolean redisSentinelEnabled,
Boolean lbStickySessionEnabled,
Integer redisMaxActive,
Boolean redisTestOnBorrow,
Boolean redisTestOnReturn,
Integer redisMaxIdle,
Integer redisMinIdle,
Boolean redisTestWhileIdle,
Integer redisTestNumPerEviction,
Integer redisTimeBetweenEviction,
String redisPassword,
Integer redisDatabase,
Integer redisTimeout,
String redisSentinelMaster,
Integer redisSessionExpiryJobInterval,
Integer redisSessionDataSyncJobInterval,
String sessionPersistentPolicies,
Integer redisSSOTimeout) {
this.redisHosts = redisHosts;
this.redisClusterEnabled = redisClusterEnabled;
this.redisSentinelEnabled = redisSentinelEnabled;
this.lbStickySessionEnabled = lbStickySessionEnabled;
this.redisMaxActive = redisMaxActive;
this.redisTestOnBorrow = redisTestOnBorrow;
this.redisTestOnReturn = redisTestOnReturn;
this.redisMaxIdle = redisMaxIdle;
this.redisMinIdle = redisMinIdle;
this.redisTestWhileIdle = redisTestWhileIdle;
this.redisTestNumPerEviction = redisTestNumPerEviction;
this.redisTimeBetweenEviction = redisTimeBetweenEviction;
this.redisPassword = redisPassword;
this.redisDatabase = redisDatabase;
this.redisTimeout = redisTimeout;
this.redisSentinelMaster = redisSentinelMaster;
this.redisSessionExpiryJobInterval = redisSessionExpiryJobInterval;
this.redisSessionDataSyncJobInterval = redisSessionDataSyncJobInterval;
this.sessionPersistentPolicies = sessionPersistentPolicies;
this.redisSSOTimeout = redisSSOTimeout;
}
/** To get 'redis.hosts' value. */
public String getRedisHosts() {
return redisHosts;
}
/** To get 'redis.cluster.enabled' value. */
public Boolean getRedisClusterEnabled() {
return redisClusterEnabled;
}
/** To get 'redis.sentinel.enabled' value. */
public Boolean getRedisSentinelEnabled() {
return redisSentinelEnabled;
}
/** To get 'lb.sticky-session.enabled' value. */
public Boolean getLbStickySessionEnabled() {
return lbStickySessionEnabled;
}
/** To get 'redis.max.active' value. */
public Integer getRedisMaxActive() {
return redisMaxActive;
}
/** To get 'redis.test.onBorrow' value. */
public Boolean getRedisTestOnBorrow() {
return redisTestOnBorrow;
}
/** To get 'redis.test.onReturn' value. */
public Boolean getRedisTestOnReturn() {
return redisTestOnReturn;
}
/** To get 'redis.max.idle' value. */
public Integer getRedisMaxIdle() {
return redisMaxIdle;
}
/** To get 'redis.min.idle' value. */
public Integer getRedisMinIdle() {
return redisMinIdle;
}
/** To get 'redis.test.whileIdle' value. */
public Boolean getRedisTestWhileIdle() {
return redisTestWhileIdle;
}
/** To get 'redis.test.numPerEviction' value. */
public Integer getRedisTestNumPerEviction() {
return redisTestNumPerEviction;
}
/** To get 'redis.time.betweenEviction' value. */
public Integer getRedisTimeBetweenEviction() {
return redisTimeBetweenEviction;
}
/** To get 'redis.password' value. */
public String getRedisPassword() {
return (redisPassword == null || redisPassword.isEmpty()) ? null : redisPassword;
}
/** To get 'redis.database' value. */
public Integer getRedisDatabase() {
return redisDatabase;
}
/** To get 'redis.timeout' value. */
public Integer getRedisTimeout() {
return Math.max(redisTimeout, Protocol.DEFAULT_TIMEOUT);
}
/** To get 'redis.sentinel.master' value. */
public String getRedisSentinelMaster() {
return redisSentinelMaster;
}
/** To get 'redis.session.expiry.job.interval' value. */
public Integer getRedisSessionExpiryJobInterval() {
return redisSessionExpiryJobInterval;
}
/** To get 'redis.session.data-sync.job.interval' value. */
public Integer getRedisSessionDataSyncJobInterval() {
return redisSessionDataSyncJobInterval;
}
/** To get 'session.persistent.policies' value */
public String getSessionPersistentPolicies() {
return sessionPersistentPolicies;
}
/** To get 'redis.sso.timeout' value */
public Integer getRedisSSOTimeout() {
return redisSSOTimeout;
}
/** {@inheritDoc} */
@Override
public String toString() {
return "Config{" +
"redisHosts='" + redisHosts + '\'' +
", redisClusterEnabled=" + redisClusterEnabled +
", redisSentinelEnabled=" + redisSentinelEnabled +
", lbStickySessionEnabled=" + lbStickySessionEnabled +
", redisMaxActive=" + redisMaxActive +
", redisTestOnBorrow=" + redisTestOnBorrow +
", redisTestOnReturn=" + redisTestOnReturn +
", redisMaxIdle=" + redisMaxIdle +
", redisMinIdle=" + redisMinIdle +
", redisTestWhileIdle=" + redisTestWhileIdle +
", redisTestNumPerEviction=" + redisTestNumPerEviction +
", redisTimeBetweenEviction=" + redisTimeBetweenEviction +
", redisPassword='" + redisPassword + '\'' +
", redisDatabase=" + redisDatabase +
", redisTimeout=" + redisTimeout +
", redisSentinelMaster='" + redisSentinelMaster + '\'' +
", redisSessionExpiryJobInterval=" + redisSessionExpiryJobInterval +
", redisSessionDataSyncJobInterval=" + redisSessionDataSyncJobInterval +
", sessionPersistentPolicies='" + sessionPersistentPolicies + '\'' +
", redisSSOTimeout='" + redisSSOTimeout + '\'' +
'}';
}
/** To get redis config type. */
public RedisConfigType getRedisConfigType() {
if (this.getRedisClusterEnabled()) {
return RedisConfigType.CLUSTER;
} else if (this.getRedisSentinelEnabled()) {
return RedisConfigType.SENTINEL;
}
return RedisConfigType.DEFAULT;
}
}

View File

@ -1,4 +1,8 @@
package tomcat.request.session; package tomcat.request.session.model;
import org.apache.catalina.Manager;
import org.apache.catalina.session.StandardSession;
import tomcat.request.session.redis.SessionManager;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
@ -8,11 +12,6 @@ import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.apache.catalina.Manager;
import org.apache.catalina.session.StandardSession;
import tomcat.request.session.redis.SessionManager;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
public class Session extends StandardSession { public class Session extends StandardSession {

View File

@ -1,4 +1,4 @@
package tomcat.request.session; package tomcat.request.session.model;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
public class SessionContext { public class SessionContext {

View File

@ -1,4 +1,4 @@
package tomcat.request.session; package tomcat.request.session.model;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;

View File

@ -0,0 +1,100 @@
package tomcat.request.session.model;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.SingleSignOnListener;
import org.apache.catalina.authenticator.SingleSignOnSessionKey;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.Principal;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/** author: Ranjith Manickam @ 20 Mar' 2020 */
public class SingleSignOnEntry implements Serializable {
private static final long serialVersionUID = 4590485271396917062L;
private String authType;
private String password;
private Principal principal;
private String username;
private boolean canReauthenticate = false;
private final ConcurrentMap<SingleSignOnSessionKey, SingleSignOnSessionKey> sessionKeys;
public SingleSignOnEntry() {
this.sessionKeys = new ConcurrentHashMap<>();
}
public SingleSignOnEntry(Principal principal, String authType, String username, String password) {
this.sessionKeys = new ConcurrentHashMap<>();
this.updateCredentials(principal, authType, username, password);
}
public void addSession(String ssoId, Session session) {
SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
SingleSignOnSessionKey currentKey = this.sessionKeys.putIfAbsent(key, key);
if (currentKey == null) {
session.addSessionListener(new SingleSignOnListener(ssoId));
}
}
public void removeSession(Session session) {
SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
this.sessionKeys.remove(key);
}
public Set<SingleSignOnSessionKey> findSessions() {
return this.sessionKeys.keySet();
}
public String getAuthType() {
return this.authType;
}
public boolean getCanReauthenticate() {
return this.canReauthenticate;
}
public String getPassword() {
return this.password;
}
public Principal getPrincipal() {
return this.principal;
}
public String getUsername() {
return this.username;
}
public synchronized void updateCredentials(Principal principal, String authType, String username, String password) {
this.principal = principal;
this.authType = authType;
this.username = username;
this.password = password;
this.canReauthenticate = "BASIC".equals(authType) || "FORM".equals(authType);
}
public void writeObjectData(ObjectOutputStream out) throws IOException {
try (ObjectOutputStream outputStream = new ObjectOutputStream(out)) {
outputStream.writeObject(this);
outputStream.flush();
}
}
public void readObjectData(ObjectInputStream in) throws IOException, ClassNotFoundException {
try (ObjectInputStream inputStream = new ObjectInputStream(in)) {
SingleSignOnEntry entry = (SingleSignOnEntry) inputStream.readObject();
this.authType = entry.authType;
this.password = entry.password;
this.principal = entry.principal;
this.username = entry.username;
this.canReauthenticate = entry.canReauthenticate;
this.sessionKeys.putAll(entry.sessionKeys);
}
}
}

View File

@ -31,7 +31,9 @@ public class SessionHandlerValve extends ValveBase {
LOGGER.error("Error processing request", ex); LOGGER.error("Error processing request", ex);
throw new BackendException(); throw new BackendException();
} finally { } finally {
manager.afterRequest(); if (this.manager != null) {
this.manager.afterRequest();
}
} }
} }
} }

View File

@ -9,24 +9,22 @@ import org.apache.catalina.Valve;
import org.apache.catalina.session.ManagerBase; import org.apache.catalina.session.ManagerBase;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import tomcat.request.session.SerializationUtil; import tomcat.request.session.constant.SessionConstants;
import tomcat.request.session.Session; import tomcat.request.session.constant.SessionConstants.SessionPolicy;
import tomcat.request.session.SessionConstants;
import tomcat.request.session.SessionConstants.SessionPolicy;
import tomcat.request.session.SessionContext;
import tomcat.request.session.SessionMetadata;
import tomcat.request.session.data.cache.DataCache; import tomcat.request.session.data.cache.DataCache;
import tomcat.request.session.data.cache.DataCacheConstants;
import tomcat.request.session.data.cache.DataCacheFactory; import tomcat.request.session.data.cache.DataCacheFactory;
import tomcat.request.session.model.Config;
import tomcat.request.session.model.Session;
import tomcat.request.session.model.SessionContext;
import tomcat.request.session.model.SessionMetadata;
import tomcat.request.session.model.SingleSignOnEntry;
import tomcat.request.session.util.ConfigUtil;
import tomcat.request.session.util.SerializationUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Properties;
import java.util.Set; import java.util.Set;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
@ -34,10 +32,11 @@ public class SessionManager extends ManagerBase implements Lifecycle {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class); private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class);
private Integer ssoTimeout;
private DataCache dataCache; private DataCache dataCache;
private SerializationUtil serializer; private SerializationUtil serializer;
private ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>(); private final ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>();
private Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT); private final Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT);
public boolean getSaveOnChange() { public boolean getSaveOnChange() {
return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE); return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE);
@ -211,15 +210,16 @@ public class SessionManager extends ManagerBase implements Lifecycle {
/** To initialize the session manager. */ /** To initialize the session manager. */
private void initialize() { private void initialize() {
try { try {
Properties properties = getApplicationProperties(); Config config = ConfigUtil.getConfig();
this.dataCache = new DataCacheFactory(properties, getSessionTimeout(null)).getDataCache(); this.ssoTimeout = config.getRedisSSOTimeout();
this.dataCache = new DataCacheFactory(config, getSessionTimeout(null)).getDataCache();
this.serializer = new SerializationUtil(); this.serializer = new SerializationUtil();
Context context = getContextIns(); Context context = getContextIns();
ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null; ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null;
this.serializer.setClassLoader(loader); this.serializer.setClassLoader(loader);
setSessionPersistentPolicies(properties); setSessionPersistentPolicies(config);
} catch (Exception ex) { } catch (Exception ex) {
LOGGER.error("Error occurred while initializing the session manager..", ex); LOGGER.error("Error occurred while initializing the session manager..", ex);
throw ex; throw ex;
@ -326,8 +326,8 @@ public class SessionManager extends ManagerBase implements Lifecycle {
} }
/** To set session persistent policies */ /** To set session persistent policies */
private void setSessionPersistentPolicies(Properties properties) { private void setSessionPersistentPolicies(Config config) {
String sessionPolicies = properties.getProperty(SessionConstants.SESSION_PERSISTENT_POLICIES); String sessionPolicies = config.getSessionPersistentPolicies();
if (sessionPolicies == null || sessionPolicies.isEmpty()) { if (sessionPolicies == null || sessionPolicies.isEmpty()) {
return; return;
} }
@ -339,30 +339,46 @@ public class SessionManager extends ManagerBase implements Lifecycle {
} }
} }
/** To get redis data cache properties. */ /** To set single-sign-on entry to cache. */
private Properties getApplicationProperties() { void setSingleSignOnEntry(String ssoId, SingleSignOnEntry entry) {
Properties properties = new Properties(); if (entry == null) {
try { return;
String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator)
.concat(SessionConstants.CONF).concat(File.separator)
.concat(DataCacheConstants.APPLICATION_PROPERTIES_FILE);
InputStream resourceStream = null;
try {
resourceStream = (!filePath.isEmpty() && new File(filePath).exists()) ? new FileInputStream(filePath) : null;
if (resourceStream == null) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
resourceStream = loader.getResourceAsStream(DataCacheConstants.APPLICATION_PROPERTIES_FILE);
}
properties.load(resourceStream);
} finally {
if (resourceStream != null) {
resourceStream.close();
} }
try {
byte[] data = this.serializer.serializeSingleSignOnEntry(entry);
this.dataCache.set(ssoId, data);
if (this.ssoTimeout > 0) {
this.dataCache.expire(ssoId, this.ssoTimeout);
} }
} catch (IOException ex) { } catch (IOException ex) {
LOGGER.error("Error while retrieving application properties", ex); LOGGER.error("Error occurred while serializing the single-sign-on entry..", ex);
} }
return properties; }
/** To get single-sign-on entry from cache. */
SingleSignOnEntry getSingleSignOnEntry(String ssoId) {
byte[] data = this.dataCache.get(ssoId);
if (data == null) {
return null;
}
SingleSignOnEntry entry = new SingleSignOnEntry();
try {
this.serializer.deserializeSingleSignOnEntry(data, entry);
} catch (IOException | ClassNotFoundException ex) {
LOGGER.error("Error occurred while de-serializing the single-sign-on entry..", ex);
return null;
}
return entry;
}
/** To check single-sign-on entry exists from cache. */
Boolean singleSignOnEntryExists(String ssoId) {
return this.dataCache.exists(ssoId);
}
/** To delete single-sign-on entry from cache. */
void deleteSingleSignOnEntry(String ssoId) {
this.dataCache.delete(ssoId);
} }
} }

View File

@ -0,0 +1,263 @@
package tomcat.request.session.redis;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Manager;
import org.apache.catalina.Realm;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.authenticator.SingleSignOn;
import org.apache.catalina.authenticator.SingleSignOnSessionKey;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tomcat.request.session.exception.BackendException;
import tomcat.request.session.model.SingleSignOnEntry;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.security.Principal;
import java.util.Set;
/** author: Ranjith Manickam @ 20 Mar' 2020 */
public class SingleSignOnValve extends SingleSignOn {
private static final Logger LOGGER = LoggerFactory.getLogger(SingleSignOnValve.class);
private Engine engine;
private SessionManager manager;
/** {@inheritDoc} */
@Override
protected synchronized void startInternal() throws LifecycleException {
Container c;
for (c = this.getContainer(); c != null && !(c instanceof Engine); c = c.getParent()) {
}
if (c instanceof Engine) {
this.engine = (Engine) c;
}
super.startInternal();
}
/** {@inheritDoc} */
@Override
public void invoke(Request request, Response response) throws BackendException {
try {
this.setSessionManager(request.getContext().getManager());
request.removeNote("org.apache.catalina.request.SSOID");
LOGGER.debug("singleSignOn.debug.invoke, requestURI: {}", request.getRequestURI());
if (request.getUserPrincipal() == null) {
LOGGER.debug("singleSignOn.debug.cookieCheck");
Cookie cookie = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie value : cookies) {
if (Constants.SINGLE_SIGN_ON_COOKIE.equals(value.getName())) {
cookie = value;
break;
}
}
}
if (cookie == null) {
LOGGER.debug("singleSignOn.debug.cookieNotFound");
} else {
LOGGER.debug("singleSignOn.debug.principalCheck, ssoId: {}", cookie.getValue());
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(cookie.getValue());
if (entry == null) {
LOGGER.debug("singleSignOn.debug.principalNotFound, ssoId: {}", cookie.getValue());
cookie.setValue("REMOVE");
cookie.setMaxAge(0);
cookie.setPath("/");
String domain = this.getCookieDomain();
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setSecure(request.isSecure());
if (request.getServletContext().getSessionCookieConfig().isHttpOnly() || request.getContext().getUseHttpOnly()) {
cookie.setHttpOnly(true);
}
response.addCookie(cookie);
} else {
LOGGER.debug("singleSignOn.debug.principalFound, principal: {}, authType: {}", (entry.getPrincipal() != null ? entry.getPrincipal().getName() : ""), entry.getAuthType());
request.setNote("org.apache.catalina.request.SSOID", cookie.getValue());
if (!this.getRequireReauthentication()) {
request.setAuthType(entry.getAuthType());
request.setUserPrincipal(entry.getPrincipal());
}
}
}
} else {
LOGGER.debug("singleSignOn.debug.hasPrincipal, principal: {}", request.getUserPrincipal().getName());
}
this.getNext().invoke(request, response);
} catch (IOException | ServletException | RuntimeException ex) {
LOGGER.error("Error processing request", ex);
throw new BackendException();
}
}
/** {@inheritDoc} */
@Override
public void sessionDestroyed(String ssoId, Session session) {
if (this.getState().isAvailable()) {
if ((session.getMaxInactiveInterval() <= 0 ||
session.getIdleTimeInternal() < (long) (session.getMaxInactiveInterval() * 1000))
&& session.getManager().getContext().getState().isAvailable()) {
LOGGER.debug("singleSignOn.debug.sessionLogout, session: {}", session);
this.removeSession(ssoId, session);
if (this.manager.singleSignOnEntryExists(ssoId)) {
this.deregister(ssoId);
}
return;
}
LOGGER.debug("singleSignOn.debug.sessionTimeout, ssoId: {}, session: {}", ssoId, session);
this.removeSession(ssoId, session);
}
}
/** {@inheritDoc} */
@Override
protected boolean associate(String ssoId, Session session) {
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
if (entry == null) {
LOGGER.debug("singleSignOn.debug.associateFail, ssoId: {}, session: {}", ssoId, session);
return false;
}
LOGGER.debug("singleSignOn.debug.associate, ssoId: {}, session: {}", ssoId, session);
entry.addSession(ssoId, session);
this.manager.setSingleSignOnEntry(ssoId, entry);
return true;
}
/** {@inheritDoc} */
@Override
protected void deregister(String ssoId) {
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
this.manager.deleteSingleSignOnEntry(ssoId);
if (entry == null) {
LOGGER.debug("singleSignOn.debug.deregisterFail, ssoId: {}", ssoId);
return;
}
Set<SingleSignOnSessionKey> ssoKeys = entry.findSessions();
if (ssoKeys.isEmpty()) {
LOGGER.debug("singleSignOn.debug.deregisterNone, ssoId: {}", ssoId);
}
for (SingleSignOnSessionKey ssoKey : ssoKeys) {
this.expire(ssoKey);
LOGGER.debug("singleSignOn.debug.deregister, ssoKey: {}, ssoId: {}", ssoKey, ssoId);
}
}
/** {@inheritDoc} */
@Override
protected boolean reauthenticate(String ssoId, Realm realm, Request request) {
if (ssoId == null || realm == null) {
return false;
}
boolean reAuthenticated = false;
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
if (entry != null && entry.getCanReauthenticate()) {
String username = entry.getUsername();
if (username != null) {
Principal reAuthPrincipal = realm.authenticate(username, entry.getPassword());
if (reAuthPrincipal != null) {
reAuthenticated = true;
request.setAuthType(entry.getAuthType());
request.setUserPrincipal(reAuthPrincipal);
}
}
}
return reAuthenticated;
}
/** {@inheritDoc} */
@Override
protected void register(String ssoId, Principal principal, String authType, String username, String password) {
LOGGER.debug("singleSignOn.debug.register, ssoId: {}, principal: {}, authType: {}", ssoId, (principal != null ? principal.getName() : ""), authType);
SingleSignOnEntry entry = new SingleSignOnEntry(principal, authType, username, password);
this.manager.setSingleSignOnEntry(ssoId, entry);
}
/** {@inheritDoc} */
@Override
protected boolean update(String ssoId, Principal principal, String authType, String username, String password) {
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
if (entry == null || !entry.getCanReauthenticate()) {
return false;
}
LOGGER.debug("singleSignOn.debug.update, ssoId: {}, authType: {}", ssoId, authType);
entry.updateCredentials(principal, authType, username, password);
this.manager.setSingleSignOnEntry(ssoId, entry);
return true;
}
/** {@inheritDoc} */
@Override
protected void removeSession(String ssoId, Session session) {
LOGGER.debug("singleSignOn.debug.removeSession, ssoId: {}, session: {}", ssoId, session);
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
if (entry != null) {
entry.removeSession(session);
if (entry.findSessions().size() == 0) {
this.deregister(ssoId);
}
}
}
/** To set session manager. */
void setSessionManager(Manager manager) {
if (manager != null) {
this.manager = (SessionManager) manager;
}
}
/** To expire session. */
private void expire(SingleSignOnSessionKey key) {
if (this.engine == null) {
LOGGER.warn("singleSignOn.sessionExpire.engineNull, key: {}", key);
} else {
Container host = this.engine.findChild(key.getHostName());
if (host == null) {
LOGGER.warn("singleSignOn.sessionExpire.hostNotFound, key: {}", key);
} else {
Context context = (Context) host.findChild(key.getContextName());
if (context == null) {
LOGGER.warn("singleSignOn.sessionExpire.contextNotFound, key: {}", key);
} else {
Session session;
try {
session = this.manager.findSession(key.getSessionId());
} catch (IOException ex) {
LOGGER.warn("singleSignOn.sessionExpire.managerError, key: {}, exception: {}", key, ex);
return;
}
if (session == null) {
LOGGER.warn("singleSignOn.sessionExpire.sessionNotFound, key: {}", key);
} else {
session.expire();
}
}
}
}
}
}

View File

@ -0,0 +1,154 @@
package tomcat.request.session.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tomcat.request.session.annotation.Property;
import tomcat.request.session.model.Config;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Properties;
/** author: Ranjith Manickam @ 5 Feb' 2020 */
public class ConfigUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class);
private static final String CONF = "conf";
private static final String CATALINA_BASE = "catalina.base";
/** To get application config. */
public static Config getConfig() {
Properties properties = getApplicationProperties();
Config config = new Config();
for (Field field : Config.class.getDeclaredFields()) {
Property property = field.getAnnotation(Property.class);
if (property == null) {
continue;
}
String propertyName = property.name();
Property.PropertyType propertyType = property.type();
if (propertyName.isEmpty()) {
continue;
}
String value = properties.getProperty(propertyName);
if (isSystemProperty(value)) {
value = getSystemProperty(value);
}
if (value == null || value.isEmpty()) {
value = property.defaultValue();
if (value.isEmpty()) {
continue;
}
}
field.setAccessible(true);
try {
switch (propertyType) {
case BOOLEAN:
field.set(config, Boolean.parseBoolean(value));
break;
case INTEGER:
field.set(config, Integer.parseInt(value));
break;
case LONG:
field.set(config, Long.parseLong(value));
break;
case STRING:
default:
field.set(config, value);
break;
}
} catch (Exception ex) {
LOGGER.error("Error while initializing application properties", ex);
}
}
return config;
}
/** To get redis data cache properties. */
private static Properties getApplicationProperties() {
Properties properties = new Properties();
try {
String filePath = System.getProperty(CATALINA_BASE)
.concat(File.separator)
.concat(CONF).concat(File.separator)
.concat(Config.APPLICATION_PROPERTIES_FILE);
InputStream resourceStream = null;
try {
resourceStream = (!filePath.isEmpty() && new File(filePath).exists()) ? new FileInputStream(filePath) : null;
if (resourceStream == null) {
LOGGER.info("Initializing tomcat redis session manager with default properties");
ClassLoader loader = Thread.currentThread().getContextClassLoader();
resourceStream = loader.getResourceAsStream(Config.APPLICATION_PROPERTIES_FILE);
}
properties.load(resourceStream);
} finally {
if (resourceStream != null) {
resourceStream.close();
}
}
} catch (IOException ex) {
LOGGER.error("Error while retrieving application properties", ex);
}
return properties;
}
/**
* To get property with the specified key from system property.
*
* @param key - search key.
* @return - Returns the system property value.
*/
private static String getSystemProperty(String key) {
int fromIndex = 0;
while (true) {
int beginIndex = key.indexOf("${", fromIndex);
int endIndex = key.indexOf("}", fromIndex);
if (beginIndex < 0 || endIndex < 0) {
break;
}
String expression = key.substring(beginIndex + 2, endIndex);
String value = System.getProperty(expression);
if (value == null || value.isEmpty()) {
fromIndex = endIndex + 1;
continue;
}
key = key.replace(String.format("${%s}", expression), value);
}
return key;
}
/**
* To check if the value is from system property.
*
* @param key - search key.
* @return - Returns true if the key start with '${' and ends with '}'.
*/
private static boolean isSystemProperty(String key) {
if (key == null || key.isEmpty()) {
return false;
}
int beginIndex = key.indexOf("${");
int endIndex = key.indexOf("}");
return beginIndex >= 0 && endIndex >= 0 && beginIndex < endIndex;
}
}

View File

@ -1,6 +1,9 @@
package tomcat.request.session; package tomcat.request.session.util;
import org.apache.catalina.util.CustomObjectInputStream; import org.apache.catalina.util.CustomObjectInputStream;
import tomcat.request.session.model.Session;
import tomcat.request.session.model.SessionMetadata;
import tomcat.request.session.model.SingleSignOnEntry;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
@ -74,4 +77,25 @@ public class SerializationUtil {
session.readObjectData(ois); session.readObjectData(ois);
} }
} }
/** To serialize single-sign-on entry. */
public byte[] serializeSingleSignOnEntry(SingleSignOnEntry entry) throws IOException {
byte[] serialized;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos))) {
entry.writeObjectData(oos);
oos.flush();
serialized = bos.toByteArray();
}
return serialized;
}
/** To de-serialize single-sign-on entry. */
public void deserializeSingleSignOnEntry(byte[] data, SingleSignOnEntry entry)
throws IOException, ClassNotFoundException {
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));
ObjectInputStream ois = new CustomObjectInputStream(bis, this.loader)) {
entry.readObjectData(ois);
}
}
} }

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<div id="download_summary"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
$( document ).ready(function() {
$.ajax({
type: "GET",
dataType: "jsonp",
url: "https://api.github.com/repos/ran-jit/tomcat-cluster-redis-session-manager/releases",
success: function(data){
let content = "<table border='1px' style='width: 80%;margin-left: 10%;margin-right: 10%;line-height: 1.5;'><tr><th style='width: 30%;'>tag</th><th>asset name & download count</th></tr>";
for(let i=0; i< data.data.length; i++) {
const tag = data.data[i];
for(var j=0; j< tag.assets.length; j++) {
const asset = tag.assets[j];
content = content + "<tr><td style='width: 10%;'>"+ tag.tag_name + "</td>";
content = content + "<td>" + asset.name + "<div style='text-align:right;'>" + asset.download_count + "</div></td></tr>";
}
}
content = content + "</table>";
document.getElementById("download_summary").innerHTML=content;
}
});
});
</script>
</body>
</html>

View File

@ -1,5 +1,7 @@
#-- Redis data-cache configuration #-- Redis data-cache configuration
# - ${ENV_VARIABLE_NAME}
#- redis hosts. ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, .... #- redis hosts. ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, ....
redis.hosts=127.0.0.1:6379 redis.hosts=127.0.0.1:6379
@ -32,3 +34,6 @@ lb.sticky-session.enabled=false
# 1. SAVE_ON_CHANGE: every time session.setAttribute() or session.removeAttribute() is called the session will be saved. # 1. SAVE_ON_CHANGE: every time session.setAttribute() or session.removeAttribute() is called the session will be saved.
# 2. ALWAYS_SAVE_AFTER_REQUEST: force saving after every request, regardless of whether or not the manager has detected changes to the session. # 2. ALWAYS_SAVE_AFTER_REQUEST: force saving after every request, regardless of whether or not the manager has detected changes to the session.
session.persistent.policies=DEFAULT session.persistent.policies=DEFAULT
#- single-sign-on session timeout. (default value: 0 ms (-no expiry))
redis.sso.timeout=0