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) {