How to create login page using Java?

Recently I have been given an assignment to create a simple page that demonstrate the following features:

  • UI can be done in any language but backend must be done in Java
  • Login page requires userid and password
  • Upon successful login, the userid and user role should be shown (USER/MANAGER)
  • If login is unsuccessful, user should stay at login page
  • The application should demonstrate MVC pattern
  • The application should support 2 languages

Spring Initializr

I have chosen spring boot starter project as everything is pre-configured for me. Head on to https://start.spring.io/ and configure as shown below:

The function of each dependency is basically self explanatory as shown in the snapshot. Click generate the project.

Import existing Maven project

Launch your Eclipse IDE for Java EE developer. You will certainly need Java EE for this project.

You can get the installer here 👇https://www.eclipse.org/downloads/packages/release/kepler/sr2/eclipse-ide-java-ee-developers

At your project explorer, right click and import existing Maven project.

Import the project folder that has been downloaded from Spring Initializr.

Open up your pom.xml which contains all your dependencies. Click save to start downloading Maven dependencies and it will shown in /projectname/Maven Dependencies

Model – User.java

In the User.java, we are going to have for each user, an id, username, password and roles. You can easily set up constructors using field and both getters and setters by right click > source. Our User class should be marked with @Entity that allows us to map the class directly to table in database. The field Id is marked with @Id and @GeneratedValue to denote it should be the primary key of the table that will be auto generated.

package com.davidcheah.demo.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.validation.constraints.NotNull;

@Entity
@NamedQuery(name = "find_all_users", query = "select u from User u")
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;

	@NotNull
	@Column(nullable = false)
	private String username;

	@NotNull
	@Column(nullable = false)
	private String password;

	private String roles;

	public String getRoles() {
		return roles;
	}

	public void setRoles(String roles) {
		this.roles = roles;
	}

	public User() {
		super();
	}

	public User(String name, String password, String roles) {
		super();
		this.username = name;
		this.password = password;
		this.roles = roles;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return username;
	}

	public void setName(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", password=" + password + ", roles=" + roles + "]";
	}

	public List<String> getRoleList() {
		if (this.roles.length() > 0) {
			return Arrays.asList(this.roles.split(","));
		}
		return new ArrayList<>();
	}

}

Controller – LoginController.java

The controller is the one handling all the Http requests and responds appropriately. When user tries to access url “/login”, the correct view shall be displayed. If the user has already logged in, then the welcome page will be shown, else they will be redirect to login page. This is managed by session management. When a session is established, the jsessionid which is unique to every session is stored in the cookie of the browser. The server checks in the memory and load the correct session attributes based on the jessionid stored in the client cookie. Logout invalidates the user session and redirect to login page. Also in this controller, there is the translation of string according to the locale set in the url. This will be explain further below.

Note: You should always check if the variable in the session is not null instead of the session itself. If no previous session is available, then a new one will be created. Hence checking if session is null is pointless.

package com.davidcheah.demo.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.davidcheah.demo.model.User;
import com.davidcheah.demo.service.LocaleService;
import com.davidcheah.demo.service.UserService;

@Controller
public class LoginController {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	UserService userService;

	@Autowired
	LocaleService localeService;

	@GetMapping("/")
	public String handleAllRequest() {
		return "redirect:/login?lang=en";
	}

	@GetMapping("/login")
	public String showLoginPage(ModelMap model, HttpServletRequest request, @RequestParam(name = "lang") String lang) {
		if (lang != null) {
			localeService.setLocale(lang);
		}

		model = getTranslatedModel(model);

		HttpSession session = request.getSession();
		if (session.getAttribute("username") != null) {

			return "welcome";
		}
		return "login";
	}

	@PostMapping("/login")
	public String handleLogin(ModelMap model, @RequestParam String username, @RequestParam String password,
			HttpServletRequest request) {

		model = getTranslatedModel(model);

		logger.info("[handleLogin] username: " + username + " and password: " + password);
		// Get user from database
		User foundUser = userService.validateUser(username, password);
		if (foundUser != null) {

			// If user found, create new session
			HttpSession session = request.getSession();
			session.setAttribute("username", foundUser.getName());
			session.setAttribute("roles", foundUser.getRoleList());

			return "welcome";
		}
		model.put("errorMessage", localeService.getTranslatedString("login_invalid"));

		return "login";
	}

	@RequestMapping(value = "/logout", method = RequestMethod.GET)
	public String logout(HttpServletRequest request, HttpServletResponse response) {
		request.getSession().invalidate();

		return "redirect:/";
	}

	private ModelMap getTranslatedModel(ModelMap model) {
		model.put("welcome_name", localeService.getTranslatedString("welcome_name"));
		model.put("welcome_role", localeService.getTranslatedString("welcome_role"));
		model.put("welcome_logout", localeService.getTranslatedString("welcome_logout"));

		model.put("login_signin", localeService.getTranslatedString("login_signin"));
		model.put("login_username", localeService.getTranslatedString("login_username"));
		model.put("login_password", localeService.getTranslatedString("login_password"));
		model.put("login_submit", localeService.getTranslatedString("login_submit"));

		return model;

	}
}

