Bläddra i källkod

Add Hello World Spring Boot sample

Issue gh-3850
Joe Grandja 9 år sedan
förälder
incheckning
f6a95333d1

+ 2 - 2
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"

+ 5 - 0
gradle/boot-sample.gradle

@@ -0,0 +1,5 @@
+apply plugin: 'spring-boot'
+
+sonarqube {
+	skipProject = true
+}

+ 1 - 0
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 = [

+ 16 - 0
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")
+}

+ 144 - 0
samples/boot/helloworld/pom.xml

@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.springframework.security</groupId>
+  <artifactId>spring-security-samples-boot-helloworld</artifactId>
+  <version>4.1.0.BUILD-SNAPSHOT</version>
+  <name>spring-security-samples-boot-helloworld</name>
+  <description>spring-security-samples-boot-helloworld</description>
+  <url>http://spring.io/spring-security</url>
+  <organization>
+    <name>spring.io</name>
+    <url>http://spring.io/</url>
+  </organization>
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <developers>
+    <developer>
+      <id>rwinch</id>
+      <name>Rob Winch</name>
+      <email>rwinch@gopivotal.com</email>
+    </developer>
+  </developers>
+  <scm>
+    <connection>scm:git:git://github.com/spring-projects/spring-security</connection>
+    <developerConnection>scm:git:git://github.com/spring-projects/spring-security</developerConnection>
+    <url>https://github.com/spring-projects/spring-security</url>
+  </scm>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.springframework</groupId>
+        <artifactId>spring-framework-bom</artifactId>
+        <version>4.2.5.RELEASE</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-thymeleaf</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-config</artifactId>
+      <version>4.1.0.BUILD-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-web</artifactId>
+      <version>4.1.0.BUILD-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.thymeleaf.extras</groupId>
+      <artifactId>thymeleaf-extras-springsecurity4</artifactId>
+      <version>2.1.2.RELEASE</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+      <version>1.2</version>
+      <scope>compile</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <version>1.1.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <version>2.2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.10.19</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>jcl-over-slf4j</artifactId>
+      <version>1.7.7</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-test</artifactId>
+      <version>4.1.0.BUILD-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <repositories>
+    <repository>
+      <id>spring-snapshot</id>
+      <url>https://repo.spring.io/snapshot</url>
+    </repository>
+  </repositories>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.7</source>
+          <target>1.7</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>

+ 120 - 0
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());
+	}
+}

+ 32 - 0
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);
+	}
+
+
+}

+ 50 - 0
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
+}

+ 54 - 0
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";
+	}
+
+}

+ 12 - 0
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

+ 13 - 0
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;
+}

+ 25 - 0
samples/boot/helloworld/src/main/resources/templates/index.html

@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
+    <head>
+        <title>Hello Spring Security</title>
+        <meta charset="utf-8" />
+        <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
+    </head>
+    <body>
+        <div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()">
+            Logged in user: <span sec:authentication="name"></span> |
+            Roles: <span sec:authentication="principal.authorities"></span>
+            <div>
+                <form action="/logout" method="post">
+                    <input type="submit" value="Logout" />
+                    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
+                </form>
+            </div>
+        </div>
+        <h1>Hello Spring Security</h1>
+        <p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
+        <ul>
+            <li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
+        </ul>
+    </body>
+</html>

+ 21 - 0
samples/boot/helloworld/src/main/resources/templates/login.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
+    <head>
+        <title>Login page</title>
+        <meta charset="utf-8" />
+        <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
+	</head>
+    <body>
+        <h1>Login page</h1>
+        <p>Example user: user1 / password1</p>
+        <p th:if="${loginError}" class="error">Wrong user or password</p>
+        <form th:action="@{/login}" method="post">
+            <label for="username">Username</label>:
+            <input type="text" id="username" name="username" autofocus="autofocus" /> <br />
+            <label for="password">Password</label>:
+            <input type="password" id="password" name="password" /> <br />
+            <input type="submit" value="Log in" />
+        </form>
+        <p><a href="/index" th:href="@{/index}">Back to home page</a></p>
+    </body>
+</html>

+ 13 - 0
samples/boot/helloworld/src/main/resources/templates/user/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
+    <head>
+        <title>Hello Spring Security</title>
+        <meta charset="utf-8" />
+        <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
+    </head>
+    <body>
+        <div th:substituteby="index::logout"></div>
+        <h1>This is a secured page!</h1>
+        <p><a href="/index" th:href="@{/index}">Back to home page</a></p>
+    </body>
+</html>

+ 1 - 0
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) {