1. Overview

In this article we're going to illustrate how to implement a simple Login Page with Spring MVC for an application that's handling the authentication with Spring Security in the backend.

For the full details on how to handle login with Spring Security, here's the article going in depth into the configuration and implementation of that.

2. The Login Page

Let's start by defining a very simple login page:

<html> <head></head> <body> <h1>Login</h1> <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 name="submit" type="submit" value="submit" /></td> </tr> </table> </form> </body> </html>

Now, let's include a client side check to make sure the username and password have been entered before we even submit the form. For this example we'll use plain Javascript, but JQuery is an excellent option as well:

<script type="text/javascript"> function validate() { if (document.f.username.value == "" && document.f.password.value == "") { alert("Username and password are required"); document.f.username.focus(); return false; } if (document.f.username.value == "") { alert("Username is required"); document.f.username.focus(); return false; } if (document.f.password.value == "") { alert("Password is required"); document.f.password.focus(); return false; } } </script>

As you can see, we simply check if the username or password fields are empty; if they are – a javascript message box will pop up with the corresponding message.

3. Message Localization

Next – let's localize the messages we're using on the front end. There are types of such messages, and each is localized in a different manner:

Messages generated before the form is processed by Spring's controllers or handlers. These messages ca be referenced in the JSP pages and are localized with Jsp/Jslt localization (see Section 4.3.) Messages that are localized once a page has been submitted for processing by Spring (after submitting the login form); these messages are localized using Spring MVC localization (See Section 4.2.)

3.1. The message.properties Files

In either case, we need to create a message.properties file for each language we want to support; the names of the files should follow this convention: messages_[localeCode].properties.

For example, if we want to support English and Spanish error messages we would have the file: messages_en.properties and messages_es_ES.properties. Note that, for English – messages.properties is also valid.

We're going to place these two files in the project's classpath (src/main/resources). The files simply contain the error codes and messages we need to display in different languages – for example:

message.username=Username required message.password=Password required message.unauth=Unauthorized access!! message.badCredentials=Invalid username or password message.sessionExpired=Session timed out message.logoutError=Sorry, error login out message.logoutSucc=You logged out successfully

3.2. Configuring Spring MVC Localization

Spring MVC provides a LocaleResolver that works in conjunction with its LocaleChangeInterceptor API to make possible the display of messages in different languages, depending on the locale setting. To configure localization – we need to define the following beans in our MVC configuration:

@Override public void addInterceptors(InterceptorRegistry registry) { LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); localeChangeInterceptor.setParamName("lang"); registry.addInterceptor(localeChangeInterceptor); } @Bean public LocaleResolver localeResolver() { CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); return cookieLocaleResolver; }

By default, the locale resolver will obtain the locale code from the HTTP header. To force a default locale, we need to set it on the localeResolver():

@Bean public LocaleResolver localeResolver() { CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver(); cookieLocaleResolver.setDefaultLocale(Locale.ENGLISH); return cookieLocaleResolver; }

This locale resolver is a CookieLocaleResolver which means that it stores the locale information in a client-side cookie; as such – it will remember the user's locale every time they log in, and during the entire visit.

alternatively, there is a SessionLocaleResolver, which remembers the locale throughout the session. To use this LocaleResolver instead, we need to replace the above method with the following:

@Bean public LocaleResolver localeResolver() { SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver(); return sessionLocaleResolver; }

Lastly, note that the LocaleChangeInterceptor will change the locale based on the value of a lang parameter sent with the login page by simple links:

<a href="?lang=en">English</a> | <a href="?lang=es_ES">Spanish</a>

3.3. JSP/JSLT Localization

JSP/JSLT API will be used to display localized messages that are caught in the jsp page itself. To use the jsp localization libraries we should add the following dependencies to the pom.xml:

<dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.2-b01</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>

4. Displaying Error Messages

4.1. Login Validation Errors

In order to use the JSP/JSTL support and display localized messages in the login.jsp lets implement the following changes in the page:

1. Add the following tag lib element to the login.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

2. Add the jsp/jslt element that will point to the messages.properties files:

<fmt:setBundle basename="messages" />

3. Add the following <fmt:…> elements to store the messages on jsp variables:

<fmt:message key="message.password" var="noPass" /> <fmt:message key="message.username" var="noUser" />

4. Modify the login validation script we saw in Section 3 so as to localize the error messages:

<script type="text/javascript"> function validate() { if (document.f.username.value == "" && document.f.password.value == "") { alert("${noUser} and ${noPass}"); document.f.username.focus(); return false; } if (document.f.username.value == "") { alert("${noUser}"); document.f.username.focus(); return false; } if (document.f.password.value == "") { alert("${noPass}"); document.f.password.focus(); return false; } } </script>

4.2. Pre Login Errors

Sometimes the login page will be passed an error parameter if the previous operation failed. For example, a registration form submit button will load the login page. If the registration was successful, then it would be a good idea to show a success message in the login form, and an error message if the opposite was true.

In the sample login form below, we are implementing this by intercepting and regSucc and regError parameters, and displaying a localized message based on their values.

<c:if test="${param.regSucc == true}"> <div id="status"> <spring:message code="message.regSucc"> </spring:message> </div> </c:if> <c:if test="${param.regError == true}"> <div id="error"> <spring:message code="message.regError"> </spring:message> </div> </c:if>

4.3. Login Security Errors

In case the login process fails for some reason, Spring Security will do a redirect to a login error URL, which we have defined to be /login.html?error=true.

So – similarly to how we have shown the status of the registration in the page, we need to do the same in case of a login problem:

<c:if test="${param.error != null}"> <div id="error"> <spring:message code="message.badCredentials"> </spring:message> </div> </c:if>

Notice that we are using a <spring:message …> element. This means that the error messages are generated during the Spring MVC processing.

The full login page – including the js validation and these additional status messages can be found in the github project.

4.4. Logout Errors

In the example that follows, the jsp code <c:if test=”${not empty SPRING_SECURITY_LAST_EXCEPTION}”> in the logout.html page will check if there was an error in the logout process.

For example – if there is a persistence exception when a custom logout handler tries to store user data before redirecting to the logout page. While these errors are rare, we should handle them as neatly as possible as well.

Lets take a look at the complete logout.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <%@taglib uri="http://www.springframework.org/tags" prefix="spring"%> <c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}"> <div id="error"> <spring:message code="message.logoutError"> </spring:message> </div> </c:if> <c:if test="${param.logSucc == true}"> <div id="success"> <spring:message code="message.logoutSucc"> </spring:message> </div> </c:if> <html> <head> <title>Logged Out</title> </head> <body> <a href="login.html">Login</a> </body> </html>

Notice that the logout page also reads the query string param logSucc, and if its value equal to true, a localized success message will be displayed.

5. The Spring Security Configuration

The focus of this article is the frontend of the login process, not the backend – so we'll look only briefly at the main points of the security configuration; for the full config, you should read the previous article.

5.1. Redirecting to the Login Error URL

The following directive in the <form-login…/> element directs the flow of the application to the url where the login error will be handled:

authentication-failure-url="/login.html?error=true"

5.2. The Logout Success Redirect

<logout invalidate-session="false" logout-success-url="/logout.html?logSucc=true" delete-cookies="JSESSIONID" />

The logout-success-url attribute simply redirects to the logout page with a parameter that confirms that the logout was successful.

6. Conclusion

In this article we have illustrated how to implement a Login page for a Spring Security backed application – handling login validation, displaying authentication errors and message localization.

We're going to be looking at a full registration implementation in the next article – with the goal of having a full implementation of the login and registration process ready for production.