Note: The session management in above uses cookies. In any case the user disable cookies in their browser, one option is to use URL redirection. In this case, the user jsessionid is appended to the url which allows our web application to load the user saved attributes.

String redirectURL = "/login";
redirectURL = response.encodeURL(redirectURL);
response.sendRedirect(redirectURL);

As you can see in the code, all the methods in controller returns a string which is the name of the jsp. We need to add the following in application.properties to tell the application to return the respective jsp files from the string

application.properties

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

Views

All views (in jsp) are stored in path /demo/src/main/webapp/WEB-INF/views. There is no hardcode string as it is replaced by variables. This is due to language localization.

Note: Tomcat-embedded does not recognize JSP scriplets. Hence we need to add the following dependency in pom.xml

<dependency>
	<groupId>org.apache.tomcat.embed</groupId>
	<artifactId>tomcat-embed-jasper</artifactId>
	<scope>provided</scope>
</dependency>

In the welcome.jsp, there is a small portion of scriptlet code that checks if the user session is available and their username is valid. If the session has already expires, then user will be redirected to login.

login.jsp

<%@ include file="common/header.jspf"%>
<body>
	<a href="/login?lang=en" role="button">English</a>
	<a href="/login?lang=fr" role="button">French</a>
	<div class="container">
		<form class="text-center border border-light p-5" action="/login"
			method="post">

			<p class="h4 mb-4">
			
			<h3>${login_signin}</h3>
			</p>

			<!-- Email -->
			<Fieldset>
				<input type="text" name="username" class="form-control mb-4"
					placeholder="${login_username}" required="required">
			</Fieldset>
			</br>
			<!-- Password -->
			<FieldSet>
				<input type="password" name="password" class="form-control mb-4"
					placeholder="${login_password}" required="required">
			</FieldSet>
			</br>

			<!-- Sign in button -->
			<button class="btn btn-info btn-block my-4" type="submit">${login_submit}
			</button>


			<h3>
				<font color="red">${errorMessage}</font>
			</h3>

		</form>
		<script src="webjars/jquery/1.9.1/jquery.min.js"></script>
		<script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>
	</div>
	<%@ include file="common/footer.jspf"%>

welcome.jsp

<%@ include file="common/header.jspf" %>
	<a href="/login?lang=en" role="button">English</a>
	<a href="/login?lang=fr" role="button">French</a>
	<div class="container">
		<%
			session = request.getSession(false);
			if (session.getAttribute("username") == null && session.getAttribute("roles") == null) {
				response.sendRedirect("login.jsp");
			}
		%>
		<h3>
			${welcome_name}:
			<%
				out.print(session.getAttribute("username"));
			%>
		</h3>
		<h3>
			${welcome_role}:
			<%
				String roles = session.getAttribute("roles").toString();
				out.print(roles.substring(1, roles.length() - 1));
			%>
		</h3>

		<a class="btn btn-danger" href="/logout" role="button">${welcome_logout}</a>

		<script src="webjars/jquery/1.9.1/jquery.min.js"></script>
		<script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>
	</div>
<%@ include file="common/footer.jspf" %>

Bootstrap is being used to further improve our frontend. To add bootstrap in pom.xml, use the following:

<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>bootstrap</artifactId>
	<version>3.3.6</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>jquery</artifactId>
	<version>1.9.1</version>
</dependency>

To be able to use bootstrap, we need to add the following inside the <head> tag

<link href="webjars/bootstrap/3.3.6/css/bootstrap.min.css"
	rel="stylesheet">

Also the following just before the </body> tag.

<script src="webjars/jquery/1.9.1/jquery.min.js"></script>
<script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>

Data Access Object (DAO)

DAO basically is an interface that provides access to the database. In User.dao, we created methods to execute custom queries to retrive information such as a list of users, add new users and find user by username.

UserDao.java

package com.davidcheah.demo.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;

import org.springframework.stereotype.Repository;

import com.davidcheah.demo.model.User;

@Repository
@Transactional
public class UserDao {

	@PersistenceContext
	EntityManager entityManager;
	
	String findUserByNameQuery = "from User WHERE username=?1";

	public User findById(int id) {
		return entityManager.find(User.class, id);
	}

	public List<User> findAll() {
		TypedQuery<User> namedQuery = entityManager.createNamedQuery("find_all_users", User.class);
		return namedQuery.getResultList();
	}

	public User findByUsername(String username) {
		try {
			TypedQuery<User> tq = entityManager.createQuery(findUserByNameQuery, User.class);
			User user = tq.setParameter(1, username).getSingleResult();
			return user;
		} catch (NoResultException noresult) {
			// if there is no result
		} catch (NonUniqueResultException notUnique) {
			// if more than one result
		}

		return null;
	}
	
	public User insert(User user) {
		return entityManager.merge(user);
	}

}

Services

All service classes are marked with @Service that allows us to inject them whenever we need them rather than instantiating ourselves. UserService basically exposes the interface to the database and also validates the user password. User password is stored in database as hash value. To do this, we use BCrypt from spring boot security.

