Provide an Actuator endpoint for non-indexed session repositories
At present, Actuator sessions endpoint is supported only on a Servlet stack and also requires an indexed session repository. With Spring Session moving to non-indexed session repositories as a default for some session stores, this means that sessions endpoint won't be available unless users opt into a (non-default) indexed session repository. This commit updates SessionEndpoint so that it is able to work with a non-indexed session repository. In such setup, it exposes operations for fetching session by id and deleting the session. Additionally, this also adds support for reactive stack by introducing ReactiveSessionEndpoint and its auto-configuration support. See gh-32046
This commit is contained in:
committed by
Moritz Halbritter
parent
a09cc22841
commit
6a9eb7754f
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.session;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReactiveSessionsEndpoint}.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
class ReactiveSessionsEndpointTests {
|
||||
|
||||
private static final Session session = new MapSession();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final ReactiveSessionRepository<Session> sessionRepository = mock(ReactiveSessionRepository.class);
|
||||
|
||||
private final ReactiveSessionsEndpoint endpoint = new ReactiveSessionsEndpoint(this.sessionRepository);
|
||||
|
||||
@Test
|
||||
void getSession() {
|
||||
given(this.sessionRepository.findById(session.getId())).willReturn(Mono.just(session));
|
||||
StepVerifier.create(this.endpoint.getSession(session.getId())).consumeNextWith((result) -> {
|
||||
assertThat(result.getId()).isEqualTo(session.getId());
|
||||
assertThat(result.getAttributeNames()).isEqualTo(session.getAttributeNames());
|
||||
assertThat(result.getCreationTime()).isEqualTo(session.getCreationTime());
|
||||
assertThat(result.getLastAccessedTime()).isEqualTo(session.getLastAccessedTime());
|
||||
assertThat(result.getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds());
|
||||
assertThat(result.isExpired()).isEqualTo(session.isExpired());
|
||||
}).verifyComplete();
|
||||
then(this.sessionRepository).should().findById(session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionWithIdNotFound() {
|
||||
given(this.sessionRepository.findById("not-found")).willReturn(Mono.empty());
|
||||
StepVerifier.create(this.endpoint.getSession("not-found")).verifyComplete();
|
||||
then(this.sessionRepository).should().findById("not-found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteSession() {
|
||||
given(this.sessionRepository.deleteById(session.getId())).willReturn(Mono.empty());
|
||||
StepVerifier.create(this.endpoint.deleteSession(session.getId())).verifyComplete();
|
||||
then(this.sessionRepository).should().deleteById(session.getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.session;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infrastructure;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ReactiveSessionsEndpoint} exposed by WebFlux.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
*/
|
||||
class ReactiveSessionsEndpointWebIntegrationTests {
|
||||
|
||||
private static final Session session = new MapSession();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final ReactiveSessionRepository<Session> sessionRepository = mock(ReactiveSessionRepository.class);
|
||||
|
||||
@WebEndpointTest(infrastructure = Infrastructure.WEBFLUX)
|
||||
void sessionForIdFound(WebTestClient client) {
|
||||
given(sessionRepository.findById(session.getId())).willReturn(Mono.just(session));
|
||||
client.get()
|
||||
.uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId()))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.jsonPath("id")
|
||||
.isEqualTo(session.getId());
|
||||
}
|
||||
|
||||
@WebEndpointTest(infrastructure = Infrastructure.WEBFLUX)
|
||||
void sessionForIdNotFound(WebTestClient client) {
|
||||
given(sessionRepository.findById("not-found")).willReturn(Mono.empty());
|
||||
client.get()
|
||||
.uri((builder) -> builder.path("/actuator/sessions/not-found").build())
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNotFound();
|
||||
}
|
||||
|
||||
@WebEndpointTest(infrastructure = Infrastructure.WEBFLUX)
|
||||
void deleteSession(WebTestClient client) {
|
||||
given(sessionRepository.deleteById(session.getId())).willReturn(Mono.empty());
|
||||
client.delete()
|
||||
.uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId()))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNoContent();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
ReactiveSessionsEndpoint sessionsEndpoint() {
|
||||
return new ReactiveSessionsEndpoint(sessionRepository);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,10 +21,10 @@ import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.session.SessionsEndpoint.SessionDescriptor;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
@@ -41,15 +41,20 @@ class SessionsEndpointTests {
|
||||
private static final Session session = new MapSession();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final FindByIndexNameSessionRepository<Session> repository = mock(FindByIndexNameSessionRepository.class);
|
||||
private final SessionRepository<Session> sessionRepository = mock(SessionRepository.class);
|
||||
|
||||
private final SessionsEndpoint endpoint = new SessionsEndpoint(this.repository);
|
||||
@SuppressWarnings("unchecked")
|
||||
private final FindByIndexNameSessionRepository<Session> indexedSessionRepository = mock(
|
||||
FindByIndexNameSessionRepository.class);
|
||||
|
||||
private final SessionsEndpoint endpoint = new SessionsEndpoint(this.sessionRepository,
|
||||
this.indexedSessionRepository);
|
||||
|
||||
@Test
|
||||
void sessionsForUsername() {
|
||||
given(this.repository.findByPrincipalName("user"))
|
||||
given(this.indexedSessionRepository.findByPrincipalName("user"))
|
||||
.willReturn(Collections.singletonMap(session.getId(), session));
|
||||
List<SessionDescriptor> result = this.endpoint.sessionsForUsername("user").getSessions();
|
||||
List<SessionsEndpoint.SessionDescriptor> result = this.endpoint.sessionsForUsername("user").getSessions();
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0).getId()).isEqualTo(session.getId());
|
||||
assertThat(result.get(0).getAttributeNames()).isEqualTo(session.getAttributeNames());
|
||||
@@ -57,30 +62,39 @@ class SessionsEndpointTests {
|
||||
assertThat(result.get(0).getLastAccessedTime()).isEqualTo(session.getLastAccessedTime());
|
||||
assertThat(result.get(0).getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds());
|
||||
assertThat(result.get(0).isExpired()).isEqualTo(session.isExpired());
|
||||
then(this.indexedSessionRepository).should().findByPrincipalName("user");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionsForUsernameWhenNoIndexedRepository() {
|
||||
SessionsEndpoint endpoint = new SessionsEndpoint(this.sessionRepository, null);
|
||||
assertThat(endpoint.sessionsForUsername("user")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSession() {
|
||||
given(this.repository.findById(session.getId())).willReturn(session);
|
||||
SessionDescriptor result = this.endpoint.getSession(session.getId());
|
||||
given(this.sessionRepository.findById(session.getId())).willReturn(session);
|
||||
SessionsEndpoint.SessionDescriptor result = this.endpoint.getSession(session.getId());
|
||||
assertThat(result.getId()).isEqualTo(session.getId());
|
||||
assertThat(result.getAttributeNames()).isEqualTo(session.getAttributeNames());
|
||||
assertThat(result.getCreationTime()).isEqualTo(session.getCreationTime());
|
||||
assertThat(result.getLastAccessedTime()).isEqualTo(session.getLastAccessedTime());
|
||||
assertThat(result.getMaxInactiveInterval()).isEqualTo(session.getMaxInactiveInterval().getSeconds());
|
||||
assertThat(result.isExpired()).isEqualTo(session.isExpired());
|
||||
then(this.sessionRepository).should().findById(session.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSessionWithIdNotFound() {
|
||||
given(this.repository.findById("not-found")).willReturn(null);
|
||||
given(this.sessionRepository.findById("not-found")).willReturn(null);
|
||||
assertThat(this.endpoint.getSession("not-found")).isNull();
|
||||
then(this.sessionRepository).should().findById("not-found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteSession() {
|
||||
this.endpoint.deleteSession(session.getId());
|
||||
then(this.repository).should().deleteById(session.getId());
|
||||
then(this.sessionRepository).should().deleteById(session.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.util.Collections;
|
||||
import net.minidev.json.JSONArray;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest.Infrastructure;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
@@ -45,7 +46,7 @@ class SessionsEndpointWebIntegrationTests {
|
||||
private static final FindByIndexNameSessionRepository<Session> repository = mock(
|
||||
FindByIndexNameSessionRepository.class);
|
||||
|
||||
@WebEndpointTest
|
||||
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
|
||||
void sessionsForUsernameWithoutUsernameParam(WebTestClient client) {
|
||||
client.get()
|
||||
.uri((builder) -> builder.path("/actuator/sessions").build())
|
||||
@@ -54,7 +55,7 @@ class SessionsEndpointWebIntegrationTests {
|
||||
.isBadRequest();
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
|
||||
void sessionsForUsernameNoResults(WebTestClient client) {
|
||||
given(repository.findByPrincipalName("user")).willReturn(Collections.emptyMap());
|
||||
client.get()
|
||||
@@ -67,7 +68,7 @@ class SessionsEndpointWebIntegrationTests {
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
|
||||
void sessionsForUsernameFound(WebTestClient client) {
|
||||
given(repository.findByPrincipalName("user")).willReturn(Collections.singletonMap(session.getId(), session));
|
||||
client.get()
|
||||
@@ -80,7 +81,7 @@ class SessionsEndpointWebIntegrationTests {
|
||||
.isEqualTo(new JSONArray().appendElement(session.getId()));
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
|
||||
void sessionForIdNotFound(WebTestClient client) {
|
||||
client.get()
|
||||
.uri((builder) -> builder.path("/actuator/sessions/session-id-not-found").build())
|
||||
@@ -89,12 +90,21 @@ class SessionsEndpointWebIntegrationTests {
|
||||
.isNotFound();
|
||||
}
|
||||
|
||||
@WebEndpointTest(infrastructure = { Infrastructure.JERSEY, Infrastructure.MVC })
|
||||
void deleteSession(WebTestClient client) {
|
||||
client.delete()
|
||||
.uri((builder) -> builder.path("/actuator/sessions/{id}").build(session.getId()))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isNoContent();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
SessionsEndpoint sessionsEndpoint() {
|
||||
return new SessionsEndpoint(repository);
|
||||
return new SessionsEndpoint(repository, repository);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user