Add sample with form login

This commit is contained in:
Dave Syer 2013-11-01 10:45:54 +00:00
parent e005ba7232
commit e53dad879d
12 changed files with 387 additions and 8 deletions

View File

@ -98,6 +98,9 @@ import org.springframework.security.web.util.matcher.AnyRequestMatcher;
@EnableConfigurationProperties
public class SecurityAutoConfiguration {
private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**",
"/images/**", "/**/favicon.ico");
private static final String[] NO_PATHS = new String[0];
@Bean(name = "org.springframework.actuate.properties.SecurityProperties")
@ -131,9 +134,6 @@ public class SecurityAutoConfiguration {
private static class ApplicationWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**",
"/images/**", "/**/favicon.ico");
@Autowired
private SecurityProperties security;
@ -271,7 +271,19 @@ public class SecurityAutoConfiguration {
@Override
public void configure(WebSecurity builder) throws Exception {
IgnoredRequestConfigurer ignoring = builder.ignoring();
ignoring.antMatchers(getEndpointPaths(this.endpointHandlerMapping, false));
List<String> ignored = new ArrayList<String>();
if (!this.security.getBasic().isEnabled()) {
ignored.addAll(this.security.getIgnored());
if (ignored.isEmpty()) {
ignored.addAll(DEFAULT_IGNORED);
}
else if (ignored.contains("none")) {
ignored.remove("none");
}
}
ignored.addAll(Arrays.asList(getEndpointPaths(this.endpointHandlerMapping,
false)));
ignoring.antMatchers(ignored.toArray(new String[0]));
}
private AuthenticationEntryPoint entryPoint() {

View File

@ -66,7 +66,7 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class);
TestUtils.addEnviroment(this.context, "security.ignored:none");
this.context.refresh();
// Just the application and\ management endpoints now
// Just the application and management endpoints now
assertEquals(2, this.context.getBean(FilterChainProxy.class).getFilterChains()
.size());
}
@ -81,8 +81,8 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class);
TestUtils.addEnviroment(this.context, "security.basic.enabled:false");
this.context.refresh();
// Just the management endpoints now
assertEquals(1, this.context.getBean(FilterChainProxy.class).getFilterChains()
// Just the management endpoints and default ignores now
assertEquals(5, this.context.getBean(FilterChainProxy.class).getFilterChains()
.size());
}

View File

@ -52,7 +52,7 @@ public class SampleActuatorUiApplication {
@Bean
public SecurityProperties securityProperties() {
SecurityProperties security = new SecurityProperties();
security.getBasic().setPath(""); // empty
security.getBasic().setPath(""); // empty so home page is unsecured
return security;
}

View File

@ -0,0 +1,48 @@
<?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>0.5.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-secure</artifactId>
<packaging>jar</packaging>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,78 @@
/*
* Copyright 2012-2013 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.boot.sample.ops.ui;
import java.util.Date;
import java.util.Map;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
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 SampleSecureApplication 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 {
// Set user password to "password" for demo purposes only
new SpringApplicationBuilder(SampleSecureApplication.class).properties(
"security.basic.enabled=false", "security.user.password=password").run(args);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
@Bean
public ApplicationSecurity applicationSecurity() {
return new ApplicationSecurity();
}
@Order(Ordered.LOWEST_PRECEDENCE - 8)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.loginPage("/login").failureUrl("/login?error").permitAll();
}
}
}

View File

@ -0,0 +1 @@
spring.thymeleaf.cache: false

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<!-- logger name="org.springframework.boot" level="DEBUG"/-->
<logger name="org.springframework.security" level="DEBUG"/>
</configuration>

File diff suppressed because one or more lines are too long

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,35 @@
<!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" 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" /> <input type="hidden"
th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,130 @@
/*
* Copyright 2012-2013 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.boot.sample.ops.ui;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
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.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Basic integration tests for demo application.
*
* @author Dave Syer
*/
public class SampleSecureApplicationTests {
private static ConfigurableApplicationContext context;
@BeforeClass
public static void start() throws Exception {
Future<ConfigurableApplicationContext> future = Executors
.newSingleThreadExecutor().submit(
new Callable<ConfigurableApplicationContext>() {
@Override
public ConfigurableApplicationContext call() throws Exception {
return (ConfigurableApplicationContext) SpringApplication
.run(SampleSecureApplication.class);
}
});
context = future.get(60, TimeUnit.SECONDS);
}
@AfterClass
public static void stop() {
if (context != null) {
context.close();
}
}
@Test
public void testHome() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = getRestTemplate().exchange(
"http://localhost:8080", HttpMethod.GET, new HttpEntity<Void>(headers),
String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(), entity
.getBody().contains("<title>Login"));
}
@Test
public void testCss() throws Exception {
ResponseEntity<String> entity = getRestTemplate().getForEntity(
"http://localhost:8080/css/bootstrap.min.css", String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body:\n" + entity.getBody(), entity.getBody().contains("body"));
}
@Test
public void testMetrics() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate().getForEntity(
"http://localhost:8080/metrics", Map.class);
assertEquals(HttpStatus.UNAUTHORIZED, entity.getStatusCode());
}
@Test
public void testError() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = getRestTemplate().exchange(
"http://localhost:8080/error", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body:\n" + entity.getBody(), entity.getBody()
.contains("<html>"));
assertTrue("Wrong body:\n" + entity.getBody(), entity.getBody()
.contains("<body>"));
assertTrue("Wrong body:\n" + entity.getBody(), entity.getBody()
.contains("Please contact the operator with the above information"));
}
private RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
});
return restTemplate;
}
}