UserService.java

package com.davidcheah.demo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.ui.ModelMap;

import com.davidcheah.demo.dao.UserDao;
import com.davidcheah.demo.model.User;

@Service
public class UserService {
	@Autowired
	UserDao userDao;

	@Autowired
	PasswordService pwService;

	public User validateUser(String username, String password) {
		User user = userDao.findByUsername(username);
		System.out.println(user);
		if (user != null) {
			if (pwService.matches(password, user.getPassword())) {
				return user;
			}
		}
		return null;
	}

	public User findById(int id) {
		return userDao.findById(id);
	}

	public List<User> findAll() {
		return userDao.findAll();
	}

	public User findByUsername(String username) {
		return userDao.findByUsername(username);
	}

	public User insert(User user) {
		return userDao.insert(user);
	}

}

In PasswordService.java allows us to use BCrypt to hash a password and check if the passwords are matching.

package com.davidcheah.demo.service;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class PasswordService {
	BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	
	public String encodes(String password) {
		return passwordEncoder.encode(password);
	}
	
	public boolean matches(String password, String hashedPassword) {
		return passwordEncoder.matches(password, hashedPassword);
	}
}

In LocaleService.java, allows us to set the locale either to English or French. Then the translated string is extracted from messages_en.properties or messages_fr.properties for both English and French based on the locale we just set.

package com.davidcheah.demo.service;

import java.util.Locale;
import java.util.ResourceBundle;

import org.springframework.stereotype.Service;

@Service
public class LocaleService {

	private Locale locale;

	public Locale getLocale() {
		return locale;
	}

	public void setLocale(String lang) {
		switch (lang) {
		case "en":
			this.locale = getEnglishLocale();
			break;
		case "fr":
			this.locale = getFrenchLocale();
			break;
		default:
			break;
		}
	}

	public LocaleService() {
		super();
		this.locale = getEnglishLocale();
	}

	public String getTranslatedString(String field) {
		ResourceBundle resource = ResourceBundle.getBundle("message", locale);
		return resource.getString(field);
	}

	public Locale getEnglishLocale() {
		String lang = "en";
		String country = "US";

		return new Locale(lang, country);
	}

	public Locale getFrenchLocale() {
		String lang = "fr";
		String country = "FR";

		return new Locale(lang, country);
	}
}

H2 Database

H2 is an inmemory database that comes with spring boot. We can access to this database using http://localhost:8080/h2-console. But first we need to enable console property in our /demo/src/main/resources/application.properties

spring.h2.console.enabled=false

We can also show the sql query log in the console with the following

spring.jpa.show-sql=true

We can create a data.sql in the same path as application.properties. What this file does is, the application will execute the query that is contained in it. This is only application to h2 database. So let’s put our table creation in the file.

data.sql

create table user
(
  id long not null,
  username varchar(255) not null,
  password varchar(255),
  roles varchar(255),
  primary key (id)
);

In DemoApplication.java, we implement CommandLineRunner which allows us to use the run method. We then use this run method to populate our database with several users.

DemoApplication.java

package com.davidcheah.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.davidcheah.demo.model.User;
import com.davidcheah.demo.service.PasswordService;
import com.davidcheah.demo.service.UserService;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private UserService userService;

	@Autowired
	private PasswordService pwService;

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		//populateDb();
		//localeTest();
	}

	private void populateDb() {
		User user1 = new User("user1", "user1", "USER");
		User user2 = new User("user2", "user2", "USER,MANAGER");
		User user3 = new User("user3", "user3", "MANAGER");

		saveUser(user1);
		saveUser(user2);
		saveUser(user3);
	}

	private void saveUser(User user) {
		String password = user.getPassword();
		String hashedPassword = pwService.encodes(password);
		user.setPassword(hashedPassword);

		logger.info("Inserting" + user.getId() + " -> {}", userService.insert(user));

		// validateUser(user, password);
	}
	
//	private void localeTest() {
//		String lang = "en";
//		String country = "US";
//		
//		Locale locale = new Locale(lang, country);
//		
//		ResourceBundle r = ResourceBundle.getBundle("message", locale);
//		
//		String str = r.getString("hello");
//		System.out.println(str);
//		
//	}

//	private void validateUser(User user, String password) {
//		if (pwService.matches(password, user.getPassword())) {
//			System.out.println("Match password");
//		}
//	}

}

MySQL Database

Ensure you have MySQL and workbench installed. To switch from H2 to MySQL database only takes a couple of steps. We need to add the mysql connection as new dependency.

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.14</version>
</dependency>

Next we need to remove data.sql and h2 dependency from pom.xml.

In the application.properties, we need to add some configuration specific to mysql

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

We can create our table in the Mysql workbench.

create table user
(
   id bigint not null,
   username varchar(255) not null,
   password varchar(255),
   roles varchar(255),
   primary key(id)
);

Result

Github Repository

Please use this link to get the source code:

https://github.com/tattwei46/simpleloginforminjava

About the author

Founder of tattweicheah.com. Loves music, sport and most importantly software development.

Leave a Reply

Your email address will not be published. Required fields are marked *