preauth & remember-me to authentication

This commit is contained in:
Rob Winch
2020-11-24 13:42:44 -06:00
parent f0a0c95587
commit cfa6376157
48 changed files with 3 additions and 2 deletions

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2002-2018 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 example.pages.HomePage;
import example.pages.LoginPage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
/**
* Test the Hello World application.
*
* @author Michael Simons
*/
public class HelloWorldTests {
private WebDriver driver;
private int port;
@BeforeEach
void setup() {
this.port = Integer.parseInt(System.getProperty("app.httpPort"));
this.driver = new HtmlUnitDriver();
}
@AfterEach
void tearDown() {
this.driver.quit();
}
@Test
void accessHomePageWithUnauthenticatedUserSendsToLoginPage() {
final LoginPage loginPage = HomePage.to(this.driver, this.port);
loginPage.assertAt();
}
@Test
void authenticatedUserIsSentToOriginalPage() {
// @formatter:off
final HomePage homePage = HomePage.to(this.driver, this.port)
.loginForm()
.username("user")
.password("password")
.submit();
// @formatter:on
}
@Test
void authenticatedUserLogsOut() {
// @formatter:off
LoginPage loginPage = HomePage.to(this.driver, this.port)
.loginForm()
.username("user")
.password("password")
.submit()
.logout();
// @formatter:on
loginPage.assertAt();
loginPage = HomePage.to(this.driver, this.port);
loginPage.assertAt();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2018 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 home page.
*
* @author Michael Simons
*/
public class HomePage {
private final WebDriver webDriver;
@FindBy(css = "p")
private WebElement message;
@FindBy(css = "input[type=submit]")
private WebElement logoutButton;
public static LoginPage to(WebDriver driver, int port) {
driver.get("http://localhost:" + port + "/");
return PageFactory.initElements(driver, LoginPage.class);
}
public HomePage(WebDriver webDriver) {
this.webDriver = webDriver;
}
public HomePage assertAt() {
assertThat(this.webDriver.getTitle()).isEqualTo("Hello Security");
return this;
}
public LoginPage logout() {
this.logoutButton.submit();
return PageFactory.initElements(this.webDriver, LoginPage.class);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2002-2018 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 log in page.
*
* @author Michael Simons
*/
public class LoginPage {
private final WebDriver webDriver;
private final LoginForm loginForm;
public LoginPage(WebDriver webDriver) {
this.webDriver = webDriver;
this.loginForm = PageFactory.initElements(this.webDriver, LoginForm.class);
}
public LoginPage assertAt() {
assertThat(this.webDriver.getTitle()).isEqualTo("Please sign in");
return this;
}
public LoginForm loginForm() {
return this.loginForm;
}
public static class LoginForm {
private WebDriver webDriver;
private WebElement username;
private WebElement password;
@FindBy(css = "button[type=submit]")
private WebElement submit;
public LoginForm(WebDriver webDriver) {
this.webDriver = webDriver;
}
public LoginForm username(String username) {
this.username.sendKeys(username);
return this;
}
public LoginForm password(String password) {
this.password.sendKeys(password);
return this;
}
public HomePage submit() {
this.submit.click();
return PageFactory.initElements(this.webDriver, HomePage.class);
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class ApplicationConfiguration {
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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;
/**
* Controller for "/".
*
* @author Rob WInch
*/
@Controller
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}

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.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Controller for the custom log in page.
*
* @author Rob WInch
*/
@Controller
public class LoginController {
@GetMapping("/login")
String login() {
return "login";
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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 javax.servlet.Filter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { ApplicationConfiguration.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Filter[] getServletFilters() {
return new Filter[] { new HiddenHttpMethodFilter() };
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// @formatter:off
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests((authorizeRequests) ->
authorizeRequests
.antMatchers("/login", "/resources/**").permitAll()
.anyRequest().authenticated()
)
.jee((jee) ->
jee
.mappableRoles("USER", "ADMIN")
);
}
// @formatter:on
// @formatter:off
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
// @formatter:on
}

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.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.security.web.session.HttpSessionEventPublisher;
/**
* We customize {@link AbstractSecurityWebApplicationInitializer} to enable the
* {@link HttpSessionEventPublisher}.
*
* @author Rob Winch
*/
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
@Override
protected boolean enableHttpSessionEventPublisher() {
return true;
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.thymeleaf.spring5.ISpringTemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// @formatter:off
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/resources/")
.setCachePeriod(31556926);
// @formatter:on
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
@Bean
public ViewResolver viewResolver(ISpringTemplateEngine templateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine);
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(true);
engine.setTemplateResolver(templateResolver);
return engine;
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
}

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,29 @@
<!--
~ 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.
-->
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Hello Security</title>
</head>
<body>
<div>
<h1>Hello Security</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Logout"/>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,24 @@
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Please sign in</title>
</head>
<body>
<div>
<form name="f" th:action="@{/j_security_check}" method="post">
<fieldset>
<legend>Please Login</legend>
<div th:if="${param.error}" class="alert alert-error">Invalid
username and password.</div>
<div th:if="${param.logout}" class="alert alert-success">You
have been logged out.</div>
<label for="username">Username</label> <input type="text"
id="username" name="j_username" /> <label for="password">Password</label>
<input type="password" id="password" name="j_password" />
<div class="form-actions">
<button type="submit" class="btn">Log in</button>
</div>
</fieldset>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,2 @@
Manifest-Version: 1.0

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login</form-login-page>
<form-error-page>/login?error</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>ROLE_USER</role-name>
</security-role>
<security-constraint>
<web-resource-collection>
<web-resource-name>Public</web-resource-name>
<description>Matches unconstrained pages</description>
<url-pattern>/login</url-pattern>
<url-pattern>/logout</url-pattern>
<url-pattern>/resources/*</url-pattern>
</web-resource-collection>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured Areas</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>ROLE_USER</role-name>
</auth-constraint>
</security-constraint>
</web-app>

View File

@@ -0,0 +1,24 @@
<html xmlns:th="https://www.thymeleaf.org">
<head>
<title>Please sign in</title>
</head>
<body>
<div>
<form name="f" action="/j_security_check" method="post">
<fieldset>
<legend>Please Login</legend>
<div th:if="${param.error}" class="alert alert-error">Invalid
username and password.</div>
<div th:if="${param.logout}" class="alert alert-success">You
have been logged out.</div>
<label for="username">Username</label> <input type="text"
id="username" name="j_username" /> <label for="password">Password</label>
<input type="password" id="password" name="j_password" />
<div class="form-actions">
<button type="submit" class="btn">Log in</button>
</div>
</fieldset>
</form>
</div>
</body>
</html>