Merge branch '2.5.x'

Closes gh-28789
pull/28809/head
Scott Frederick 3 years ago
commit 4eed637481

@ -76,9 +76,7 @@ dependencies {
testImplementation("org.springframework:spring-websocket")
testImplementation("org.springframework.hateoas:spring-hateoas")
testImplementation("org.springframework.security:spring-security-test")
testImplementation("org.thymeleaf:thymeleaf")
testImplementation("org.thymeleaf:thymeleaf-spring5")
testImplementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect")
testImplementation("org.freemarker:freemarker")
testRuntimeOnly("org.aspectj:aspectjweaver")
testRuntimeOnly("org.yaml:snakeyaml")

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -64,7 +64,6 @@ class RemoteUrlPropertyExtractorTests {
void validUrl() {
ApplicationContext context = doTest("http://localhost:8080");
assertThat(context.getEnvironment().getProperty("remoteUrl")).isEqualTo("http://localhost:8080");
assertThat(context.getEnvironment().getProperty("spring.thymeleaf.cache")).isNull();
}
@Test

@ -29,12 +29,11 @@ import org.apache.jasper.EmbeddedServletOptions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
@ -55,6 +54,7 @@ import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -83,18 +83,11 @@ class LocalDevToolsAutoConfigurationTests {
}
}
@Test
void thymeleafCacheIsFalse() throws Exception {
this.context = getContext(() -> initializeAndRun(Config.class));
SpringResourceTemplateResolver resolver = this.context.getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isFalse();
}
@Test
void defaultPropertyCanBeOverriddenFromCommandLine() throws Exception {
this.context = getContext(() -> initializeAndRun(Config.class, "--spring.thymeleaf.cache=true"));
SpringResourceTemplateResolver resolver = this.context.getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isTrue();
this.context = getContext(() -> initializeAndRun(Config.class, "--spring.freemarker.cache=true"));
AbstractTemplateViewResolver resolver = this.context.getBean(AbstractTemplateViewResolver.class);
assertThat(resolver.isCache()).isTrue();
}
@Test
@ -103,8 +96,8 @@ class LocalDevToolsAutoConfigurationTests {
System.setProperty("user.home", new File("src/test/resources/user-home").getAbsolutePath());
try {
this.context = getContext(() -> initializeAndRun(Config.class));
SpringResourceTemplateResolver resolver = this.context.getBean(SpringResourceTemplateResolver.class);
assertThat(resolver.isCacheable()).isTrue();
AbstractTemplateViewResolver resolver = this.context.getBean(AbstractTemplateViewResolver.class);
assertThat(resolver.isCache()).isTrue();
}
finally {
System.setProperty("user.home", userHome);
@ -257,7 +250,6 @@ class LocalDevToolsAutoConfigurationTests {
private Map<String, Object> getDefaultProperties(Map<String, Object> specifiedProperties) {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.thymeleaf.check-template-location", false);
properties.put("spring.devtools.livereload.port", 0);
properties.put("server.port", 0);
properties.putAll(specifiedProperties);
@ -266,14 +258,14 @@ class LocalDevToolsAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
@Import({ ServletWebServerFactoryAutoConfiguration.class, LocalDevToolsAutoConfiguration.class,
ThymeleafAutoConfiguration.class })
FreeMarkerAutoConfiguration.class })
static class Config {
}
@Configuration(proxyBeanMethods = false)
@ImportAutoConfiguration({ ServletWebServerFactoryAutoConfiguration.class, LocalDevToolsAutoConfiguration.class,
ThymeleafAutoConfiguration.class })
FreeMarkerAutoConfiguration.class })
static class ConfigWithMockLiveReload {
@Bean

@ -13,7 +13,6 @@ configurations {
dependencies {
developmentOnly project(":spring-boot-project:spring-boot-devtools")
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-thymeleaf"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -16,15 +16,9 @@
package smoketest.devtools;
import java.util.Date;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
@ -34,15 +28,4 @@ public class MyController {
Thread.sleep(5000);
}
@GetMapping("/")
public ModelAndView get(HttpSession session) {
Object sessionVar = session.getAttribute("var");
if (sessionVar == null) {
sessionVar = new Date();
session.setAttribute("var", sessionVar);
}
ModelMap model = new ModelMap("message", Message.MESSAGE).addAttribute("sessionVar", sessionVar);
return new ModelAndView("hello", model);
}
}

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Hello</title>
</head>
<body>
<h1 th:text="${message}">Header</h1>
<div class="content">
<h2 th:text="${sessionVar}">Session Var</h2>
Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Cras ut fringilla augue, quis dictum
turpis. Sed tincidunt mi vel euismod viverra. Nulla facilisi.
Suspendisse mauris dolor, egestas ac leo at, porttitor ullamcorper
leo. Suspendisse consequat, justo ut rutrum interdum, nibh massa
semper dui, id sagittis tellus lectus at nibh. Etiam at scelerisque
nisi. Quisque vel eros tempor, fermentum sapien sed, gravida neque.
Fusce interdum sed dolor a semper. Morbi porta mauris a velit laoreet
viverra. Praesent et tellus vehicula, sagittis mi quis, faucibus urna.
Ut diam tortor, vehicula eget aliquam eget, elementum a odio. Fusce at
nisl sapien. Suspendisse potenti.
</div>
</body>
</html>

@ -8,7 +8,6 @@ description = "Spring Boot web method security smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-thymeleaf"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -16,9 +16,6 @@
package smoketest.security.method;
import java.util.Date;
import java.util.Map;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@ -32,7 +29,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
@ -71,17 +67,11 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
protected static class ApplicationSecurity {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
requests.antMatchers("/login").permitAll();
requests.anyRequest().fullyAuthenticated();
});
http.formLogin((form) -> {
form.loginPage("/login");
form.failureUrl("/login?error");
});
http.logout((logout) -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")));
http.exceptionHandling((exceptions) -> exceptions.accessDeniedPage("/access?error"));
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests((requests) -> requests.anyRequest().fullyAuthenticated());
http.formLogin((form) -> form.loginPage("/login").permitAll());
http.exceptionHandling((exceptions) -> exceptions.accessDeniedPage("/access"));
return http.build();
}
@ -93,6 +83,7 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
@Bean
SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
http.csrf().disable();
http.requestMatcher(EndpointRequest.toAnyEndpoint());
http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
http.httpBasic();
@ -106,10 +97,7 @@ public class SampleMethodSecurityApplication implements WebMvcConfigurer {
@GetMapping("/")
@Secured("ROLE_ADMIN")
public String home(Map<String, Object> model) {
model.put("message", "Hello World");
model.put("title", "Hello Home");
model.put("date", new Date());
public String home() {
return "home";
}

@ -1,3 +1,4 @@
spring.thymeleaf.cache: false
logging.level.org.springframework.security: INFO
logging.level.org.springframework.security=INFO
management.endpoints.web.exposure.include=*
spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.html

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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>
<p class="alert alert-error">Access denied:
you do not have permission for that resource</p>
<div th:text="${message}">Fake content</div>
<div>Please contact the operator with the above information.</div>
</div>
</body>
</html>

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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>

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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>

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<div class="container">
<p>Access denied: you do not have permission for that resource</p>
</div>
</body>
</html>

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<div class="container">
<p>Home</p>
</div>
</body>
</html>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body">
<div class="container">
<div class="content">
<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" />
</form>
</div>
</div>
</body>
</html>

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -16,9 +16,7 @@
package smoketest.security.method;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Collections;
import org.junit.jupiter.api.Test;
@ -43,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Basic integration tests for demo application.
*
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleMethodSecurityApplicationTests {
@ -56,35 +55,32 @@ class SampleMethodSecurityApplicationTests {
@Test
void testHome() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, new HttpEntity<Void>(headers),
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("<title>Login");
}
@Test
void testLogin() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", "admin");
form.set("password", "admin");
getCsrf(form, headers);
ResponseEntity<String> entity = this.restTemplate.exchange("/login", HttpMethod.POST,
new HttpEntity<>(form, headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(entity.getHeaders().getLocation().toString()).isEqualTo("http://localhost:" + this.port + "/");
assertThat(entity.getHeaders().getLocation().toString()).endsWith(this.port + "/");
}
@Test
void testDenied() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", "user");
form.set("password", "user");
getCsrf(form, headers);
ResponseEntity<String> entity = this.restTemplate.exchange("/login", HttpMethod.POST,
new HttpEntity<>(form, headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
@ -99,7 +95,7 @@ class SampleMethodSecurityApplicationTests {
@Test
void testManagementProtected() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = this.restTemplate.exchange("/actuator/beans", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
@ -118,14 +114,4 @@ class SampleMethodSecurityApplicationTests {
}
}
private void getCsrf(MultiValueMap<String, String> form, HttpHeaders headers) {
ResponseEntity<String> page = this.restTemplate.getForEntity("/login", String.class);
String cookie = page.getHeaders().getFirst("Set-Cookie");
headers.set("Cookie", cookie);
String body = page.getBody();
Matcher matcher = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*").matcher(body);
matcher.find();
form.set("_csrf", matcher.group(1));
}
}

@ -7,7 +7,6 @@ description = "Spring Boot web secure custom smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-thymeleaf"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -16,41 +16,21 @@
package smoketest.web.secure.custom;
import java.util.Date;
import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
@Controller
public class SampleWebSecureCustomApplication implements WebMvcConfigurer {
@GetMapping("/")
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");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
registry.addViewController("/login").setViewName("login");
}
@ -63,15 +43,9 @@ public class SampleWebSecureCustomApplication implements WebMvcConfigurer {
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
requests.antMatchers("/css/**").permitAll();
requests.anyRequest().fullyAuthenticated();
});
http.formLogin((form) -> {
form.loginPage("/login");
form.failureUrl("/login?error").permitAll();
});
http.logout(LogoutConfigurer::permitAll);
http.csrf().disable();
http.authorizeRequests((requests) -> requests.anyRequest().fullyAuthenticated());
http.formLogin((form) -> form.loginPage("/login").permitAll());
return http.build();
}

@ -1,4 +1,5 @@
spring.thymeleaf.cache: false
logging.level.org.springframework.security: INFO
logging.level.org.springframework.security=INFO
spring.security.user.name=user
spring.security.user.password=password
spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.html

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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>

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://www.thymeleaf.org"> Thymeleaf -
Plain </a>
<ul class="nav">
<li><a th:href="@{/}" href="home.html"> Home </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>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</div>
</body>
</html>

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<div class="container">
<p>Home</p>
</div>
</body>
</html>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<div class="container">
<div class="content">
<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" />
</form>
</div>
</div>
</body>
</html>

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -16,9 +16,7 @@
package smoketest.web.secure.custom;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Collections;
import org.junit.jupiter.api.Test;
@ -42,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Basic integration tests for demo application.
*
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleWebSecureCustomApplicationTests {
@ -55,7 +54,7 @@ class SampleWebSecureCustomApplicationTests {
@Test
void testHome() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, new HttpEntity<Void>(headers),
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
@ -65,17 +64,17 @@ class SampleWebSecureCustomApplicationTests {
@Test
void testLoginPage() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/login", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("_csrf");
assertThat(entity.getBody()).contains("<title>Login</title>");
}
@Test
void testLogin() {
HttpHeaders headers = getHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", "user");
@ -84,27 +83,6 @@ class SampleWebSecureCustomApplicationTests {
new HttpEntity<>(form, headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(entity.getHeaders().getLocation().toString()).endsWith(this.port + "/");
assertThat(entity.getHeaders().get("Set-Cookie")).isNotNull();
}
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> page = this.restTemplate.getForEntity("/login", String.class);
assertThat(page.getStatusCode()).isEqualTo(HttpStatus.OK);
String cookie = page.getHeaders().getFirst("Set-Cookie");
headers.set("Cookie", cookie);
Pattern pattern = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*");
Matcher matcher = pattern.matcher(page.getBody());
assertThat(matcher.matches()).as(page.getBody()).isTrue();
headers.set("X-CSRF-TOKEN", matcher.group(1));
return headers;
}
@Test
void testCss() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/css/bootstrap.min.css", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("body");
}
}

@ -8,7 +8,6 @@ description = "Spring Boot web secure JDBC smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jdbc"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-thymeleaf"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
runtimeOnly("com.h2database:h2")

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -16,9 +16,6 @@
package smoketest.web.secure.jdbc;
import java.util.Date;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -26,34 +23,17 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
@Controller
public class SampleWebSecureJdbcApplication implements WebMvcConfigurer {
@GetMapping("/")
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");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
registry.addViewController("/login").setViewName("login");
}
@ -66,15 +46,9 @@ public class SampleWebSecureJdbcApplication implements WebMvcConfigurer {
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> {
requests.antMatchers("/css/**").permitAll();
requests.anyRequest().fullyAuthenticated();
});
http.formLogin((form) -> {
form.loginPage("/login");
form.failureUrl("/login?error").permitAll();
});
http.logout(LogoutConfigurer::permitAll);
http.csrf().disable();
http.authorizeRequests((requests) -> requests.anyRequest().fullyAuthenticated());
http.formLogin((form) -> form.loginPage("/login").permitAll());
return http.build();
}

@ -1,3 +1,3 @@
debug: true
spring.thymeleaf.cache: false
logging.level.org.springframework.security: INFO
logging.level.org.springframework.security=INFO
spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.html

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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>

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://www.thymeleaf.org"> Thymeleaf -
Plain </a>
<ul class="nav">
<li><a th:href="@{/}" href="home.html"> Home </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>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</div>
</body>
</html>

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<div class="container">
<p>Home</p>
</div>
</body>
</html>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body">
<div class="container">
<div class="content">
<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" />
</form>
</div>
</div>
</body>
</html>

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -16,9 +16,7 @@
package smoketest.web.secure.jdbc;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Collections;
import org.junit.jupiter.api.Test;
@ -42,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Basic integration tests for demo application.
*
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleWebSecureJdbcApplicationTests {
@ -55,7 +54,7 @@ class SampleWebSecureJdbcApplicationTests {
@Test
void testHome() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, new HttpEntity<Void>(headers),
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
@ -65,17 +64,17 @@ class SampleWebSecureJdbcApplicationTests {
@Test
void testLoginPage() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/login", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("_csrf");
assertThat(entity.getBody()).contains("<title>Login</title>");
}
@Test
void testLogin() {
HttpHeaders headers = getHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", "user");
@ -84,27 +83,6 @@ class SampleWebSecureJdbcApplicationTests {
new HttpEntity<>(form, headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(entity.getHeaders().getLocation().toString()).endsWith(this.port + "/");
assertThat(entity.getHeaders().get("Set-Cookie")).isNotNull();
}
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> page = this.restTemplate.getForEntity("/login", String.class);
assertThat(page.getStatusCode()).isEqualTo(HttpStatus.OK);
String cookie = page.getHeaders().getFirst("Set-Cookie");
headers.set("Cookie", cookie);
Pattern pattern = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*");
Matcher matcher = pattern.matcher(page.getBody());
assertThat(matcher.matches()).as(page.getBody()).isTrue();
headers.set("X-CSRF-TOKEN", matcher.group(1));
return headers;
}
@Test
void testCss() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/css/bootstrap.min.css", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("body");
}
}

@ -6,9 +6,7 @@ plugins {
description = "Spring Boot web secure smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-thymeleaf"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -16,42 +16,21 @@
package smoketest.web.secure;
import java.util.Date;
import java.util.Map;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
@Controller
public class SampleWebSecureApplication implements WebMvcConfigurer {
@GetMapping("/")
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");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
registry.addViewController("/login").setViewName("login");
}
@ -64,17 +43,13 @@ public class SampleWebSecureApplication implements WebMvcConfigurer {
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests((requests) -> {
requests.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
requests.antMatchers("/public/**").permitAll();
requests.anyRequest().fullyAuthenticated();
});
http.httpBasic();
http.formLogin((form) -> {
form.loginPage("/login");
form.failureUrl("/login?error").permitAll();
});
http.logout(LogoutConfigurer::permitAll);
http.formLogin((form) -> form.loginPage("/login").permitAll());
return http.build();
}

@ -1,6 +1,6 @@
spring.thymeleaf.cache: false
# demo only:
logging.level.org.springframework.security: INFO
logging.level.org.springframework.boot.actuate.audit.listener.AuditListener: DEBUG
logging.level.org.springframework.security=INFO
logging.level.org.springframework.boot.actuate.audit.listener.AuditListener=DEBUG
spring.security.user.name=user
spring.security.user.password=password
spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.html

@ -1,10 +0,0 @@
create table users (
username varchar(256),
password varchar(256),
enabled boolean
);
create table authorities (
username varchar(256),
authority varchar(256)
);

@ -1,32 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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>

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://www.thymeleaf.org"> Thymeleaf -
Plain </a>
<ul class="nav">
<li><a th:href="@{/}" href="home.html"> Home </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>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</div>
</body>
</html>

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="https://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="https://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,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<div class="container">
<p>Home</p>
</div>
</body>
</html>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<div class="container">
<div class="content">
<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" />
</form>
</div>
</div>
</body>
</html>

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -16,9 +16,7 @@
package smoketest.web.secure;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Collections;
import org.junit.jupiter.api.Test;
@ -42,9 +40,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* Basic integration tests for demo application.
*
* @author Dave Syer
* @author Scott Frederick
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class SampleSecureApplicationTests {
class SampleWebSecureApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@ -55,9 +54,9 @@ class SampleSecureApplicationTests {
@Test
void testHome() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/", HttpMethod.GET, new HttpEntity<Void>(headers),
String.class);
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/home", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(entity.getHeaders().getLocation().toString()).endsWith(this.port + "/login");
}
@ -65,17 +64,17 @@ class SampleSecureApplicationTests {
@Test
void testLoginPage() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = this.restTemplate.exchange("/login", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("_csrf");
assertThat(entity.getBody()).contains("<title>Login</title>");
}
@Test
void testLogin() {
HttpHeaders headers = getHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", "user");
@ -84,27 +83,6 @@ class SampleSecureApplicationTests {
new HttpEntity<>(form, headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
assertThat(entity.getHeaders().getLocation().toString()).endsWith(this.port + "/");
assertThat(entity.getHeaders().get("Set-Cookie")).isNotNull();
}
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
ResponseEntity<String> page = this.restTemplate.getForEntity("/login", String.class);
assertThat(page.getStatusCode()).isEqualTo(HttpStatus.OK);
String cookie = page.getHeaders().getFirst("Set-Cookie");
headers.set("Cookie", cookie);
Pattern pattern = Pattern.compile("(?s).*name=\"_csrf\".*?value=\"([^\"]+).*");
Matcher matcher = pattern.matcher(page.getBody());
assertThat(matcher.matches()).as(page.getBody()).isTrue();
headers.set("X-CSRF-TOKEN", matcher.group(1));
return headers;
}
@Test
void testCss() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/css/bootstrap.min.css", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("body");
}
}

@ -3,7 +3,7 @@ plugins {
id "org.springframework.boot.conventions"
}
description = "Spring Boot web UI smoke test"
description = "Spring Boot web Thymeleaf smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-thymeleaf"))

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package smoketest.web.ui;
package smoketest.web.thymeleaf;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package smoketest.web.ui;
package smoketest.web.thymeleaf;
import java.util.Calendar;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package smoketest.web.ui;
package smoketest.web.thymeleaf;
public interface MessageRepository {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package smoketest.web.ui;
package smoketest.web.thymeleaf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -14,12 +14,12 @@
* limitations under the License.
*/
package smoketest.web.ui.mvc;
package smoketest.web.thymeleaf.mvc;
import javax.validation.Valid;
import smoketest.web.ui.Message;
import smoketest.web.ui.MessageRepository;
import smoketest.web.thymeleaf.Message;
import smoketest.web.thymeleaf.MessageRepository;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package smoketest.web.ui;
package smoketest.web.thymeleaf;
import java.util.regex.Pattern;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package smoketest.web.ui;
package smoketest.web.thymeleaf;
import java.net.URI;
@ -63,12 +63,4 @@ class SampleWebUiApplicationTests {
assertThat(location.toString()).contains("localhost:" + this.port);
}
@Test
void testCss() {
ResponseEntity<String> entity = this.restTemplate
.getForEntity("http://localhost:" + this.port + "/css/bootstrap.min.css", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("body");
}
}
Loading…
Cancel
Save