Reputation: 753
As per OWASP sesssion's must have an absolute timeout which defines the maximum amount of time a session can be active. I know how to set the max inactivity timeout for a spring session using server.servlet.session.timeout
however I am not sure how to set the absolute timeout for the session. I guess I could set the Max-Age
attribute for the Cookie which would potentially serve as an absolute timeout, however I was wondering if the absolute timeout could be somehow set on the server side session?
Upvotes: 1
Views: 1375
Reputation: 60
This feature is not implemented in Spring sessions.
A request for this feature at https://github.com/spring-projects/spring-session/issues/922 was rejected, with this workaround provided:
You could provide a delegating implementation of the
SessionRepository
interface that delegates to another implementation ofSessionRepository
(i.e. Redis) and then when it retrieves the session determines if the creation date is past the absolute session timeout. If it is, then you would invalidate the session and return a null value for the lookup.
(More implementation details are discussed in the issue comments.)
Upvotes: 2
Reputation: 109
There's no built-in way to do it with Spring Sessions. However, there are some workarounds.
One solution is to create a cglib proxy around the repository. This is an example based on the JdbcIndexedSessionRepository, but it should be relatively to extend it to any other session repository.
One note is that cglib requires you to pass in parameters for the constructor if the SessionRepository super class does not have a default constructor. In the case of JdbcIndexedSessionRepository, the parameters need to be non-null, but they are interfaces and Spring provides implementations that are simple to construct. I could have instead used reflection to get the objects from the existing repository, but there was no need to do that since the superclass of the proxy is never actually used.
@Configuration
class SessionConfiguration {
public static final Duration ABSOLUTE_SESSION_TIMEOUT = Duration.of(8, ChronoUnit.HOURS);
/**
* Configures the session repository to have all sessions timeout after at least
* {@link SessionConfiguration#ABSOLUTE_SESSION_TIMEOUT}.
*
* Requires the session repository to be a JdbcIndexedSessionRepository.
*/
@Bean
public static BeanPostProcessor absoluteSessionRepositoryBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) {
if (!(bean instanceof JdbcIndexedSessionRepository)) {
return bean;
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(JdbcIndexedSessionRepository.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
if (!method.getName().equals("findById")) {
return method.invoke(bean, args);
}
Session session = (Session) method.invoke(bean, args);
if (session == null) {
return null;
}
Instant timeoutTime = session.getCreationTime().plus(ABSOLUTE_SESSION_TIMEOUT);
if (timeoutTime.compareTo(Instant.now()) < 0) {
return null;
}
return session;
});
return enhancer.create(
new Class[]{JdbcOperations.class, TransactionOperations.class},
new Object[]{new JdbcTemplate(), new TransactionTemplate()}
);
}
};
}
}
Another solution is to use a SessionRepositoryCustomizer to alter the SQL query called by the repository. Of course, this only works if your repository is based on a SQL database, like JdbcIndexedSessionRepository is. This code uses MySQL specific behavior with the UNIX_TIMESTAMP()
function, but it should be relatively simple to extend it to most other SQL databases.
@Configuration
class SessionConfiguration {
public static final Duration ABSOLUTE_SESSION_TIMEOUT = Duration.of(8, ChronoUnit.HOURS);
/**
* Configures the session repository to have all sessions timeout after
* {@link SessionConfiguration#ABSOLUTE_SESSION_TIMEOUT}.
*
* Requires the session repository to be a JdbcIndexedSessionRepository and for the database to be MySQL.
* The customizer sets a custom query that relies on the MySQL-specific UNIX_TIMESTAMP function.
*
* UNIX_TIMESTAMP() returns the current time in seconds since the epoch while the session's CREATION_TIME is in
* milliseconds since the epoch, so we need to multiply the current time by 1000 to get comparable numbers.
* See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_unix-timestamp for more
*/
@Bean
public SessionRepositoryCustomizer<JdbcIndexedSessionRepository> absoluteTimeoutSessionRepositoryCustomizer() {
return sessionRepository -> {
String query = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, "
+ "S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.SESSION_ID = ? AND (UNIX_TIMESTAMP() * 1000 - S.CREATION_TIME) < "
+ ABSOLUTE_SESSION_TIMEOUT.toMillis();
sessionRepository.setGetSessionQuery(query);
};
}
}
Upvotes: 0