add reactive/webflux/java folder

This commit is contained in:
Rob Winch
2020-07-29 14:51:50 -05:00
parent 56beccdef9
commit 1077d2c75c
138 changed files with 9 additions and 9 deletions

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2020 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
*
* https://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 example;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import example.pages.IndexPage;
import example.pages.LoginPage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
/**
* Integration tests for WebFlux based form log in sample.
*
* @author Rob Winch
* @since 5.0
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebfluxFormApplicationITests {
WebDriver driver;
@LocalServerPort
int port;
@BeforeEach
void setup() {
this.driver = new HtmlUnitDriver(BrowserVersion.CHROME);
}
@Test
void loginWhenInvalidUsernameThenError() {
LoginPage login = IndexPage.to(this.driver, this.port, LoginPage.class);
login.assertAt();
login.loginForm().username("invalid").password("password").submit(LoginPage.class).assertError();
}
@Test
void loginAndLogout() {
LoginPage login = IndexPage.to(this.driver, this.port, LoginPage.class);
login.assertAt();
IndexPage index = login.loginForm().username("user").password("password").submit(IndexPage.class);
index.assertAt();
login = index.logout();
login.assertAt().assertLogout();
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2020 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
*
* https://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 example.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The index page.
*
* @author Rob Winch
* @since 5.0
*/
public class IndexPage {
private WebDriver driver;
private WebElement logout;
public IndexPage(WebDriver webDriver) {
this.driver = webDriver;
}
public static <T> T to(WebDriver driver, int port, Class<T> page) {
driver.get("http://localhost:" + port + "/");
return PageFactory.initElements(driver, page);
}
public IndexPage assertAt() {
assertThat(this.driver.getTitle()).isEqualTo("Secured");
return this;
}
public LoginPage logout() {
this.logout.click();
return LoginPage.create(this.driver);
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2020 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
*
* https://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 example.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The custom log in page.
*
* @author Rob Winch
* @since 5.0
*/
public class LoginPage {
private WebDriver driver;
@FindBy(css = "div[role=alert]")
private WebElement alert;
private LoginForm loginForm;
public LoginPage(WebDriver webDriver) {
this.driver = webDriver;
this.loginForm = PageFactory.initElements(webDriver, LoginForm.class);
}
static LoginPage create(WebDriver driver) {
return PageFactory.initElements(driver, LoginPage.class);
}
public LoginPage assertAt() {
assertThat(this.driver.getTitle()).isEqualTo("Please Log In");
return this;
}
public LoginPage assertError() {
assertThat(this.alert.getText()).isEqualTo("Invalid username and password.");
return this;
}
public LoginPage assertLogout() {
assertThat(this.alert.getText()).isEqualTo("You have been logged out.");
return this;
}
public LoginForm loginForm() {
return this.loginForm;
}
public static class LoginForm {
private WebDriver driver;
private WebElement username;
private WebElement password;
@FindBy(css = "button[type=submit]")
private WebElement submit;
public LoginForm(WebDriver driver) {
this.driver = driver;
}
public LoginForm username(String username) {
this.username.sendKeys(username);
return this;
}
public LoginForm password(String password) {
this.password.sendKeys(password);
return this;
}
public <T> T submit(Class<T> page) {
this.submit.click();
return PageFactory.initElements(this.driver, page);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020 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
*
* https://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 example;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Sends the user to the views.
*
* @author Rob Winch
* @since 5.0
*/
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
@GetMapping("/login")
public String login() {
return "login";
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2020 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
*
* https://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 example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Demonstrates how to make a webflux based application with a custom form log in.
*
* @author Rob Winch
* @since 5.0
*/
@SpringBootApplication
public class WebfluxFormApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxFormApplication.class, args);
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2020 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
*
* https://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 example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* WebFlux configuration for custom log in page.
*
* @author Rob Winch
* @since 5.0
*/
@Configuration
@EnableWebFluxSecurity
public class WebfluxFormSecurityConfiguration {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
// @formatter:off
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new MapReactiveUserDetailsService(user);
}
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((exchanges) -> exchanges
.pathMatchers("/login").permitAll()
.anyExchange().authenticated()
)
.httpBasic(withDefaults())
.formLogin((form) -> form
.loginPage("/login")
);
// @formatter:on
return http.build();
}
}

View File

@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>Secured</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div class="container">
<h1>Secured</h1>
<form th:action="@{/logout}" method="post">
<input id="logout" type="submit" value="Log Out"/>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Please Log In</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body>
<div class="container">
<form class="form-signin" method="post" th:action="@{/login}">
<h2 class="form-signin-heading">Please Log In</h2>
<div th:if="${param.error}" class="alert alert-danger" role="alert">Invalid
username and password.</div>
<div th:if="${param.logout}" class="alert alert-success" role="alert">You
have been logged out.</div>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</body>
</html>