Modernize Default Log In Page

Fixes: gh-5515
This commit is contained in:
Rob Winch 2018-07-09 12:52:52 -05:00
parent a66b945ab7
commit 05ed028f9d
13 changed files with 384 additions and 188 deletions

View File

@ -53,15 +53,33 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
request.requestURI = "/login" request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'> response.getContentAsString() == """<!DOCTYPE html>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'> <html lang="en">
<table> <head>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> <meta charset="utf-8">
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> <meta name="description" content="">
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> <meta name="author" content="">
</table> <title>Please sign in</title>
</form></body></html>""" <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" when: "fail to log in"
super.setup() super.setup()
request.servletPath = "/login" request.servletPath = "/login"
@ -77,15 +95,33 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
request.queryString = "error" request.queryString = "error"
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'> response.getContentAsString() == """<!DOCTYPE html>
<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'> <html lang="en">
<table> <head>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> <meta charset="utf-8">
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> <meta name="description" content="">
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> <meta name="author" content="">
</table> <title>Please sign in</title>
</form></body></html>""" <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" when: "login success"
super.setup() super.setup()
request.servletPath = "/login" request.servletPath = "/login"
@ -106,15 +142,33 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
request.method = "GET" request.method = "GET"
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default success page" then: "sent to default success page"
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'> response.getContentAsString() == """<!DOCTYPE html>
<p style='color:green;'>You have been logged out</p><h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'> <html lang="en">
<table> <head>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> <meta charset="utf-8">
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> <meta name="description" content="">
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> <meta name="author" content="">
</table> <title>Please sign in</title>
</form></body></html>""" <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 @Configuration
@ -191,16 +245,34 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
request.requestURI = "/login" request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'> response.getContentAsString() == """<!DOCTYPE html>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'> <html lang="en">
<table> <head>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> <meta charset="utf-8">
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr> <meta name="description" content="">
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> <meta name="author" content="">
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> <title>Please sign in</title>
</table> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
</form></body></html>""" <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 @Configuration
@ -224,13 +296,29 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
request.requestURI = "/login" request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'> response.getContentAsString() == """<!DOCTYPE html>
<table> <html lang="en">
<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr> <head>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> <meta charset="utf-8">
</table> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> <meta name="description" content="">
</form></body></html>""" <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 @Configuration
@ -252,23 +340,44 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
request.requestURI = "/login" request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain) springSecurityFilterChain.doFilter(request,response,chain)
then: then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'> response.getContentAsString() == """<!DOCTYPE html>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'> <html lang="en">
<table> <head>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr> <meta charset="utf-8">
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr> <meta name="description" content="">
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> <meta name="author" content="">
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> <title>Please sign in</title>
</table> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'> <link href="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
<table> </head>
<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr> <body>
<tr><td><input type='checkbox' name='remember-me'></td><td>Remember me on this computer.</td></tr> <div class="container">
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr> <form class="form-signin" method="post" action="/login">
</table> <h2 class="form-signin-heading">Please sign in</h2>
<input name="${csrfToken.parameterName}" type="hidden" value="${csrfToken.token}" /> <p>
</form></body></html>""" <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 @Configuration

View File

@ -54,14 +54,32 @@ public class FormLoginBeanDefinitionParserTests {
this.spring.configLocations(this.xml("Simple")).autowire(); this.spring.configLocations(this.xml("Simple")).autowire();
String expectedContent = String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" + "<!DOCTYPE html>\n"
"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" + + "<html lang=\"en\">\n"
"<table>\n" + + " <head>\n"
" <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" + + " <meta charset=\"utf-8\">\n"
" <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" + + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" + + " <meta name=\"description\" content=\"\">\n"
"</table>\n" + + " <meta name=\"author\" content=\"\">\n"
"</form></body></html>"; + " <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)); this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
} }
@ -73,14 +91,31 @@ public class FormLoginBeanDefinitionParserTests {
this.spring.configLocations(this.xml("WithCustomAttributes")).autowire(); this.spring.configLocations(this.xml("WithCustomAttributes")).autowire();
String expectedContent = String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.custom_user.focus();'>\n" + "<!DOCTYPE html>\n"
"<h3>Login with Username and Password</h3><form name='f' action='/signin' method='POST'>\n" + + "<html lang=\"en\">\n" + " <head>\n"
"<table>\n" + + " <meta charset=\"utf-8\">\n"
" <tr><td>User:</td><td><input type='text' name='custom_user' value=''></td></tr>\n" + + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
" <tr><td>Password:</td><td><input type='password' name='custom_pass'/></td></tr>\n" + + " <meta name=\"description\" content=\"\">\n"
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" + + " <meta name=\"author\" content=\"\">\n"
"</table>\n" + + " <title>Please sign in</title>\n"
"</form></body></html>"; + " <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)); this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
} }
@ -92,19 +127,38 @@ public class FormLoginBeanDefinitionParserTests {
this.spring.configLocations(this.xml("WithOpenId")).autowire(); this.spring.configLocations(this.xml("WithOpenId")).autowire();
String expectedContent = String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" + "<!DOCTYPE html>\n" + "<html lang=\"en\">\n" + " <head>\n"
"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" + + " <meta charset=\"utf-8\">\n"
"<table>\n" + + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
" <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" + + " <meta name=\"description\" content=\"\">\n"
" <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" + + " <meta name=\"author\" content=\"\">\n"
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" + + " <title>Please sign in</title>\n"
"</table>\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"
"</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>\n" + + " <link href=\"http://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
"<table>\n" + + " </head>\n"
" <tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>\n" + + " <body>\n"
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" + + " <div class=\"container\">\n"
"</table>\n" + + " <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
"</form></body></html>"; + " <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)); this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
} }
@ -116,19 +170,38 @@ public class FormLoginBeanDefinitionParserTests {
this.spring.configLocations(this.xml("WithOpenIdCustomAttributes")).autowire(); this.spring.configLocations(this.xml("WithOpenIdCustomAttributes")).autowire();
String expectedContent = String expectedContent =
"<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>\n" + "<!DOCTYPE html>\n" + "<html lang=\"en\">\n" + " <head>\n"
"<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>\n" + + " <meta charset=\"utf-8\">\n"
"<table>\n" + + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
" <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" + + " <meta name=\"description\" content=\"\">\n"
" <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" + + " <meta name=\"author\" content=\"\">\n"
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" + + " <title>Please sign in</title>\n"
"</table>\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"
"</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/signin' method='POST'>\n" + + " <link href=\"http://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
"<table>\n" + + " </head>\n"
" <tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>\n" + + " <body>\n"
" <tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" + + " <div class=\"container\">\n"
"</table>\n" + + " <form class=\"form-signin\" method=\"post\" action=\"/login\">\n"
"</form></body></html>"; + " <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)); this.mvc.perform(get("/login")).andExpect(content().string(expectedContent));
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -208,7 +208,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
boolean logoutSuccess) { boolean logoutSuccess) {
String errorMsg = "none"; String errorMsg = "Invalid credentials";
if (loginError) { if (loginError) {
HttpSession session = request.getSession(false); HttpSession session = request.getSession(false);
@ -216,82 +216,76 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
if (session != null) { if (session != null) {
AuthenticationException ex = (AuthenticationException) session AuthenticationException ex = (AuthenticationException) session
.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); .getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
errorMsg = ex != null ? ex.getMessage() : "none"; errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
} }
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("<html><head><title>Login Page</title></head>"); 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");
if (formLoginEnabled) { String contextPath = request.getContextPath();
sb.append("<body onload='document.f.").append(usernameParameter) if (this.formLoginEnabled) {
.append(".focus();'>\n"); 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)
if (loginError) { + createLogoutSuccess(logoutSuccess)
sb.append("<p style='color:red;'>Your login attempt was not successful, try again.<br/><br/>Reason: "); + " <p>\n"
sb.append(errorMsg); + " <label for=\"username\" class=\"sr-only\">Username</label>\n"
sb.append("</p>"); + " <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
} + " </p>\n"
+ " <p>\n"
if (logoutSuccess) { + " <label for=\"password\" class=\"sr-only\">Password</label>\n"
sb.append("<p style='color:green;'>You have been logged out</p>"); + " <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"
} + " </p>\n"
+ createRememberMe(this.rememberMeParameter)
if (formLoginEnabled) { + renderHiddenInputs(request)
sb.append("<h3>Login with Username and Password</h3>"); + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
sb.append("<form name='f' action='").append(request.getContextPath()) + " </form>\n");
.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>");
} }
if (openIdEnabled) { if (openIdEnabled) {
sb.append("<h3>Login with OpenID Identity</h3>"); sb.append(" <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"
sb.append("<form name='oidf' action='").append(request.getContextPath()) + " <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"
.append(openIDauthenticationUrl).append("' method='POST'>\n"); + createError(loginError, errorMsg)
sb.append("<table>\n"); + createLogoutSuccess(logoutSuccess)
sb.append(" <tr><td>Identity:</td><td><input type='text' size='30' name='"); + " <p>\n"
sb.append(openIDusernameParameter).append("'/></td></tr>\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"
if (openIDrememberMeParameter != null) { + " </p>\n"
sb.append(" <tr><td><input type='checkbox' name='") + createRememberMe(this.openIDrememberMeParameter)
.append(openIDrememberMeParameter) + renderHiddenInputs(request)
.append("'></td><td>Remember me on this computer.</td></tr>\n"); + " <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
} + " </form>\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>");
} }
if (oauth2LoginEnabled) { if (oauth2LoginEnabled) {
sb.append("<h3>Login with OAuth 2.0</h3>"); sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h3>");
sb.append("<table>\n"); 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()) { for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
sb.append(" <tr><td>"); sb.append(" <tr><td>");
sb.append("<a href=\"").append(request.getContextPath()).append(clientAuthenticationUrlToClientName.getKey()).append("\">"); String url = clientAuthenticationUrlToClientName.getKey();
sb.append(HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue(), "UTF-8")); sb.append("<a href=\"").append(contextPath).append(url).append("\">");
String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());
sb.append(clientName);
sb.append("</a>"); sb.append("</a>");
sb.append("</td></tr>\n"); sb.append("</td></tr>\n");
} }
sb.append("</table>\n"); sb.append("</table></div>\n");
} }
sb.append("</body></html>"); sb.append("</body></html>");
@ -299,10 +293,21 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
return sb.toString(); 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()) { 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) { private boolean isLogoutSuccess(HttpServletRequest request) {
@ -317,6 +322,14 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
return matches(request, failureUrl); 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) { private boolean matches(HttpServletRequest request, String url) {
if (!"GET".equals(request.getMethod()) || url == null) { if (!"GET".equals(request.getMethod()) || url == null) {
return false; return false;

View File

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