12 changed files with 392 additions and 0 deletions
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<parent> |
||||
<!-- Your own application should inherit from spring-boot-starter-parent --> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-samples</artifactId> |
||||
<version>1.1.6.BUILD-SNAPSHOT</version> |
||||
</parent> |
||||
<artifactId>spring-boot-sample-web-secure-jdbc</artifactId> |
||||
<name>spring-boot-sample-web-secure-jdbc</name> |
||||
<description>Spring Boot Web Secure Sample</description> |
||||
<url>http://projects.spring.io/spring-boot/</url> |
||||
<organization> |
||||
<name>Pivotal Software, Inc.</name> |
||||
<url>http://www.spring.io</url> |
||||
</organization> |
||||
<properties> |
||||
<main.basedir>${basedir}/../..</main.basedir> |
||||
</properties> |
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-security</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-jdbc</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.h2database</groupId> |
||||
<artifactId>h2</artifactId> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.apache.httpcomponents</groupId> |
||||
<artifactId>httpclient</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-starter-test</artifactId> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.springframework.boot</groupId> |
||||
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</project> |
||||
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.ui.secure; |
||||
|
||||
import java.util.Date; |
||||
import java.util.Map; |
||||
|
||||
import javax.sql.DataSource; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties; |
||||
import org.springframework.boot.builder.SpringApplicationBuilder; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.core.annotation.Order; |
||||
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.WebSecurityConfigurerAdapter; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; |
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; |
||||
|
||||
@EnableAutoConfiguration |
||||
@ComponentScan |
||||
@Controller |
||||
public class SampleWebSecureCustomApplication extends WebMvcConfigurerAdapter { |
||||
|
||||
@RequestMapping("/") |
||||
public String home(Map<String, Object> model) { |
||||
model.put("message", "Hello World"); |
||||
model.put("title", "Hello Home"); |
||||
model.put("date", new Date()); |
||||
return "home"; |
||||
} |
||||
|
||||
@RequestMapping("/foo") |
||||
public String foo() { |
||||
throw new RuntimeException("Expected exception in controller"); |
||||
} |
||||
|
||||
public static void main(String[] args) throws Exception { |
||||
new SpringApplicationBuilder(SampleWebSecureCustomApplication.class).run(args); |
||||
} |
||||
|
||||
@Override |
||||
public void addViewControllers(ViewControllerRegistry registry) { |
||||
registry.addViewController("/login").setViewName("login"); |
||||
} |
||||
|
||||
@Bean |
||||
public ApplicationSecurity applicationSecurity() { |
||||
return new ApplicationSecurity(); |
||||
} |
||||
|
||||
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) |
||||
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { |
||||
|
||||
@Autowired |
||||
private SecurityProperties security; |
||||
|
||||
@Autowired |
||||
private DataSource dataSource; |
||||
|
||||
@Override |
||||
protected void configure(HttpSecurity http) throws Exception { |
||||
http.authorizeRequests().antMatchers("/css/**").permitAll().anyRequest() |
||||
.fullyAuthenticated().and().formLogin().loginPage("/login") |
||||
.failureUrl("/login?error").permitAll(); |
||||
} |
||||
|
||||
@Override |
||||
public void configure(AuthenticationManagerBuilder auth) throws Exception { |
||||
auth.jdbcAuthentication().dataSource(this.dataSource); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
debug: true |
||||
spring.thymeleaf.cache: false |
||||
security.basic.enabled: false |
||||
logging.level.org.springframework.security: INFO |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
insert into users (username, password, enabled) values ('user', 'user', true); |
||||
|
||||
insert into authorities (username, authority) values ('user', 'ROLE_ADMIN'); |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html> |
||||
<html xmlns:th="http://www.thymeleaf.org"> |
||||
<head> |
||||
<title>Error</title> |
||||
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" |
||||
href="../../css/bootstrap.min.css" /> |
||||
</head> |
||||
<body> |
||||
<div class="container"> |
||||
<div class="navbar"> |
||||
<div class="navbar-inner"> |
||||
<a class="brand" href="http://www.thymeleaf.org"> Thymeleaf - |
||||
Plain </a> |
||||
<ul class="nav"> |
||||
<li><a th:href="@{/}" href="home.html"> Home </a></li> |
||||
<li><a th:href="@{/logout}" href="logout"> Logout </a></li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
<h1 th:text="${title}">Title</h1> |
||||
<div id="created" th:text="${#dates.format(timestamp)}">July 11, |
||||
2012 2:17:16 PM CDT</div> |
||||
<div> |
||||
There was an unexpected error (type=<span th:text="${error}">Bad</span>, status=<span th:text="${status}">500</span>). |
||||
</div> |
||||
<div th:text="${message}">Fake content</div> |
||||
<div> |
||||
Please contact the operator with the above information. |
||||
</div> |
||||
</div> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html> |
||||
<html xmlns:th="http://www.thymeleaf.org"> |
||||
<head> |
||||
<title th:text="${title}">Title</title> |
||||
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" |
||||
href="../../css/bootstrap.min.css" /> |
||||
</head> |
||||
<body> |
||||
<div class="container"> |
||||
<div class="navbar"> |
||||
<div class="navbar-inner"> |
||||
<a class="brand" href="http://www.thymeleaf.org"> Thymeleaf - |
||||
Plain </a> |
||||
<ul class="nav"> |
||||
<li><a th:href="@{/}" href="home.html"> Home </a></li> |
||||
<li><a th:href="@{/logout}" href="logout"> Logout </a></li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
<h1 th:text="${title}">Title</h1> |
||||
<div th:text="${message}">Fake content</div> |
||||
<div id="created" th:text="${#dates.format(date)}">July 11, |
||||
2012 2:17:16 PM CDT</div> |
||||
</div> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html> |
||||
<html xmlns:th="http://www.thymeleaf.org"> |
||||
<head> |
||||
<title>Login</title> |
||||
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" |
||||
href="../../css/bootstrap.min.css" /> |
||||
</head> |
||||
<body onload="document.f.username.focus();"> |
||||
<div class="container"> |
||||
<div class="navbar"> |
||||
<div class="navbar-inner"> |
||||
<a class="brand" href="http://www.thymeleaf.org"> Thymeleaf - |
||||
Plain </a> |
||||
<ul class="nav"> |
||||
<li><a th:href="@{/}" href="home.html"> Home </a></li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
<div class="content"> |
||||
<p th:if="${param.logout}" class="alert">You have been logged out</p> |
||||
<p th:if="${param.error}" class="alert alert-error">There was an error, please try again</p> |
||||
<h2>Login with Username and Password</h2> |
||||
<form name="form" th:action="@{/login}" action="/login" method="POST"> |
||||
<fieldset> |
||||
<input type="text" name="username" value="" placeholder="Username" /> |
||||
<input type="password" name="password" placeholder="Password" /> |
||||
</fieldset> |
||||
<input type="submit" id="login" value="Login" |
||||
class="btn btn-primary" /> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
/* |
||||
* Copyright 2012-2014 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.ui.secure; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.boot.test.IntegrationTest; |
||||
import org.springframework.boot.test.SpringApplicationConfiguration; |
||||
import org.springframework.boot.test.TestRestTemplate; |
||||
import org.springframework.http.HttpEntity; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.test.annotation.DirtiesContext; |
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; |
||||
import org.springframework.test.context.web.WebAppConfiguration; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertNotNull; |
||||
import static org.junit.Assert.assertTrue; |
||||
|
||||
/** |
||||
* Basic integration tests for demo application. |
||||
* |
||||
* @author Dave Syer |
||||
*/ |
||||
@RunWith(SpringJUnit4ClassRunner.class) |
||||
@SpringApplicationConfiguration(classes = SampleWebSecureCustomApplication.class) |
||||
@WebAppConfiguration |
||||
@IntegrationTest("server.port:0") |
||||
@DirtiesContext |
||||
public class SampleWebSecureCustomApplicationTests { |
||||
|
||||
@Value("${local.server.port}") |
||||
private int port; |
||||
|
||||
@Test |
||||
public void testHome() throws Exception { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); |
||||
ResponseEntity<String> entity = new TestRestTemplate().exchange( |
||||
"http://localhost:" + this.port, HttpMethod.GET, new HttpEntity<Void>( |
||||
headers), String.class); |
||||
assertEquals(HttpStatus.FOUND, entity.getStatusCode()); |
||||
assertTrue("Wrong location:\n" + entity.getHeaders(), entity.getHeaders() |
||||
.getLocation().toString().endsWith(port + "/login")); |
||||
} |
||||
|
||||
@Test |
||||
public void testLoginPage() throws Exception { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); |
||||
ResponseEntity<String> entity = new TestRestTemplate().exchange( |
||||
"http://localhost:" + this.port + "/login", HttpMethod.GET, |
||||
new HttpEntity<Void>(headers), String.class); |
||||
assertEquals(HttpStatus.OK, entity.getStatusCode()); |
||||
assertTrue("Wrong content:\n" + entity.getBody(), |
||||
entity.getBody().contains("_csrf")); |
||||
} |
||||
|
||||
@Test |
||||
public void testLogin() throws Exception { |
||||
HttpHeaders headers = getHeaders(); |
||||
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); |
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); |
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>(); |
||||
form.set("username", "user"); |
||||
form.set("password", "user"); |
||||
ResponseEntity<String> entity = new TestRestTemplate().exchange( |
||||
"http://localhost:" + this.port + "/login", HttpMethod.POST, |
||||
new HttpEntity<MultiValueMap<String, String>>(form, headers), |
||||
String.class); |
||||
assertEquals(HttpStatus.FOUND, entity.getStatusCode()); |
||||
assertTrue("Wrong location:\n" + entity.getHeaders(), entity.getHeaders() |
||||
.getLocation().toString().endsWith(port + "/")); |
||||
assertNotNull("Missing cookie:\n" + entity.getHeaders(), |
||||
entity.getHeaders().get("Set-Cookie")); |
||||
} |
||||
|
||||
private HttpHeaders getHeaders() { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
ResponseEntity<String> page = new TestRestTemplate().getForEntity( |
||||
"http://localhost:" + this.port + "/login", String.class); |
||||
assertEquals(HttpStatus.OK, page.getStatusCode()); |
||||
String cookie = page.getHeaders().getFirst("Set-Cookie"); |
||||
headers.set("Cookie", cookie); |
||||
Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*") |
||||
.matcher(page.getBody()); |
||||
assertTrue("No csrf token: " + page.getBody(), matcher.matches()); |
||||
headers.set("X-CSRF-TOKEN", matcher.group(1)); |
||||
return headers; |
||||
} |
||||
|
||||
@Test |
||||
public void testCss() throws Exception { |
||||
ResponseEntity<String> entity = new TestRestTemplate().getForEntity( |
||||
"http://localhost:" + this.port + "/css/bootstrap.min.css", String.class); |
||||
assertEquals(HttpStatus.OK, entity.getStatusCode()); |
||||
assertTrue("Wrong body:\n" + entity.getBody(), entity.getBody().contains("body")); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue