Java Spring Reactive WebSession на примере
Рассмотрим простой пример создания сессии, её использования и инвалидации в реактивном стеке Spring’а.
Зависимость
Добавим org.springframework.session:spring-session-core
:
org.springframework.boot
spring-boot-starter-webflux
// ...
org.springframework.session
spring-session-core
Конфигурация
В качестве примера будем хранить сессии в ConcurrentHashMap (вы наверное захотите Redis, Hazelcast, другое):
@Configuration
public class SessionConfig {
@Bean
public ReactiveSessionRepository reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
}
Инъекция WebSession
Ниже пример эндпоинтов, где экземпляр объекта WebSession инъектируется Spring’ом в параметры эндпоинтов:
@GetMapping("/session/new")
public Mono sessionNew(final WebSession webSession) {
webSession.start();
// webSession.getAttributes().put(key, value);
return Mono.just(webSession.getId());
}
@GetMapping("/session/info")
public Mono sessionInfo(final WebSession webSession) {
// webSession.getAttribute(key);
// webSession.getAttributes().put(key, value);
return Mono.just(webSession.getId() + " exp=" + webSession.isExpired() + " started=" + webSession.isStarted() + " "
+ webSession.getCreationTime() + " idle=" + webSession.getMaxIdleTime());
}
Каждый новый вызов /session/new
будет создавать новую сессию. По завершению эндпоинта сессия сохраняется в репозиторий.
Вызов /session/info инъектирует ту же самую сессию только в том случае, если вы при вызове передадите Cookie SESSION={sessionId}.
curl http://localhost:8080/session/info --cookie 'SESSION=session-id-from-first-endpoint-response'
Это стандартное поведение DefaultWebSessionManager
который подтягивается автоконфигурацией.
Причем, если сессия expired (по умолчанию — 30 минут), то в /session/info webSession
инъектируется новая сессия, которая еще не была стартована.
Можно в конфигурации сменить sessionIdResolver
, чтобы вместо Cookie, id сессии передавать например в HTTP хэдере с именемSESSION :
@Bean
public WebSessionIdResolver webSessionIdResolver() {
return new HeaderWebSessionIdResolver();
}
или запилить свой кастомный session id resolver.
Однако, в этом случае, если вы например используете OAuth2 login, то Keycloak может выкидывать ошибку credentials error
, по всей видимости из-за того, что в конфигурации по умолчанию использует Cookie для сессий авторизации.
Альтернатива инъекции
Получение сессии из SessionStore:
@Resource(name = "webSessionManager")
private DefaultWebSessionManager webSessionManager;
private Mono getSession(final String sessionId) {
return webSessionManager.getSessionStore().retrieveSession(sessionId)
.map(webSession -> {
// ...
})
.switchIfEmpty(Mono.error(SessionExpiredException::new));
}
Тогда можно передавать sessionId как угодно, например в:
Инвалидация/экспайринг сессии
По умолчанию, сессия войдет в состояние expired спустя 30 минут после последнего её использования, например с помощью инъекции в параметр или retrieveSession().
Чтобы задать другой интервал — правим application.yaml:
server:
reactive:
session:
timeout: 1d
в примере 1d
= 1 день. Минуты — m
, секунды — s
.
Инвалидировать сессию можно методом webSession.invalidate () однако стоит помнить, что простой его вызов без подписки просто вернет Mono не выполнив собственно самой инвалидации сессии. Поэтому, подписываемся, например так:
@GetMapping("/session/invalidate")
public Mono invalidate(final WebSession webSession) {
return webSession.invalidate()
.then(Mono.just(webSession.getId() + " exp=" + webSession.isExpired() + " started=" + webSession.isStarted() + " "
+ webSession.getCreationTime() + " idle " + webSession.getMaxIdleTime()));
}