Browse Source

Modernize Default Log In Page

Fixes: gh-5515
Rob Winch 7 years ago
parent
commit
05ed028f9d
13 changed files with 385 additions and 189 deletions
  1. 170 61
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy
  2. 115 42
      config/src/test/java/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.java
  3. 4 4
      samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java
  4. 2 2
      samples/javaconfig/helloworld/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java
  5. 2 2
      samples/javaconfig/jdbc/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java
  6. 2 2
      samples/javaconfig/ldap/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java
  7. 2 2
      samples/xml/helloworld/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java
  8. 2 2
      samples/xml/jaas/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java
  9. 3 3
      samples/xml/jaas/src/integration-test/java/org/springframework/security/samples/pages/LogoutPage.java
  10. 2 2
      samples/xml/ldap/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java
  11. 3 3
      samples/xml/ldap/src/integration-test/java/org/springframework/security/samples/pages/LogoutPage.java
  12. 76 63
      web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java
  13. 2 1
      web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java

+ 170 - 61
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy

@@ -53,15 +53,33 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 			request.requestURI = "/login"
 			springSecurityFilterChain.doFilter(request,response,chain)
 		then:
-			response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
-<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
-<table>
-	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
-	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
-	<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
-	<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
-</table>
-</form></body></html>"""
+			response.getContentAsString() == """<!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 sign 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="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
+  </head>
+  <body>
+     <div class="container">
+      <form class="form-signin" method="post" action="/login">
+        <h2 class="form-signin-heading">Please sign in</h2>
+        <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>
+<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+</body></html>"""
 		when: "fail to log in"
 			super.setup()
 			request.servletPath = "/login"
@@ -77,15 +95,33 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 			request.queryString = "error"
 			springSecurityFilterChain.doFilter(request,response,chain)
 		then:
-			response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
-<p style='color:red;'>Your login attempt was not successful, try again.<br/><br/>Reason: Bad credentials</p><h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
-<table>
-	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
-	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
-	<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
-	<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
-</table>
-</form></body></html>"""
+			response.getContentAsString() == """<!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 sign 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="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
+  </head>
+  <body>
+     <div class="container">
+      <form class="form-signin" method="post" action="/login">
+        <h2 class="form-signin-heading">Please sign in</h2>
+<div class="alert alert-danger" role="alert">Bad credentials</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>
+<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+</body></html>"""
 		when: "login success"
 			super.setup()
 			request.servletPath = "/login"
@@ -106,15 +142,33 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 			request.method = "GET"
 			springSecurityFilterChain.doFilter(request,response,chain)
 		then: "sent to default success page"
-			response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
-<p style='color:green;'>You have been logged out</p><h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
-<table>
-	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
-	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
-	<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
-	<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
-</table>
-</form></body></html>"""
+			response.getContentAsString() == """<!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 sign 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="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
+  </head>
+  <body>
+     <div class="container">
+      <form class="form-signin" method="post" action="/login">
+        <h2 class="form-signin-heading">Please sign in</h2>
+<div class="alert alert-success" role="alert">You have been signed 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>
+<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+</body></html>"""
 	}
 
 	@Configuration
@@ -191,16 +245,34 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 			request.requestURI = "/login"
 			springSecurityFilterChain.doFilter(request,response,chain)
 		then:
-			response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
-<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
-<table>
-	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
-	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
-	<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
-	<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
-	<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
-</table>
-</form></body></html>"""
+			response.getContentAsString() == """<!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 sign 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="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
+  </head>
+  <body>
+     <div class="container">
+      <form class="form-signin" method="post" action="/login">
+        <h2 class="form-signin-heading">Please sign in</h2>
+        <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>
+<p><input type='checkbox' name='remember-me'/> Remember me on this computer.</p>
+<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+</body></html>"""
 	}
 
 	@Configuration
@@ -224,13 +296,29 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 			request.requestURI = "/login"
 			springSecurityFilterChain.doFilter(request,response,chain)
 		then:
