Spring Web MVC framework provides a interceptor mechanism useful when you want to apply specific functionality to certain requests, for example, checking for the user authentication. The basis of this mechanism is the HandlerInterceptor interface. This object that replaces the traditional J2EE Servlet Filter, fits in the Handler life cycle and it is able to perform operations in a totally independent and decoupled way. HandlerInterceptor defines three methods allowing to apply specific operations in the chain-execution of the Handler:
– preHandle: called before the actual handler is executed
– postHandle: called after the handler is executed
– afterCompletion: called after the complete request has finished
In this post I show how to checking the user authentication using the HandlerInterceptor. This example can be useful to understand the interceptor mechanism in the Spring MVC chain-execution. In order to provide a high level view, I sketched some sequence diagrams showing the main use cases about the authentication mechanism. I only show a most basic implementation of the authentication checking. If you are looking for a more secure and reliable solutions about the authentication and access-control process you could take a look at the Spring Security.
ROADMAP
STEP 1. Spring MVC Configuration
STEP 2. Login Components Configuration
STEP 3. Interceptor Configuration
STEP 1. Spring MVC Configuration
At first I create a Spring MVC skeleton using Maven then I import the project in the Eclipse IDE, also I enable the “Project Facets” > “Dynamic Web Module” setting. For the view component I also add some web resources like css, images and javascript libraries .
$ mvn archetype:generate -DgroupId=eu.giuseppeurso.sampleintercep -DartifactId=sample-intercep -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
I define the Spring MVC Servlet Dispatcher and a initial view resolver, here is an excerpt.
<!-- web.xml --> <servlet> <servlet-name>spring-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/spring-dispatcher.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<!-- spring-dispatcher.xml --> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <context:component-scan base-package="eu.giuseppeurso.sampleinterc" /> <!-- Enables the Spring MVC @Controller programming model --> <annotation-driven /> <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/* directory --> <resources mapping="/css/**" location="/css/" /> <resources mapping="/js/**" location="/js/" /> <resources mapping="/images/**" location="/images/" /> <resources mapping="/jquery-ui-1.9.2/**" location="/jquery-ui-1.9.2/" /> <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean>
I create the Spring MVC Controller instance containing a simple RequestMapping for the welcome.jsp.
//SampleController.java @Controller public class SampleController { private static final Logger logger = LoggerFactory.getLogger(SampleController.class); @RequestMapping(value = "/welcome", method = RequestMethod.GET) public String welcome() { return "welcome"; } }
<!-- welcome.jsp --> <div> <h1>WELCOME! Spring MVC Interceptor Sample</h1> </div>
STEP 2. Login Components Configuration
I update the Controller to mapping the web application context-root to a login form then I create a jsp page to submit the user credentials. Pay attention to the modelAttribute in the form:form tag, it must hold the same value defined in the Controller (“loginAttribute“).
// SampleController.java @RequestMapping(value = "/", method = RequestMethod.GET) public String showLogin(Model model, LoginForm loginForm) { logger.info("Login page"); model.addAttribute("loginAttribute", loginForm); return "login"; }
// LoginForm.java public class LoginForm { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
<!-- login.jsp --> <form:form action="login" method="post" modelAttribute="loginAttribute"> <table border="0" cellpadding="0" cellspacing="0"> <tr> <th>Username</th> <td><form:input type="text" class="login-inp" path="username" /></td> </tr> <tr> <th>Password</th> <td><form:input type="password" value="************" onfocus="this.value=''" class="login-inp" path="password"/></td> </tr> <tr> <th></th> <td valign="top"><input type="checkbox" class="checkbox-size" id="login-check" /><label for="login-check">Remember me</label></td> </tr> <tr> <th></th> <td><input type="submit" class="submit-login" /></td> </tr> </table> </form:form>
STEP 3. Interceptor Configuration
Finally I define in the spring-dispatcher.xml file the HandlerInterceptor implementation. In order to manage the user authentication checking, I override the preHandle method. I use the LOGGEDIN_USER attribute to check the authenticated users in session. To avoid a mistaken redirect loop I exclude the root web context and the login event urls (see above sequence diagram).
<!-- spring-dispatcher.xml--> <interceptors> <interceptor> <mapping path="/*"/> <beans:bean class="eu.giuseppeurso.sampleinterc.interceptor.AuthenticationInterceptor" /> </interceptor> </interceptors>
// AuthenticationInterceptor.java @Override public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception { log.info("Interceptor: Pre-handle"); // Avoid a redirect loop for some urls if( !request.getRequestURI().equals("/sample-interc/") && !request.getRequestURI().equals("/sample-interc/login.do") && !request.getRequestURI().equals("/sample-interc/login.failed")) { LoginForm userData = (LoginForm) request.getSession().getAttribute("LOGGEDIN_USER"); if(userData == null) { response.sendRedirect("/sample-interc/"); return false; } } return true;
Furthermore I must update the Controller adding the login events RequestMapping. The POST method checks the user credentials submitted in the login form. To simulate a Persistence Data Layer I create a simply text file containing the user credential values. To access data I use the java.util ResourceBundle. When Tomcat starts, the servlet container initializes the Request Mapping Handlers, you can see the bottom log messages. When I request the root web context url for the first time, the AuthenticationInterceptor checks the unauthenticaed user then it shows the login page.
//SampleController.java @RequestMapping(value = "/login.do", method = RequestMethod.POST) public String login(Model model, LoginForm loginform, Locale locale, HttpServletRequest request) throws Exception { String username = loginform.getUsername(); String password = loginform.getPassword(); // A simple authentication manager if(username != null && password != null){ if( username.equals(settings.getString("username")) && password.equals(settings.getObject("password")) ){ //return "welcome"; request.getSession().setAttribute("LOGGEDIN_USER", loginform); return "redirect:/welcome"; }else{ return "redirect:/login.failed"; } }else{ return "redirect:/login.failed"; } }
In conclusion I’ve only demonstrated the most basic implementation of Handler Interceptor for authetication checking: if we try to access any other URLs without logging into the application it will automatically redirect to login page, contrariwise it will give a response with a welcome page.
To avoid yours tests with request.getRequestURI(), you can define in mvc-dispatcher 🙂
Hi Melkior,
Thanks for tip.
I will try to execute some tests redefining the mvc-dispatcher.
😉
Giuseppe
Hi
Missing mvc:exclude-mapping path=”/sample-interc/**” (with > and <) in my comment to make sense