如何为 Spring 会话设置绝对会话超时
How to set absolute session timeout for a Spring Session
根据 OWASP 会话必须有一个绝对超时,它定义了会话可以处于活动状态的最长时间。我知道如何使用 server.servlet.session.timeout
设置 spring 会话的最大不活动超时,但是我不确定如何设置会话的绝对超时。我想我可以为 Cookie 设置 Max-Age
属性,这可能会用作绝对超时,但是我想知道是否可以在服务器端会话上以某种方式设置绝对超时?
此功能未在 Spring 会话中实现。
有关解决方法,请参阅 https://github.com/spring-projects/spring-session/issues/922。
没有 built-in 方法可以使用 Spring 会话。但是,有一些解决方法。
一个解决方案是围绕存储库创建一个 cglib 代理。 这是一个基于JdbcIndexedSessionRepository 的示例,但应该相对于将它扩展到任何其他会话存储库。
需要注意的是,如果 SessionRepository super class 没有默认构造函数,cglib 要求您为构造函数传入参数。在 JdbcIndexedSessionRepository 的情况下,参数需要是 non-null,但它们是接口并且 Spring 提供了易于构造的实现。我本可以使用反射从现有存储库中获取对象,但没有必要这样做,因为代理的 superclass 从未实际使用过。
@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);
};
}
}
根据 OWASP 会话必须有一个绝对超时,它定义了会话可以处于活动状态的最长时间。我知道如何使用 server.servlet.session.timeout
设置 spring 会话的最大不活动超时,但是我不确定如何设置会话的绝对超时。我想我可以为 Cookie 设置 Max-Age
属性,这可能会用作绝对超时,但是我想知道是否可以在服务器端会话上以某种方式设置绝对超时?
此功能未在 Spring 会话中实现。 有关解决方法,请参阅 https://github.com/spring-projects/spring-session/issues/922。
没有 built-in 方法可以使用 Spring 会话。但是,有一些解决方法。
一个解决方案是围绕存储库创建一个 cglib 代理。 这是一个基于JdbcIndexedSessionRepository 的示例,但应该相对于将它扩展到任何其他会话存储库。
需要注意的是,如果 SessionRepository super class 没有默认构造函数,cglib 要求您为构造函数传入参数。在 JdbcIndexedSessionRepository 的情况下,参数需要是 non-null,但它们是接口并且 Spring 提供了易于构造的实现。我本可以使用反射从现有存储库中获取对象,但没有必要这样做,因为代理的 superclass 从未实际使用过。
@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);
};
}
}