From f6a95333d1756e1ad241118f0f1c370c36643302 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 28 Apr 2016 13:25:52 -0400 Subject: [PATCH] Add Hello World Spring Boot sample Issue gh-3850 --- build.gradle | 4 +- gradle/boot-sample.gradle | 5 + gradle/javaprojects.gradle | 1 + samples/boot/helloworld/build.gradle | 16 ++ samples/boot/helloworld/pom.xml | 144 ++++++++++++++++++ .../samples/HelloWorldApplicationTests.java | 120 +++++++++++++++ .../samples/HelloWorldApplication.java | 32 ++++ .../samples/config/SecurityConfig.java | 50 ++++++ .../security/samples/web/MainController.java | 54 +++++++ .../src/main/resources/application.yml | 12 ++ .../src/main/resources/static/css/main.css | 13 ++ .../src/main/resources/templates/index.html | 25 +++ .../src/main/resources/templates/login.html | 21 +++ .../main/resources/templates/user/index.html | 13 ++ settings.gradle | 1 + 15 files changed, 509 insertions(+), 2 deletions(-) create mode 100644 gradle/boot-sample.gradle create mode 100644 samples/boot/helloworld/build.gradle create mode 100644 samples/boot/helloworld/pom.xml create mode 100644 samples/boot/helloworld/src/integration-test/java/org/springframework/security/samples/HelloWorldApplicationTests.java create mode 100644 samples/boot/helloworld/src/main/java/org/springframework/security/samples/HelloWorldApplication.java create mode 100644 samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java create mode 100644 samples/boot/helloworld/src/main/java/org/springframework/security/samples/web/MainController.java create mode 100644 samples/boot/helloworld/src/main/resources/application.yml create mode 100644 samples/boot/helloworld/src/main/resources/static/css/main.css create mode 100644 samples/boot/helloworld/src/main/resources/templates/index.html create mode 100644 samples/boot/helloworld/src/main/resources/templates/login.html create mode 100644 samples/boot/helloworld/src/main/resources/templates/user/index.html diff --git a/build.gradle b/build.gradle index f9a502fe2f..214a55baf0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -import groovy.text.SimpleTemplateEngine - buildscript { repositories { maven { url "https://repo.spring.io/plugins-release" } @@ -11,6 +9,7 @@ buildscript { classpath('me.champeau.gradle:gradle-javadoc-hotfix-plugin:0.1') classpath('org.asciidoctor:asciidoctor-gradle-plugin:1.5.1') classpath("io.spring.gradle:docbook-reference-plugin:0.3.1") + classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE") } } @@ -93,6 +92,7 @@ configure(subprojects - coreModuleProjects - project(':spring-security-samples-j configure(javaProjects) { ext.TOMCAT_GRADLE = "$rootDir/gradle/tomcat.gradle" ext.WAR_SAMPLE_GRADLE = "$rootDir/gradle/war-sample.gradle" + ext.BOOT_SAMPLE_GRADLE = "$rootDir/gradle/boot-sample.gradle" apply from: "$rootDir/gradle/javaprojects.gradle" if(!project.name.contains('gae')) { apply from: "$rootDir/gradle/checkstyle.gradle" diff --git a/gradle/boot-sample.gradle b/gradle/boot-sample.gradle new file mode 100644 index 0000000000..b87b8d99a0 --- /dev/null +++ b/gradle/boot-sample.gradle @@ -0,0 +1,5 @@ +apply plugin: 'spring-boot' + +sonarqube { + skipProject = true +} \ No newline at end of file diff --git a/gradle/javaprojects.gradle b/gradle/javaprojects.gradle index 435583f63e..847ce71b8e 100644 --- a/gradle/javaprojects.gradle +++ b/gradle/javaprojects.gradle @@ -33,6 +33,7 @@ ext.springDataCommonsVersion = '1.9.1.RELEASE' ext.springDataJpaVersion = '1.7.1.RELEASE' ext.springDataRedisVersion = '1.4.1.RELEASE' ext.springSessionVersion = '1.0.0.RELEASE' +ext.springBootVersion = '1.3.3.RELEASE' ext.thymeleafVersion = '2.1.4.RELEASE' ext.spockDependencies = [ diff --git a/samples/boot/helloworld/build.gradle b/samples/boot/helloworld/build.gradle new file mode 100644 index 0000000000..455931c112 --- /dev/null +++ b/samples/boot/helloworld/build.gradle @@ -0,0 +1,16 @@ +apply from: BOOT_SAMPLE_GRADLE + +springBoot { + mainClass = 'org.springframework.security.samples.HelloWorldApplication' +} + +dependencies { + compile "org.springframework.boot:spring-boot-starter-web", + "org.springframework.boot:spring-boot-starter-thymeleaf", + "org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.2.RELEASE", + project(":spring-security-config"), + project(":spring-security-web") + + testCompile "org.springframework.boot:spring-boot-starter-test", + project(":spring-security-test") +} \ No newline at end of file diff --git a/samples/boot/helloworld/pom.xml b/samples/boot/helloworld/pom.xml new file mode 100644 index 0000000000..83c3622839 --- /dev/null +++ b/samples/boot/helloworld/pom.xml @@ -0,0 +1,144 @@ + + + 4.0.0 + org.springframework.security + spring-security-samples-boot-helloworld + 4.1.0.BUILD-SNAPSHOT + spring-security-samples-boot-helloworld + spring-security-samples-boot-helloworld + http://spring.io/spring-security + + spring.io + http://spring.io/ + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + rwinch + Rob Winch + rwinch@gopivotal.com + + + + scm:git:git://github.com/spring-projects/spring-security + scm:git:git://github.com/spring-projects/spring-security + https://github.com/spring-projects/spring-security + + + + + org.springframework + spring-framework-bom + 4.2.5.RELEASE + pom + import + + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + compile + + + org.springframework.boot + spring-boot-starter-web + compile + + + org.springframework.security + spring-security-config + 4.1.0.BUILD-SNAPSHOT + compile + + + org.springframework.security + spring-security-web + 4.1.0.BUILD-SNAPSHOT + compile + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity4 + 2.1.2.RELEASE + compile + + + commons-logging + commons-logging + 1.2 + compile + true + + + ch.qos.logback + logback-classic + 1.1.2 + test + + + junit + junit + 4.12 + test + + + org.assertj + assertj-core + 2.2.0 + test + + + org.mockito + mockito-core + 1.10.19 + test + + + org.slf4j + jcl-over-slf4j + 1.7.7 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + 4.1.0.BUILD-SNAPSHOT + test + + + org.springframework + spring-test + test + + + + + spring-snapshot + https://repo.spring.io/snapshot + + + + + + maven-compiler-plugin + + 1.7 + 1.7 + + + + + diff --git a/samples/boot/helloworld/src/integration-test/java/org/springframework/security/samples/HelloWorldApplicationTests.java b/samples/boot/helloworld/src/integration-test/java/org/springframework/security/samples/HelloWorldApplicationTests.java new file mode 100644 index 0000000000..2ac4d24290 --- /dev/null +++ b/samples/boot/helloworld/src/integration-test/java/org/springframework/security/samples/HelloWorldApplicationTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2016 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 org.springframework.security.samples; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author Joe Grandja + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(HelloWorldApplication.class) +@WebAppConfiguration +public class HelloWorldApplicationTests { + + @Autowired + private WebApplicationContext context; + + private MockMvc mockMvc; + + @Before + public void setup() { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + public void accessUnprotected() throws Exception { + this.mockMvc.perform(get("/index")).andExpect(status().isOk()); + } + + @Test + public void accessProtectedRedirectsToLogin() throws Exception { + MvcResult mvcResult = this.mockMvc.perform(get("/user/index")) + .andExpect(status().is3xxRedirection()) + .andReturn(); + + assertThat(mvcResult.getResponse().getRedirectedUrl()).endsWith("/login"); + } + + @Test + public void loginUser() throws Exception { + this.mockMvc.perform(formLogin().user("user1").password("password1")) + .andExpect(authenticated()); + } + + @Test + public void loginInvalidUser() throws Exception { + this.mockMvc.perform(formLogin().user("invalid").password("invalid")) + .andExpect(unauthenticated()) + .andExpect(status().is3xxRedirection()); + } + + @Test + public void loginUserAccessProtected() throws Exception { + MvcResult mvcResult = this.mockMvc.perform(formLogin().user("user1").password("password1")) + .andExpect(authenticated()) + .andReturn(); + + MockHttpSession httpSession = MockHttpSession.class.cast(mvcResult.getRequest().getSession(false)); + + this.mockMvc.perform(get("/user/index") + .session(httpSession)) + .andExpect(status().isOk()); + } + + @Test + public void loginUserValidateLogout() throws Exception { + MvcResult mvcResult = this.mockMvc.perform(formLogin().user("user1").password("password1")) + .andExpect(authenticated()) + .andReturn(); + + MockHttpSession httpSession = MockHttpSession.class.cast(mvcResult.getRequest().getSession(false)); + + this.mockMvc.perform(post("/logout").with(csrf()) + .session(httpSession)) + .andExpect(unauthenticated()); + + this.mockMvc.perform(get("/user/index") + .session(httpSession)) + .andExpect(unauthenticated()) + .andExpect(status().is3xxRedirection()); + } +} \ No newline at end of file diff --git a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/HelloWorldApplication.java b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/HelloWorldApplication.java new file mode 100644 index 0000000000..5f3c2ef472 --- /dev/null +++ b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/HelloWorldApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2016 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 org.springframework.security.samples; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Joe Grandja + */ +@SpringBootApplication +public class HelloWorldApplication { + + public static void main(String[] args) { + SpringApplication.run(HelloWorldApplication.class, args); + } + + +} \ No newline at end of file diff --git a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java new file mode 100644 index 0000000000..2e6f2308e6 --- /dev/null +++ b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2016 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 org.springframework.security.samples.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * @author Joe Grandja + */ +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + // @formatter:off + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/css/**", "/index").permitAll() + .antMatchers("/user/**").hasRole("USER") + .and() + .formLogin().loginPage("/login").failureUrl("/login-error"); + } + // @formatter:on + + // @formatter:off + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth + .inMemoryAuthentication() + .withUser("user1").password("password1").roles("USER"); + } + // @formatter:on +} diff --git a/samples/boot/helloworld/src/main/java/org/springframework/security/samples/web/MainController.java b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/web/MainController.java new file mode 100644 index 0000000000..9e77da5f74 --- /dev/null +++ b/samples/boot/helloworld/src/main/java/org/springframework/security/samples/web/MainController.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2016 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 org.springframework.security.samples.web; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author Joe Grandja + */ +@Controller +public class MainController { + + @RequestMapping("/") + public String root() { + return "redirect:/index"; + } + + @RequestMapping("/index") + public String index() { + return "index"; + } + + @RequestMapping("/user/index") + public String userIndex() { + return "user/index"; + } + + @RequestMapping("/login") + public String login() { + return "login"; + } + + @RequestMapping("/login-error") + public String loginError(Model model) { + model.addAttribute("loginError", true); + return "login"; + } + +} diff --git a/samples/boot/helloworld/src/main/resources/application.yml b/samples/boot/helloworld/src/main/resources/application.yml new file mode 100644 index 0000000000..20dbf8b9d8 --- /dev/null +++ b/samples/boot/helloworld/src/main/resources/application.yml @@ -0,0 +1,12 @@ +server: + port: 8080 + +logging: + level: + root: WARN + org.springframework.web: INFO + org.springframework.security: INFO + +spring: + thymeleaf: + cache: true diff --git a/samples/boot/helloworld/src/main/resources/static/css/main.css b/samples/boot/helloworld/src/main/resources/static/css/main.css new file mode 100644 index 0000000000..5e6687a387 --- /dev/null +++ b/samples/boot/helloworld/src/main/resources/static/css/main.css @@ -0,0 +1,13 @@ +body { + font-family: sans; + font-size: 1em; +} + +p.error { + font-weight: bold; + color: red; +} + +div.logout { + float: right; +} \ No newline at end of file diff --git a/samples/boot/helloworld/src/main/resources/templates/index.html b/samples/boot/helloworld/src/main/resources/templates/index.html new file mode 100644 index 0000000000..64dedbef96 --- /dev/null +++ b/samples/boot/helloworld/src/main/resources/templates/index.html @@ -0,0 +1,25 @@ + + + + Hello Spring Security + + + + +
+ Logged in user: | + Roles: +
+
+ + +
+
+
+

Hello Spring Security

+

This is an unsecured page, but you can access the secured pages after authenticating.

+ + + \ No newline at end of file diff --git a/samples/boot/helloworld/src/main/resources/templates/login.html b/samples/boot/helloworld/src/main/resources/templates/login.html new file mode 100644 index 0000000000..e359b3b2dd --- /dev/null +++ b/samples/boot/helloworld/src/main/resources/templates/login.html @@ -0,0 +1,21 @@ + + + + Login page + + + + +

Login page

+

Example user: user1 / password1

+

Wrong user or password

+
+ : +
+ : +
+ +
+

Back to home page

+ + diff --git a/samples/boot/helloworld/src/main/resources/templates/user/index.html b/samples/boot/helloworld/src/main/resources/templates/user/index.html new file mode 100644 index 0000000000..f0329e7bd6 --- /dev/null +++ b/samples/boot/helloworld/src/main/resources/templates/user/index.html @@ -0,0 +1,13 @@ + + + + Hello Spring Security + + + + +
+

This is a secured page!

+

Back to home page

+ + diff --git a/settings.gradle b/settings.gradle index a0b3dc32c2..41586c8da0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -55,6 +55,7 @@ findProject(':bom').name = 'spring-security-bom' includeSamples("samples" + File.separator + "xml") includeSamples("samples" + File.separator + "javaconfig") +includeSamples("samples" + File.separator + "boot") void includeSamples(String samplesDir) { FileTree tree = fileTree(samplesDir) {