diff --git a/spring-session-docs/src/docs/asciidoc/index.adoc b/spring-session-docs/src/docs/asciidoc/index.adoc index fb30174b..4098518d 100644 --- a/spring-session-docs/src/docs/asciidoc/index.adoc +++ b/spring-session-docs/src/docs/asciidoc/index.adoc @@ -58,6 +58,10 @@ To get started with Spring Session, the best place to start is our Sample Applic | Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store. | link:guides/boot-jdbc.html[HttpSession with JDBC Guide] +| {gh-samples-url}spring-session-sample-boot-hazelcast[HttpSession with Hazelcast] +| Demonstrates how to use Spring Session to replace the `HttpSession` with Hazelcast. +| + | {gh-samples-url}spring-session-sample-boot-findbyusername[Find by Username] | Demonstrates how to use Spring Session to find sessions by username. | link:guides/boot-findbyusername.html[Find by Username Guide] diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/spring-session-sample-boot-hazelcast.gradle b/spring-session-samples/spring-session-sample-boot-hazelcast/spring-session-sample-boot-hazelcast.gradle new file mode 100644 index 00000000..b8d2b97d --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/spring-session-sample-boot-hazelcast.gradle @@ -0,0 +1,19 @@ +apply plugin: 'io.spring.convention.spring-sample-boot' + +dependencies { + compile project(':spring-session-hazelcast') + compile "org.springframework.boot:spring-boot-starter-web" + compile "org.springframework.boot:spring-boot-starter-thymeleaf" + compile "org.springframework.boot:spring-boot-starter-security" + compile "com.hazelcast:hazelcast-client" + compile "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect" + compile "org.webjars:bootstrap" + compile "org.webjars:html5shiv" + compile "org.webjars:webjars-locator-core" + + testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.junit.jupiter:junit-jupiter-api" + testRuntime "org.junit.jupiter:junit-jupiter-engine" + integrationTestCompile seleniumDependencies + integrationTestCompile "org.testcontainers:testcontainers" +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/BootTests.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/BootTests.java new file mode 100644 index 00000000..2d5b8abd --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/BootTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2014-2020 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 sample; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openqa.selenium.WebDriver; +import org.testcontainers.containers.GenericContainer; +import sample.pages.HomePage; +import sample.pages.LoginPage; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.htmlunit.webdriver.MockMvcHtmlUnitDriverBuilder; + +/** + * @author Ellie Bahadori + */ +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +@SpringBootTest(webEnvironment = WebEnvironment.MOCK) +class BootTests { + + private static final String DOCKER_IMAGE = "hazelcast/hazelcast:latest"; + + @Autowired + private MockMvc mockMvc; + + private WebDriver driver; + + @BeforeEach + void setup() { + this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build(); + } + + @AfterEach + void tearDown() { + this.driver.quit(); + } + + @Test + void home() { + LoginPage login = HomePage.go(this.driver); + login.assertAt(); + } + + @Test + void login() { + LoginPage login = HomePage.go(this.driver); + HomePage home = login.form().login(HomePage.class); + home.assertAt(); + home.containCookie("SESSION"); + home.doesNotContainCookie("JSESSIONID"); + } + + @Test + void logout() { + LoginPage login = HomePage.go(this.driver); + HomePage home = login.form().login(HomePage.class); + home.logout(); + login.assertAt(); + } + + @TestConfiguration + static class Config { + + @Bean + GenericContainer hazelcastContainer() { + GenericContainer hazelcastContainer = new GenericContainer(DOCKER_IMAGE).withExposedPorts(5701); + hazelcastContainer.start(); + return hazelcastContainer; + } + + } + +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/BasePage.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/BasePage.java new file mode 100644 index 00000000..057cea48 --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/BasePage.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014-2020 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 sample.pages; + +import org.openqa.selenium.WebDriver; + +/** + * @author Ellie Bahadori + */ +public class BasePage { + + private WebDriver driver; + + public BasePage(WebDriver driver) { + this.driver = driver; + } + + public WebDriver getDriver() { + return this.driver; + } + + public static void get(WebDriver driver, String get) { + String baseUrl = "http://localhost"; + driver.get(baseUrl + get); + } + +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/HomePage.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/HomePage.java new file mode 100644 index 00000000..5efdc18a --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/HomePage.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014-2020 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 sample.pages; + +import java.util.Set; + +import org.openqa.selenium.By; +import org.openqa.selenium.Cookie; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.PageFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ellie Bahadori + */ +public class HomePage extends BasePage { + + public HomePage(WebDriver driver) { + super(driver); + } + + public static LoginPage go(WebDriver driver) { + get(driver, "/"); + return PageFactory.initElements(driver, LoginPage.class); + } + + public void assertAt() { + assertThat(getDriver().getTitle()).isEqualTo("Spring Session Sample - Secured Content"); + } + + public void containCookie(String cookieName) { + Set cookies = getDriver().manage().getCookies(); + assertThat(cookies).extracting("name").contains(cookieName); + } + + public void doesNotContainCookie(String cookieName) { + Set cookies = getDriver().manage().getCookies(); + assertThat(cookies).extracting("name").doesNotContain(cookieName); + } + + public HomePage logout() { + WebElement logout = getDriver().findElement(By.cssSelector("input[type=\"submit\"]")); + logout.click(); + return PageFactory.initElements(getDriver(), HomePage.class); + } + +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/LoginPage.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/LoginPage.java new file mode 100644 index 00000000..56d83cbc --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/java/sample/pages/LoginPage.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014-2020 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 sample.pages; + +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.PageFactory; +import org.openqa.selenium.support.pagefactory.DefaultElementLocatorFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ellie Bahadori + */ +public class LoginPage extends BasePage { + + public LoginPage(WebDriver driver) { + super(driver); + } + + public void assertAt() { + assertThat(getDriver().getTitle()).isEqualTo("Please sign in"); + } + + public Form form() { + return new Form(getDriver()); + } + + public class Form { + + @FindBy(name = "username") + private WebElement username; + + @FindBy(name = "password") + private WebElement password; + + @FindBy(tagName = "button") + private WebElement button; + + public Form(SearchContext context) { + PageFactory.initElements(new DefaultElementLocatorFactory(context), this); + } + + public T login(Class page) { + this.username.sendKeys("user"); + this.password.sendKeys("password"); + this.button.click(); + return PageFactory.initElements(getDriver(), page); + } + + } + +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/resources/testcontainers.properties b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/resources/testcontainers.properties new file mode 100644 index 00000000..e3e83419 --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/integration-test/resources/testcontainers.properties @@ -0,0 +1 @@ +ryuk.container.timeout=120 diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/Application.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/Application.java new file mode 100644 index 00000000..895c0f7f --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/Application.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014-2020 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 sample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; + +/** + * @author Ellie Bahadori + */ +@EnableCaching +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/SecurityConfig.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/SecurityConfig.java new file mode 100644 index 00000000..8750a10c --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/SecurityConfig.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014-2020 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 sample.config; + +import org.springframework.boot.autoconfigure.security.servlet.PathRequest; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * Spring Security configuration. + * + * @author Ellie Bahadori + */ +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + // @formatter:off + // tag::config[] + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests((authorize) -> authorize + .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() + .anyRequest().authenticated() + ) + .formLogin((formLogin) -> formLogin + .permitAll() + ); + } + // end::config[] + // @formatter:on + +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/SessionConfig.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/SessionConfig.java new file mode 100644 index 00000000..d926b775 --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/SessionConfig.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014-2020 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 sample.config; + +import com.hazelcast.config.Config; +import com.hazelcast.config.MapAttributeConfig; +import com.hazelcast.config.MapIndexConfig; +import com.hazelcast.config.NetworkConfig; +import com.hazelcast.config.SerializerConfig; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.session.MapSession; +import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; +import org.springframework.session.hazelcast.HazelcastSessionSerializer; +import org.springframework.session.hazelcast.PrincipalNameExtractor; + +// tag::class[] +@Configuration +public class SessionConfig { + + @Bean + public Config clientConfig() { + Config config = new Config(); + NetworkConfig networkConfig = config.getNetworkConfig(); + networkConfig.setPort(0); + networkConfig.getJoin().getMulticastConfig().setEnabled(false); + MapAttributeConfig attributeConfig = new MapAttributeConfig() + .setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE) + .setExtractor(PrincipalNameExtractor.class.getName()); + config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) + .addMapAttributeConfig(attributeConfig).addMapIndexConfig( + new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false)); + SerializerConfig serializerConfig = new SerializerConfig(); + serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class); + config.getSerializationConfig().addSerializerConfig(serializerConfig); + return config; + + } + +} +// end::class[] diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/WebMvcConfig.java b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/WebMvcConfig.java new file mode 100644 index 00000000..220ff600 --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/java/sample/config/WebMvcConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014-2020 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 sample.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("index"); + } + +} diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/application.properties b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/application.properties new file mode 100644 index 00000000..1b5271b5 --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.security.user.password=password diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/static/favicon.ico b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/static/favicon.ico new file mode 100644 index 00000000..bfb99740 Binary files /dev/null and b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/static/favicon.ico differ diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/static/images/logo.png b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/static/images/logo.png new file mode 100644 index 00000000..39323088 Binary files /dev/null and b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/static/images/logo.png differ diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/templates/index.html b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/templates/index.html new file mode 100644 index 00000000..af1da6b9 --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/templates/index.html @@ -0,0 +1,11 @@ + + + Secured Content + + +
+

Secured Page

+

This page is secured using Spring Boot, Spring Session, and Spring Security.

+
+ + diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/templates/layout.html b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/templates/layout.html new file mode 100644 index 00000000..548267f8 --- /dev/null +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/src/main/resources/templates/layout.html @@ -0,0 +1,122 @@ + + + + Spring Session Sample + + + + + + + + + + + +
+ + +
+
+ Some Success message +
+
+ Fake content +
+
+ +
+
+ + + +