Requirement

  • Create a Login page to enter user name and password
  • On submit, validate the user name / password against MySQL database
  • If the authentication is successful, forward to home page showing welcome message along with the user name
  • If the authentication fails, return back to the login page with appropriate error message
  • If there is exception / errors during authentication process return back to login page with appropriate error message.

 

Pre-requisites

  • MySQL and Tomcat is already installed
  • MySQL is setup with database and tables for use. Refer Simple Login Application for details
  • You have basic knowledge of Java and J2EE web applications
  • Basic idea about Struts 1 or 2 or at least about MVC design

 

Concepts Covered

  • IDE set up for Struts 2
  • Struts2 key components
  • Difference between struts 1 and struts 2 highlighted

 

Environment Setup

  • Download Eclipse for Java EE which is all ready to go with JEE features and install it. Download from Eclipse for JEE Download
  • Create a "Dynamic Web Project" in eclipse with the name Struts2LoginApp.
    Click next, then next and finish.
  • Download latest Struts2 binaries from Struts 2 Binaries Download. At the time of writing this article, struts 2.3.24 was used.
  • Unzip the binaries and copy the following JAR files to the lib directory(<workspace>\Struts2LoginApp\WebContent\WEB-INF\lib) of the newly created project:

commons-fileupload-1.3.1.jar
commons-io-2.2.jar
commons-lang3-3.2.jar
commons-logging-1.1.3.jar
commons-logging-api-1.1.jar
freemarker-2.3.22.jar
javassist-3.11.0.GA.jar
ognl-3.0.6.jar
struts2-core-2.3.24.jar
xwork-core-2.3.24.jar


Since we make connections to MySQL using JDBC, we would also need to put the JAR for the MySQL JDBC driver (mysql-connector-java-5.1.20-bin.jar). You can download the latest version of the jar from MySQL JDBC Driver Download.