-			response.getContentAsString() == """<html><head><title>Login Page</title></head><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
-<table>
-	<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
-	<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
-</table>
-	<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
-</form></body></html>"""
+			response.getContentAsString() == """<!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 sign 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="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
+  </head>
+  <body>
+     <div class="container">
+      <form name="oidf" class="form-signin" method="post" action="/login/openid">
+        <h2 class="form-signin-heading">Login with OpenID Identity</h2>
+        <p>
+          <label for="username" class="sr-only">Identity</label>
+          <input type="text" id="username" name="openid_identifier" class="form-control" placeholder="Username" required autofocus>
+        </p>
+<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+</body></html>"""
 	}
 
 	@Configuration
@@ -252,23 +340,44 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 			request.requestURI = "/login"
 			springSecurityFilterChain.doFilter(request,response,chain)
 		then:
-			response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
-<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
-<table>
-	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
-	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
-	<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
-	<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
-	<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
-</table>
-</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
-<table>
-	<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
-	<tr><td><input type='checkbox' name='remember-me'></td><td>Remember me on this computer.</td></tr>
-	<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
-</table>
-	<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
-</form></body></html>"""
+			response.getContentAsString() == """<!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 sign 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="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
+  </head>
+  <body>
+     <div class="container">
+      <form class="form-signin" method="post" action="/login">
+        <h2 class="form-signin-heading">Please sign in</h2>
+        <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>
+<p><input type='checkbox' name='remember-me'/> Remember me on this computer.</p>
+<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+      <form name="oidf" class="form-signin" method="post" action="/login/openid">
+        <h2 class="form-signin-heading">Login with OpenID Identity</h2>
+        <p>
+          <label for="username" class="sr-only">Identity</label>
+          <input type="text" id="username" name="openid_identifier" class="form-control" placeholder="Username" required autofocus>
+        </p>
+<p><input type='checkbox' name='remember-me'/> Remember me on this computer.</p>
+<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" />
+        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
+      </form>
+</body></html>"""
 	}
 
 	@Configuration

+ 115 - 42
config/src/test/java/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.java

@@ -54,14 +54,32 @@ public class FormLoginBeanDefinitionParserTests {
 		this.spring.configLocations(this.xml("Simple")).autowire();
 
 		String expectedContent =
-				"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" +
-				"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" +
-				"<table>\n" +
-				"	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
-				"	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
-				"	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
-				"</table>\n" +
-				"</form></body></html>";
+				"<!DOCTYPE html>\n"
+						+ "<html lang=\"en\">\n"
+						+ "  <head>\n"
+						+ "    <meta charset=\"utf-8\">\n"
+						+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
+						+ "    <meta name=\"description\" content=\"\">\n"
+						+ "    <meta name=\"author\" content=\"\">\n"
+						+ "    <title>Please sign in</title>\n"
+						+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+						+ "    <link href=\"http://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+						+ "  </head>\n"
+						+ "  <body>\n"
+						+ "     <div class=\"container\">\n"
+						+ "      <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
+						+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
+						+ "          <input type=\"text\" id=\"username\" name=\"username\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+						+ "        </p>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"password\" class=\"sr-only\">Password</label>\n"
+						+ "          <input type=\"password\" id=\"password\" name=\"password\" class=\"form-control\" placeholder=\"Password\" required>\n"
+						+ "        </p>\n"
+						+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+						+ "      </form>\n"
+						+ "</body></html>";
 
 		this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
 	}
@@ -73,14 +91,31 @@ public class FormLoginBeanDefinitionParserTests {
 		this.spring.configLocations(this.xml("WithCustomAttributes")).autowire();
 
 		String expectedContent =
-				"<html><head><title>Login Page</title></head><body onload='document.f.custom_user.focus();'>\n" +
-						"<h3>Login with Username and Password</h3><form name='f' action='/signin' method='POST'>\n" +
-						"<table>\n" +
-						"	<tr><td>User:</td><td><input type='text' name='custom_user' value=''></td></tr>\n" +
-						"	<tr><td>Password:</td><td><input type='password' name='custom_pass'/></td></tr>\n" +
-						"	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
-						"</table>\n" +
-						"</form></body></html>";
+				"<!DOCTYPE html>\n"
+						+ "<html lang=\"en\">\n" + "  <head>\n"
+						+ "    <meta charset=\"utf-8\">\n"
+						+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
+						+ "    <meta name=\"description\" content=\"\">\n"
+						+ "    <meta name=\"author\" content=\"\">\n"
+						+ "    <title>Please sign in</title>\n"
+						+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+						+ "    <link href=\"http://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+						+ "  </head>\n"
+						+ "  <body>\n"
+						+ "     <div class=\"container\">\n"
+						+ "      <form class=\"form-signin\" method=\"post\" action=\"/signin\">\n"
+						+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
+						+ "          <input type=\"text\" id=\"username\" name=\"custom_user\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+						+ "        </p>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"password\" class=\"sr-only\">Password</label>\n"
+						+ "          <input type=\"password\" id=\"password\" name=\"custom_pass\" class=\"form-control\" placeholder=\"Password\" required>\n"
+						+ "        </p>\n"
+						+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+						+ "      </form>\n"
+						+ "</body></html>";
 
 		this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
 	}
@@ -92,19 +127,38 @@ public class FormLoginBeanDefinitionParserTests {
 		this.spring.configLocations(this.xml("WithOpenId")).autowire();
 
 		String expectedContent =
-				"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" +
-				"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" +
-				"<table>\n" +
-				"	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
-				"	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
-				"	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
-				"</table>\n" +
-				"</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>\n" +
-				"<table>\n" +
-				"	<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>\n" +
-				"	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
-				"</table>\n" +
-				"</form></body></html>";
+				"<!DOCTYPE html>\n" + "<html lang=\"en\">\n" + "  <head>\n"
+						+ "    <meta charset=\"utf-8\">\n"
+						+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
+						+ "    <meta name=\"description\" content=\"\">\n"
+						+ "    <meta name=\"author\" content=\"\">\n"
+						+ "    <title>Please sign in</title>\n"
+						+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+						+ "    <link href=\"http://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+						+ "  </head>\n"
+						+ "  <body>\n"
+						+ "     <div class=\"container\">\n"
+						+ "      <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
+						+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
+						+ "          <input type=\"text\" id=\"username\" name=\"username\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+						+ "        </p>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"password\" class=\"sr-only\">Password</label>\n"
+						+ "          <input type=\"password\" id=\"password\" name=\"password\" class=\"form-control\" placeholder=\"Password\" required>\n"
+						+ "        </p>\n"
+						+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+						+ "      </form>\n"
+						+ "      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"/login/openid\">\n"
+						+ "        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"username\" class=\"sr-only\">Identity</label>\n"
+						+ "          <input type=\"text\" id=\"username\" name=\"openid_identifier\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+						+ "        </p>\n"
+						+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+						+ "      </form>\n"
+						+ "</body></html>";
 
 		this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
 	}
@@ -116,19 +170,38 @@ public class FormLoginBeanDefinitionParserTests {
 		this.spring.configLocations(this.xml("WithOpenIdCustomAttributes")).autowire();
 
 		String expectedContent =
-				"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" +
-						"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" +
-						"<table>\n" +
-						"	<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
-						"	<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
-						"	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
-						"</table>\n" +
-						"</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/signin' method='POST'>\n" +
-						"<table>\n" +
-						"	<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>\n" +
-						"	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
-						"</table>\n" +
-						"</form></body></html>";
+				"<!DOCTYPE html>\n" + "<html lang=\"en\">\n" + "  <head>\n"
+						+ "    <meta charset=\"utf-8\">\n"
+						+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
+						+ "    <meta name=\"description\" content=\"\">\n"
+						+ "    <meta name=\"author\" content=\"\">\n"
+						+ "    <title>Please sign in</title>\n"
+						+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+						+ "    <link href=\"http://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+						+ "  </head>\n"
+						+ "  <body>\n"
+						+ "     <div class=\"container\">\n"
+						+ "      <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
+						+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
+						+ "          <input type=\"text\" id=\"username\" name=\"username\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+						+ "        </p>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"password\" class=\"sr-only\">Password</label>\n"
+						+ "          <input type=\"password\" id=\"password\" name=\"password\" class=\"form-control\" placeholder=\"Password\" required>\n"
+						+ "        </p>\n"
+						+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+						+ "      </form>\n"
+						+ "      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"/signin\">\n"
+						+ "        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"
+						+ "        <p>\n"
+						+ "          <label for=\"username\" class=\"sr-only\">Identity</label>\n"
+						+ "          <input type=\"text\" id=\"username\" name=\"openid_identifier\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+						+ "        </p>\n"
+						+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+						+ "      </form>\n"
+						+ "</body></html>";
 
 		this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
 	}

+ 4 - 4
samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java

@@ -218,7 +218,7 @@ public class OAuth2LoginApplicationTests {
 		page = this.webClient.getPage(new URL(authorizationResponseUri));
 		assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
 
-		HtmlElement errorElement = page.getBody().getFirstByXPath("p");
+		HtmlElement errorElement = page.getBody().getFirstByXPath("div");
 		assertThat(errorElement).isNotNull();
 		assertThat(errorElement.asText()).contains("authorization_request_not_found");
 	}
@@ -248,7 +248,7 @@ public class OAuth2LoginApplicationTests {
 		page = this.webClient.getPage(new URL(authorizationResponseUri));
 		assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
 
-		HtmlElement errorElement = page.getBody().getFirstByXPath("p");
+		HtmlElement errorElement = page.getBody().getFirstByXPath("div");
 		assertThat(errorElement).isNotNull();
 		assertThat(errorElement.asText()).contains("authorization_request_not_found");
 	}
@@ -284,13 +284,13 @@ public class OAuth2LoginApplicationTests {
 		page = this.webClient.getPage(new URL(authorizationResponseUri));
 		assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl);
 
-		HtmlElement errorElement = page.getBody().getFirstByXPath("p");
+		HtmlElement errorElement = page.getBody().getFirstByXPath("div");
 		assertThat(errorElement).isNotNull();
 		assertThat(errorElement.asText()).contains("invalid_redirect_uri_parameter");
 	}
 
 	private void assertLoginPage(HtmlPage page) throws Exception {
-		assertThat(page.getTitleText()).isEqualTo("Login Page");
+		assertThat(page.getTitleText()).isEqualTo("Please sign in");
 
 		int expectedClients = 4;
 

+ 2 - 2
samples/javaconfig/helloworld/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java

@@ -37,7 +37,7 @@ public class LoginPage {
 	}
 
 	public LoginPage assertAt() {
-		assertThat(this.webDriver.getTitle()).isEqualTo("Login Page");
+		assertThat(this.webDriver.getTitle()).isEqualTo("Please sign in");
 		return this;
 	}
 
@@ -49,7 +49,7 @@ public class LoginPage {
 		private WebDriver webDriver;
 		private WebElement username;
 		private WebElement password;
-		@FindBy(css = "input[type=submit]")
+		@FindBy(css = "button[type=submit]")
 		private WebElement submit;
 
 		public LoginForm(WebDriver webDriver) {

+ 2 - 2
samples/javaconfig/jdbc/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java

@@ -37,7 +37,7 @@ public class LoginPage {
 	}
 
 	public LoginPage assertAt() {
-		assertThat(this.webDriver.getTitle()).isEqualTo("Login Page");
+		assertThat(this.webDriver.getTitle()).isEqualTo("Please sign in");
 		return this;
 	}
 
@@ -49,7 +49,7 @@ public class LoginPage {
 		private WebDriver webDriver;
 		private WebElement username;
 		private WebElement password;
-		@FindBy(css = "input[type=submit]")
+		@FindBy(css = "button[type=submit]")
 		private WebElement submit;
 
 		public LoginForm(WebDriver webDriver) {

+ 2 - 2
samples/javaconfig/ldap/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java

@@ -37,7 +37,7 @@ public class LoginPage {
 	}
 
 	public LoginPage assertAt() {
-		assertThat(this.webDriver.getTitle()).isEqualTo("Login Page");
+		assertThat(this.webDriver.getTitle()).isEqualTo("Please sign in");
 		return this;
 	}
 
@@ -49,7 +49,7 @@ public class LoginPage {
 		private WebDriver webDriver;
 		private WebElement username;
 		private WebElement password;
-		@FindBy(css = "input[type=submit]")
+		@FindBy(css = "button[type=submit]")
 		private WebElement submit;
 
 		public LoginForm(WebDriver webDriver) {

+ 2 - 2
samples/xml/helloworld/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java

@@ -37,7 +37,7 @@ public class LoginPage {
 	}
 
 	public LoginPage assertAt() {
-		assertThat(this.webDriver.getTitle()).isEqualTo("Login Page");
+		assertThat(this.webDriver.getTitle()).isEqualTo("Please sign in");
 		return this;
 	}
 
@@ -49,7 +49,7 @@ public class LoginPage {
 		private WebDriver webDriver;
 		private WebElement username;
 		private WebElement password;
-		@FindBy(css = "input[type=submit]")
+		@FindBy(css = "button[type=submit]")
 		private WebElement submit;
 
 		public LoginForm(WebDriver webDriver) {

+ 2 - 2
samples/xml/jaas/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java

@@ -39,7 +39,7 @@ public class LoginPage {
 	}
 
 	public LoginPage assertAt() {
-		assertThat(this.webDriver.getTitle()).isEqualTo("Login Page");
+		assertThat(this.webDriver.getTitle()).isEqualTo("Please sign in");
 		return this;
 	}
 
@@ -51,7 +51,7 @@ public class LoginPage {
 		private WebDriver webDriver;
 		private WebElement username;
 		private WebElement password;
-		@FindBy(css = "input[type=submit]")
+		@FindBy(css = "button[type=submit]")
 		private WebElement submit;
 
 		public LoginForm(WebDriver webDriver) {

+ 3 - 3
samples/xml/jaas/src/integration-test/java/org/springframework/security/samples/pages/LogoutPage.java

@@ -27,8 +27,8 @@ import static org.assertj.core.api.Assertions.assertThat;
  * @author Michael Simons
  */
 public class LogoutPage extends LoginPage {
-	@FindBy(css = "p")
-	private WebElement p;
+	@FindBy(css = "div[role=alert]")
+	private WebElement alert;
 
 	public LogoutPage(WebDriver webDriver) {
 		super(webDriver);
@@ -38,7 +38,7 @@ public class LogoutPage extends LoginPage {
 	public LogoutPage assertAt() {
 		super.assertAt();
 
-		assertThat(p.getText()).isEqualTo("You have been logged out");
+		assertThat(this.alert.getText()).isEqualTo("You have been signed out");
 		return this;
 	}
 }

+ 2 - 2
samples/xml/ldap/src/integration-test/java/org/springframework/security/samples/pages/LoginPage.java

@@ -39,7 +39,7 @@ public class LoginPage {
 	}
 
 	public LoginPage assertAt() {
-		assertThat(this.webDriver.getTitle()).isEqualTo("Login Page");
+		assertThat(this.webDriver.getTitle()).isEqualTo("Please sign in");
 		return this;
 	}
 
@@ -51,7 +51,7 @@ public class LoginPage {
 		private WebDriver webDriver;
 		private WebElement username;
 		private WebElement password;
-		@FindBy(css = "input[type=submit]")
+		@FindBy(css = "button[type=submit]")
 		private WebElement submit;
 
 		public LoginForm(WebDriver webDriver) {

+ 3 - 3
samples/xml/ldap/src/integration-test/java/org/springframework/security/samples/pages/LogoutPage.java

@@ -27,8 +27,8 @@ import static org.assertj.core.api.Assertions.assertThat;
  * @author Michael Simons
  */
 public class LogoutPage extends LoginPage {
-	@FindBy(css = "p")
-	private WebElement p;
+	@FindBy(css = "div[role=alert]")
+	private WebElement alert;
 
 	public LogoutPage(WebDriver webDriver) {
 		super(webDriver);
@@ -38,7 +38,7 @@ public class LogoutPage extends LoginPage {
 	public LogoutPage assertAt() {
 		super.assertAt();
 
-		assertThat(p.getText()).isEqualTo("You have been logged out");
+		assertThat(this.alert.getText()).isEqualTo("You have been signed out");
 		return this;
 	}
 }

+ 76 - 63
web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageGeneratingFilter.java

@@ -208,7 +208,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
 
 	private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
 			boolean logoutSuccess) {
-		String errorMsg = "none";
+		String errorMsg = "Invalid credentials";
 
 		if (loginError) {
 			HttpSession session = request.getSession(false);
@@ -216,82 +216,76 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
 			if (session != null) {
 				AuthenticationException ex = (AuthenticationException) session
 						.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
-				errorMsg = ex != null ? ex.getMessage() : "none";
+				errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
 			}
 		}
 
 		StringBuilder sb = new StringBuilder();
 
-		sb.append("<html><head><title>Login Page</title></head>");
-
-		if (formLoginEnabled) {
-			sb.append("<body onload='document.f.").append(usernameParameter)
-					.append(".focus();'>\n");
-		}
-
-		if (loginError) {
-			sb.append("<p style='color:red;'>Your login attempt was not successful, try again.<br/><br/>Reason: ");
-			sb.append(errorMsg);
-			sb.append("</p>");
-		}
-
-		if (logoutSuccess) {
-			sb.append("<p style='color:green;'>You have been logged out</p>");
-		}
-
-		if (formLoginEnabled) {
-			sb.append("<h3>Login with Username and Password</h3>");
-			sb.append("<form name='f' action='").append(request.getContextPath())
-					.append(authenticationUrl).append("' method='POST'>\n");
-			sb.append("<table>\n");
-			sb.append("	<tr><td>User:</td><td><input type='text' name='");
-			sb.append(usernameParameter).append("' value='").append("'></td></tr>\n");
-			sb.append("	<tr><td>Password:</td><td><input type='password' name='")
-					.append(passwordParameter).append("'/></td></tr>\n");
-
-			if (rememberMeParameter != null) {
-				sb.append("	<tr><td><input type='checkbox' name='")
-						.append(rememberMeParameter)
-						.append("'/></td><td>Remember me on this computer.</td></tr>\n");
-			}
-
-			sb.append("	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n");
-			renderHiddenInputs(sb, request);
-			sb.append("</table>\n");
-			sb.append("</form>");
+		sb.append("<!DOCTYPE html>\n"
+				+ "<html lang=\"en\">\n"
+				+ "  <head>\n"
+				+ "    <meta charset=\"utf-8\">\n"
+				+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
+				+ "    <meta name=\"description\" content=\"\">\n"
+				+ "    <meta name=\"author\" content=\"\">\n"
+				+ "    <title>Please sign in</title>\n"
+				+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+				+ "    <link href=\"http://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+				+ "  </head>\n"
+				+ "  <body>\n"
+				+ "     <div class=\"container\">\n");
+
+		String contextPath = request.getContextPath();
+		if (this.formLoginEnabled) {
+			sb.append("      <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"
+					+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
+					+ createError(loginError, errorMsg)
+					+ createLogoutSuccess(logoutSuccess)
+					+ "        <p>\n"
+					+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
+					+ "          <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+					+ "        </p>\n"
+					+ "        <p>\n"
+					+ "          <label for=\"password\" class=\"sr-only\">Password</label>\n"
+					+ "          <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"
+					+ "        </p>\n"
+					+ createRememberMe(this.rememberMeParameter)
+					+ renderHiddenInputs(request)
+					+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+					+ "      </form>\n");
 		}
 
 		if (openIdEnabled) {
-			sb.append("<h3>Login with OpenID Identity</h3>");
-			sb.append("<form name='oidf' action='").append(request.getContextPath())
-					.append(openIDauthenticationUrl).append("' method='POST'>\n");
-			sb.append("<table>\n");
-			sb.append("	<tr><td>Identity:</td><td><input type='text' size='30' name='");
-			sb.append(openIDusernameParameter).append("'/></td></tr>\n");
-
-			if (openIDrememberMeParameter != null) {
-				sb.append("	<tr><td><input type='checkbox' name='")
-						.append(openIDrememberMeParameter)
-						.append("'></td><td>Remember me on this computer.</td></tr>\n");
-			}
-
-			sb.append("	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n");
-			sb.append("</table>\n");
-			renderHiddenInputs(sb, request);
-			sb.append("</form>");
+			sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"
+					+ "        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"
+					+ createError(loginError, errorMsg)
+					+ createLogoutSuccess(logoutSuccess)
+					+ "        <p>\n"
+					+ "          <label for=\"username\" class=\"sr-only\">Identity</label>\n"
+					+ "          <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
+					+ "        </p>\n"
+					+ createRememberMe(this.openIDrememberMeParameter)
+					+ renderHiddenInputs(request)
+					+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
+					+ "      </form>\n");
 		}
 
 		if (oauth2LoginEnabled) {
-			sb.append("<h3>Login with OAuth 2.0</h3>");
-			sb.append("<table>\n");
+			sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h3>");
+			sb.append(createError(loginError, errorMsg));
+			sb.append(createLogoutSuccess(logoutSuccess));
+			sb.append("<table class=\"table table-striped\">\n");
 			for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
 				sb.append(" <tr><td>");
-				sb.append("<a href=\"").append(request.getContextPath()).append(clientAuthenticationUrlToClientName.getKey()).append("\">");
-				sb.append(HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue(), "UTF-8"));
+				String url = clientAuthenticationUrlToClientName.getKey();
+				sb.append("<a href=\"").append(contextPath).append(url).append("\">");
+				String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());
+				sb.append(clientName);
 				sb.append("</a>");
 				sb.append("</td></tr>\n");
 			}
-			sb.append("</table>\n");
+			sb.append("</table></div>\n");
 		}
 
 		sb.append("</body></html>");
@@ -299,10 +293,21 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
 		return sb.toString();
 	}
 
-	private void renderHiddenInputs(StringBuilder sb, HttpServletRequest request) {
+	private String renderHiddenInputs(HttpServletRequest request) {
+		StringBuilder sb = new StringBuilder();
 		for(Map.Entry<String, String> input : this.resolveHiddenInputs.apply(request).entrySet()) {
-			sb.append("	<input name=\"").append(input.getKey()).append("\" type=\"hidden\" value=\"").append(input.getValue()).append("\" />\n");
+			sb.append("<input name=\"").append(input.getKey()).append("\" type=\"hidden\" value=\"").append(input.getValue()).append("\" />\n");
+		}
+		return sb.toString();
+	}
+
+	private String createRememberMe(String paramName) {
+		if (paramName == null) {
+			return "";
 		}
+		return "<p><input type='checkbox' name='"
+				+ paramName
+				+ "'/> Remember me on this computer.</p>\n";
 	}
 
 	private boolean isLogoutSuccess(HttpServletRequest request) {
@@ -317,6 +322,14 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
 		return matches(request, failureUrl);
 	}
 
+	private static String createError(boolean isError, String message) {
+		return isError ? "<div class=\"alert alert-danger\" role=\"alert\">" + HtmlUtils.htmlEscape(message) + "</div>" : "";
+	}
+
+	private static String createLogoutSuccess(boolean isLogoutSuccess) {
+		return isLogoutSuccess ? "<div class=\"alert alert-success\" role=\"alert\">You have been signed out</div>" : "";
+	}
+
 	private boolean matches(HttpServletRequest request, String url) {
 		if (!"GET".equals(request.getMethod()) || url == null) {
 			return false;

+ 2 - 1
web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java

@@ -122,7 +122,8 @@ public class LoginPageGeneratingWebFilter implements WebFilter {
 		boolean isLogoutSuccess = queryParams.containsKey("logout");
 		return "      <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
 				+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
-				+ createError(isError) + createLogoutSuccess(isLogoutSuccess)
+				+ createError(isError)
+				+ createLogoutSuccess(isLogoutSuccess)
 				+ "        <p>\n"
 				+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
 				+ "          <input type=\"text\" id=\"username\" name=\"username\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"