From a8c027ba8e6e789196751f683b2ccaf0b889f608 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Thu, 16 Nov 2017 13:04:06 +0100 Subject: [PATCH 1/2] Add Spring Session WebFlux sample See gh-11055 --- spring-boot-samples/pom.xml | 1 + .../README.adoc | 37 +++++++++ .../pom.xml | 76 ++++++++++++++++++ .../sample/session/HelloRestController.java | 31 ++++++++ .../SampleSessionWebFluxApplication.java | 57 ++++++++++++++ .../src/main/resources/application.properties | 0 .../SampleSessionWebFluxApplicationTests.java | 77 +++++++++++++++++++ 7 files changed, 279 insertions(+) create mode 100644 spring-boot-samples/spring-boot-sample-session-webflux/README.adoc create mode 100644 spring-boot-samples/spring-boot-sample-session-webflux/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/HelloRestController.java create mode 100644 spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/SampleSessionWebFluxApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-session-webflux/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index b106d43086..aabcb4cf45 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -67,6 +67,7 @@ spring-boot-sample-secure-webflux spring-boot-sample-servlet spring-boot-sample-session + spring-boot-sample-session-webflux spring-boot-sample-simple spring-boot-sample-test spring-boot-sample-test-nomockito diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/README.adoc b/spring-boot-samples/spring-boot-sample-session-webflux/README.adoc new file mode 100644 index 0000000000..a537f4af9b --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-webflux/README.adoc @@ -0,0 +1,37 @@ += Spring Boot Spring Session Sample + +This sample demonstrates the Spring Session WebFlux auto-configuration support. Spring +Session supports multiple reactive session store types, including: + +* `Redis` +* `MongoDB` + + + +== Using a different session store +Initially, the project uses MongoDB session store backed by an embedded MongoDB. You can +try out your favorite session store as explained below. + + + +=== Redis +Add `org.springframework.session:spring-session-data-redis` and +`spring-boot-starter-data-redis-reactive` dependencies to the project and make sure it is +configured properly (by default, a Redis instance with the default settings is expected +on your local box). + +TIP: Run sample application using Redis session store using +`$mvn spring-boot:run -Predis`. + + + +=== MongoDB +Add `org.springframework.session:spring-session-data-mongodb` and +`spring-boot-starter-data-mongodb-reactive` and +`de.flapdoodle.embed:de.flapdoodle.embed.mongo` dependencies to the project. An embedded +MongoDB is automatically configured. + +TIP: Run sample application using MongoDB session store using +`$mvn spring-boot:run -Pmongodb`. + +Note that this profile is active by default. diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/pom.xml b/spring-boot-samples/spring-boot-sample-session-webflux/pom.xml new file mode 100644 index 0000000000..2e381e027e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-webflux/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + ${revision} + + spring-boot-sample-session-webflux + Spring Boot Session WebFlux Sample + Spring Boot Session WebFlux Sample + + ${basedir}/../.. + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + redis + + + org.springframework.session + spring-session-data-redis + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + + + + + mongodb + + true + + + + org.springframework.session + spring-session-data-mongodb + + + org.springframework.boot + spring-boot-starter-data-mongodb-reactive + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + + + + + diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/HelloRestController.java b/spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/HelloRestController.java new file mode 100644 index 0000000000..0fc7698432 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/HelloRestController.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2017 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 + * + * http://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 sample.session; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.WebSession; + +@RestController +public class HelloRestController { + + @GetMapping("/") + String sessionId(WebSession session) { + return session.getId(); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/SampleSessionWebFluxApplication.java b/spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/SampleSessionWebFluxApplication.java new file mode 100644 index 0000000000..b69bb57e25 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-webflux/src/main/java/sample/session/SampleSessionWebFluxApplication.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2017 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 + * + * http://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 sample.session; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.ReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; + +@SpringBootApplication +public class SampleSessionWebFluxApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleSessionWebFluxApplication.class); + } + + @Bean + public ReactiveUserDetailsService userDetailsRepository() { + return new MapReactiveUserDetailsService(User.withDefaultPasswordEncoder() + .username("user").password("password").roles("USER").build()); + } + + @Bean + public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + // @formatter:off + return http + .authorizeExchange() + .anyExchange().authenticated() + .and() + .httpBasic().securityContextRepository(new WebSessionServerSecurityContextRepository()) + .and() + .formLogin() + .and() + .build(); + // @formatter:on + } + +} diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-session-webflux/src/main/resources/application.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java b/spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java new file mode 100644 index 0000000000..0570986523 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2017 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 + * + * http://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 sample.session; + +import java.util.Base64; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link SampleSessionWebFluxApplication}. + * + * @author Vedran Pavic + */ +@RunWith(SpringRunner.class) +@SpringBootTest(properties = "server.session.timeout:1", webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SampleSessionWebFluxApplicationTests { + + @LocalServerPort + private int port; + + @Autowired + private WebClient.Builder webClientBuilder; + + @Test + public void userDefinedMappingsSecureByDefault() throws Exception { + WebClient webClient = this.webClientBuilder + .baseUrl("http://localhost:" + this.port + "/").build(); + + ClientResponse response = webClient.get().header("Authorization", getBasicAuth()) + .exchange().block(); + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK); + ResponseCookie sessionCookie = response.cookies().getFirst("SESSION"); + String sessionId = response.bodyToMono(String.class).block(); + + response = webClient.get().cookie("SESSION", sessionCookie.getValue()).exchange() + .block(); + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.bodyToMono(String.class).block()).isEqualTo(sessionId); + + Thread.sleep(1000); + + response = webClient.get().cookie("SESSION", sessionCookie.getValue()).exchange() + .block(); + assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + private String getBasicAuth() { + return "Basic " + Base64.getEncoder().encodeToString("user:password".getBytes()); + } + +} From 4412285c45a31697735450c75ac1563b95662f83 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 17 Nov 2017 16:43:32 -0800 Subject: [PATCH 2/2] Polish Spring Session WebFlux sample Closes gh-11055 --- .../src/main/resources/application.properties | 0 .../session/SampleSessionWebFluxApplicationTests.java | 6 +----- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 spring-boot-samples/spring-boot-sample-session-webflux/src/main/resources/application.properties diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-session-webflux/src/main/resources/application.properties deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java b/spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java index 0570986523..5b6195f899 100644 --- a/spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-session-webflux/src/test/java/sample/session/SampleSessionWebFluxApplicationTests.java @@ -51,20 +51,16 @@ public class SampleSessionWebFluxApplicationTests { public void userDefinedMappingsSecureByDefault() throws Exception { WebClient webClient = this.webClientBuilder .baseUrl("http://localhost:" + this.port + "/").build(); - ClientResponse response = webClient.get().header("Authorization", getBasicAuth()) .exchange().block(); assertThat(response.statusCode()).isEqualTo(HttpStatus.OK); ResponseCookie sessionCookie = response.cookies().getFirst("SESSION"); String sessionId = response.bodyToMono(String.class).block(); - - response = webClient.get().cookie("SESSION", sessionCookie.getValue()).exchange() + response = webClient.get().cookie("SESSION", sessionCookie.getValue()).exchange() .block(); assertThat(response.statusCode()).isEqualTo(HttpStatus.OK); assertThat(response.bodyToMono(String.class).block()).isEqualTo(sessionId); - Thread.sleep(1000); - response = webClient.get().cookie("SESSION", sessionCookie.getValue()).exchange() .block(); assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);