web.xml configuration:
A blank web.xml might have been added to your project under WEB-INF folder. This file is the JSP deployment descriptor. We need to configure the struts 2 controller, which is a servlet filter by the name FilterDispatcher. If you are familiar with Struts1, you will recollect that in the web.xml, we used to make an entry for the ActionServlet which was the controller in struts1.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee" 
   xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
   http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID" version="3.0">
   
   <display-name>Struts 2 Login App</display-name>
   <welcome-file-list>
      <welcome-file>Login.jsp</welcome-file>
   </welcome-file-list>
   <filter>
      <filter-name>struts2</filter-name>
      <filter-class>
         org.apache.struts2.dispatcher.FilterDispatcher
      </filter-class>
   </filter>

   <filter-mapping>
      <filter-name>struts2</filter-name>
      <url-pattern>/*</url-pattern>
   </filter-mapping>
</web-app>

 

Analysis and Design

If you do not have the database table created or do not have any user in that table, please refer the analysis and design from the article Simple Login Application for details.

Taking inspiration from the previous article, we will create similar components for our simple login app using struts 2.

So, below are the identified components:

Login.jsp
Home.jsp
LogonAction.java

Let us look at each identified component and its role in our application:

  • Login.jsp -> It would present the login screen to enter user id and password. It also displays any validation errors and login failure message.
  • Home.jsp -> Simple JSP which displays a welcome message after successful login
  • com.tech_freaks.action.LogonAction.java 
    While we take inspiration from LogonServlet of our previous example, we improve the design to not include any database connectivity code in our Action class. To achieve this, we will refactor all the DB access / calls to a Helper class by the name DBHelper.java. This also helps in modifying the code, if we tomorrow decide to use Entity Beans (EJB) or hibernate to access the database instead of raw JDBC code with minimum impact on our Action class. The LogonAction performs the following tasks.
    - Perform basic empty check on server side before calling the database
    - Call our DB Helper class, with user name and password to check, if the combination exists.
  • com.tech_freaks.utils.DBHelper.java
    It will perform all task related to getting connection to the DB, executing a query and method to return Boolean indicating, if record was available or not in the DB. We do not write method like, isUserPasswordValid in this class, as it is a generic low level class, and should not be aware about the logic implemented in the classes calling it. Below would be the methods:

public boolean containsResults(String query, String[] params) -> The params we have created as a String array, which will have both user name and password. If the values required for the where clause of the SQL query are of different datatype, we might need a different approach. This method is invoked by the LogonAction

private Connection getConnection() -> Internally used to get a Connection object for the DB call.

private void closeConnection(Connection conn) - Closes an open connection after the query has been executed.

 

Development

We will first start by adding a struts.xml for our project. In eclipse, you will have to create a folder 'classes' under \workspace\Struts2LoginApp\WebContent\WEB-INF\ and add struts.xml in that folder.

Below is the struts.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
   "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
   "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.devMode" value="true" />
   <package name="loginapp" extends="struts-default">
     
      <action name="logon" 
            class="com.tech_freaks.action.LogonAction" 
            method="execute">
            <result name="success">/Home.jsp</result>
            <result name="input">/Login.jsp</result>
            <result name="error">/Login.jsp</result>
      </action>
   </package>
</struts>

 The configuration file is straightforward and easy to understand. <constant> is setting the project in a development mode. This will give helpful errors in the logs / screen while debugging issues.

<package> is applicable when we divide the configure into multiple logical parts instead of having all action definition inside single file.
Let us take a careful look at the entry for <action> and ensure all mapping components are named to match the entries in here.

€¢ €œname€ attribute needs to be mapped to the <form action=€€> in the Login.jsp. This is how struts knows which Action class to invoke. We leave the method to €œexecute€ as our Action class will extend ActionSupport class and override the execute method.
€¢ €œclass€ should be fully qualified class name of the Action class.
€¢ When the Action executes, we can configure different JSPs / views to invoke after completion. The String value returned from Action.execute() method should match the €œname€ attribute in one of the €œresult€. However, it is important to note that, if we are using struts validation framework, we need to have an <result> with name=€input€. The framework will redirect to the view configured for <result> name="input" with the error message. If you do not have entry for €œinput€ you will see an error saying that €œNo result defined for action and result input - action €“€œ

Next, we take a look at the LogonAction.java.
As we plan to validate for empty fields, we need to extend the ActionSupport class. Once extended, we need to override the methods execute() and validate(). The next thing is we need to have instance variables for user fields in the Login.jsp. Note that the instance variable (mainly the getter/ setters for the instance variables) must match exactly to the €œname€ attribute on the <input> fields on the JSP. This is also a difference between struts 1.1 were we had separate ActionForms which had the fields mapped to the <input>. 
The complete java code is below

 package com.tech_freaks.action;

import java.util.HashMap;
import java.util.Map;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
import com.tech_freaks.utils.DBHelper;

public class LogonAction extends ActionSupport {
	
	public static final String LOGIN_SUCCESS = "success";
	public static final String LOGIN_FAILED = "error";
	
	private static final String LOGIN_QUERY = "select * from users where user_name=? and password=?";
	
	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;
	}

	@Override
	public String execute() throws Exception {
		ValueStack stack = ActionContext.getContext().getValueStack();
		String[] params = new String[] {userName, password};
		DBHelper dbHelper = new DBHelper();
		
		boolean isSuccess = dbHelper.containsResult(LOGIN_QUERY, params);
		if(isSuccess)
			return LOGIN_SUCCESS;
		else {
			Map<String, Object> context = new HashMap<String, Object>();
			context.put("errorMsg", new String("Invalid user name or password. Try again.")); 
			stack.push(context);
			return LOGIN_FAILED;
		}
	}

	@Override
	public void validate() {
		 if (userName == null || userName.trim().equals(""))
	      {
	         addFieldError("userName","Please enter a user name to login with.");
	      }
		 if (password == null || password.trim().equals(""))
	      {
	         addFieldError("password","Please enter a password.");
	      }
	}
}
 
 

Let"s take a close look at validate(). Use the addFieldError() method with the first parameter matching the field name which has the validation error. Again, this name needs to exactly match with <input> name attribute in the JSP. If we use the struts taglib in the JSP, it also takes care of showing the error message for each field inline to the field.

Next, we take a look at the execute() method. It is fairly straightforward. It calls the DBHelper class to verify in the database, if a record exists for the supplied userName and password combination. Based on the result, it returns €œsuccess€ or €œerror€. The value returned needs to match with the <result> name attribute defined in the struts.xml which we examined before. The appropriate view will be displayed.

For the failed login attempt, we have to go back to the login page and display an error message. We use this requirement to explain ActionContext and ValueStack concept in struts 2. The ActionContext has access to variables like session, request params etc. It also contains a value stack. You can put objects into value stack. As the name suggests, it is a stack with push, pop, peek methods in it. In the JSP, when you want to access the variables in the ActionContext using the struts taglib, you use #<attribute> say #session. However, for accessing value stack, we do not have to use the #. As you can see, we are pushing a Map with key errorMsg into the value stack, with value containing the error message which needs to be displayed to the user on failed login attempt.

Login.jsp
This page is modified from our previous version of Login.jsp. The main change is the struts taglib usage. We use the <s:property> to pull the attribute from the value stack which we previously discussed. This is used where the login attempt fails. We have used <s:form>, <s:textfield> etc. They help giving inline errors for each field as per the login in the validate() method.

Below is the complete Login.jsp 

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
   pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>
Tech-freaks.com - Struts2 - SimpleLogin App Login Page
</title>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
</head>
<body>
<s:property value="errorMsg"/><br/>
<s:form method="POST" action="logon">
	<s:textfield  name="userName" label="User Name"/>
	<s:password  name="password" label="Password" />
	<s:submit name="submit" label="Submit" align="center" />
		
</s:form>
</body>
</html>

 

Home.jsp
Simple page, which when requested due to successful login, fetches the €œusername€ field and displays the welcome message. Below is the complete JSP

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><%@page
	language="java" contentType="text/html; charset=ISO-8859-1"
	pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
	<head>
		<title>
		Tech-freaks.com - Struts 2 SimpleLogin App Home Page
		</title>
		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
	</head>
	<body>
	 	<h1> Welcome to Tech-freaks.com - Struts2 Simple Logon App, <s:property value="userName"/></h1>
	</body>
</html>

 

Testing 

Once you have all the components put in, right click the project in Eclipse and Export as WAR. Drop the generated WAR in <Tomcat>\webapps folder and start Tomcat. Verify Tomcat logs, if the deployment of the WAR was successful. 
Test by accessing the URL : http://localhost:8080/Struts2LoginApp/Login.jsp (Modify the port and context name as per your customization)
Run and verify each scenario mentioned in the requirement.


Congratulations, on completing your first "Non-Hello World" project using struts 2!


You can download the complete Eclipse project from Github by clicking the link below.

Download complete Struts 2 Login App Source code from Github

We use cookies

We use cookies on our website. Some of them are essential for the operation of the site, while others help us to improve this site and the user experience (tracking cookies). You can decide for yourself whether you want to allow cookies or not. Please note that if you reject them, you may not be able to use all the functionalities of the site.