Spring Sessions 没有内置的方法可以做到这一点。但是,有一些解决方法。
一种解决方案是围绕存储库创建一个 cglib 代理。 这是一个基于 JdbcIndexedSessionRepository 的示例,但它应该是相对的,可以将其扩展到任何其他会话存储库。
需要注意的是,如果 SessionRepository 超类没有默认构造函数,cglib 要求您为构造函数传入参数。在 JdbcIndexedSessionRepository 的情况下,参数需要不为空,但它们是接口,Spring 提供了易于构造的实现。我本可以使用反射从现有存储库中获取对象,但没有必要这样做,因为从未实际使用过代理的超类。
@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()}
);
}
};
}
}
另一个解决方案是使用 SessionRepositoryCustomizer 来更改存储库调用的 SQL 查询。当然,这仅适用于您的存储库基于 SQL 数据库,例如 JdbcIndexedSessionRepository。 此代码使用 MySQL 特定行为和 UNIX_TIMESTAMP() 函数,但将其扩展到大多数其他 SQL 数据库应该相对简单。
@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);
};
}
}