Java Spring Reactive WebSession на примере

af8cbbadb60ac01b3b5881c6aac286b6

Рассмотрим простой пример создания сессии, её использования и инвалидации в реактивном стеке 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()));
    }

© Habrahabr.ru