Sunday, September 11, 2022

Spring Security With Custom Login Page Using JAVA Configuration

JAVA,Dispatcher Servlet,Spring Framework,Spring Filter,Spring Security,programming,software development,technology,spring boot
In our previous tutorial, we've learned to configure and implement basic Spring Security to a Spring MVC application and we've seen that application automatically redirected to the login form provided by Spring Security. In this tutorial, we'll learn how to show our custom login form in place of Spring Security's provided login form and also implement a logout facility.

So to implement login, first we've to create a new controller and inside that controller, create a GET method that will return the custom login page name; then bind the controller method in the Spring Security configuration so that Spring Security will automatically call our custom login page; and finally, create a JSP page to design our login form using HTML and CSS.

To implement logout, we'll add a logout button on the home page, do some configuration, and show a logout status message on the login page.

In this tutorial, we'll use our previous tutorial code base. You can download it from here.

POM.XML

Here is our pom.xml of the maven project for this tutorial:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.raven</groupId>
	<artifactId>securespringmvccustomlogin</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	<name>secure-spring-mvc-custom-login</name>
	<packaging>war</packaging>

	<properties>
		<springframework.version>5.3.21</springframework.version>
		<springsecurity.version>5.6.6</springsecurity.version>

		<maven.compiler.source>11</maven.compiler.source>
		<maven.compiler.target>11</maven.compiler.target>
	</properties>

	<dependencies>
		<!-- Spring MVC support -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${springframework.version}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${springsecurity.version}</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${springsecurity.version}</version>
		</dependency>

		<!-- Servlet support -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>4.0.1</version>
		</dependency>

		<!-- JSP support -->
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>2.3.3</version>
		</dependency>

		<!-- JSTL support -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<dependency>
			<groupId>taglibs</groupId>
			<artifactId>standard</artifactId>
			<version>1.1.2</version>
		</dependency>

	</dependencies>

	<build>
		<finalName>secure-spring-mvc-custom-login</finalName>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-war-plugin</artifactId>
					<version>3.3.2</version>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>

</project>

Controller

We'll create a CustomLoginController class controller package. Here it is:
package com.raven.securespringmvccustomlogin.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class CustomLoginController {

	@GetMapping("/showCustomLoginPage")
	public String showCustomLoginPage() {
		return "customLoginForm";
	}
}
Our showCustomLoginPage() method will return customLoginForm, and based on our configuration, view resolver will search for customLoginForm.jsp (as we are using JSP as our view technology) in /WEB-INF/view/. So we need to create customLoginForm.jsp in /WEB-INF/view/.

Configuration

We have a configuration class named ApplicationSecutiryConfiguration - here we had configured a user for in-memory authentication by overriding configure(AuthenticationManagerBuilder auth) method. Now, we need to override the configure(HttpSecurity http) method to configure HttpSecurity for our application:
package com.raven.securespringmvccustomlogin.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.User.UserBuilder;

@Configuration
@EnableWebSecurity
public class ApplicationSecutiryConfiguration extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		UserBuilder userBuilder = User.withDefaultPasswordEncoder();
		auth.inMemoryAuthentication()
				.withUser(userBuilder.username("admin")
				.password("admin123")
				.roles("ADMIN"));
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.anyRequest()
			.authenticated()
			.and()
			.formLogin()
			.loginPage("/showCustomLoginPage")
			.loginProcessingUrl("/authenticateTheUser").permitAll()
			.and()
			.logout().permitAll();
	}
}
But why do we need  HttpSecurity? Let's see what Spring documentation says:
"A HttpSecurity is similar to Spring Security's XML <http> 
  element in the namespace configuration. It allows configuring web based 
  security for specific http requests. By default it will be applied to 
  all requests..." -- spring-security-docs
So, HttpSecurity is used to secure a specific web path or URL. In other words, we are just defining our security policies/rules using HttpSecurity for some specific URL.

In the configure(HttpSecurity http) method, we have specified that any request coming to this application should be authenticated, and as we are using form-based authentication (formlogin()), that is why we have mapped our custom login page to allow users to provide their credentials. After the request is submitted, the request will be processed by /authenticateTheUser. Now Spring Security would check the user credentials that we had submitted.

We've also configured a logout facility using .and().logout().permitAll(). So when a user clicks the logout button, the request will go to the logout URL: /logout. This is actually the default URL for logging out. This logout URL will be managed by the Spring Security Filters. So Spring Security would invalidate a user's HTTP session and remove session cookies, redirect the user to a login page, and add a logout parameter: ?logout with the base URL.

View

Now create a customLoginForm.jsp in the view directory in /WEB-INF/:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Log In - Secure Spring MVC</title>
<%-- <link rel="stylesheet"
	href="${pageContext.request.contextPath}/css/style.css"> --%>
<style>
@import
	url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap')
	;

html, body {
	height: 100%;
}
body {
	font-family: 'Roboto', sans-serif;
	background-image: linear-gradient(to top, #7028e4 0%, #e5b2ca 100%);
}
.demo-container {
	height: 100%;
	display: flex;
	justify-content: center;
	align-items: center;
}
.btn-lg {
	padding: 12px 26px;
	font-size: 14px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
}
::placeholder {
	font-size: 14px;
	letter-spacing: 0.5px;
}
.form-control-lg {
	font-size: 16px;
	padding: 25px 20px;
}
.font-500 {
	font-weight: 500;
}
</style>
<link rel="stylesheet"
	href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.css" />
<link rel="stylesheet"
	href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.css" />
<script src="https://code.jquery.com/jquery-3.4.1.slim.js"></script>
<script
	src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.js"></script>
<script
	src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.js"></script>
</head>
<body>
	<div class="demo-container">
		<div class="container">
			<div class="row">
				<div class="col-lg-6 col-12 mx-auto">
					<div class="p-5 bg-white rounded shadow-lg">
						<h3 class="mb-2 text-center">Log In</h3>
						<p class="text-center lead">Log In to manage your account</p>
						<form:form
							action="${pageContext.request.contextPath}/authenticateTheUser"
							method="POST">
							<label class="font-500">User name</label>
							<input name="username" placeholder="enter username"
								class="form-control form-control-lg mb-3" type="text">

							<label class="font-500">Password</label>
							<input name="password" placeholder="enter password"
								class="form-control form-control-lg" type="password">

							<div style="margin-top: 20px; margin-top: 20px">
								<button type="submit"
									class="btn btn-primary btn-lg w-100 shadow-lg">LOG IN</button>
							</div>

							<div style="margin-top: 20px; margin-top: 20px">
								<!-- ERROR MESSAGE -->
								<c:if test="${param.error != null}">
									<div class="alert alert-danger col-xs-offset-1 col-xs-10">
										Invalid username and password!</div>
								</c:if>

								<!-- LOGOUT MESSAGE -->
								<c:if test="${param.logout != null}">
									<div class="alert alert-success col-xs-offset-1 col-xs-10">
										You've been successfully logged out!</div>
								</c:if>
							</div>
						</form:form>
					</div>
				</div>
			</div>
		</div>
	</div>

</body>
</html>
OK, this is our custom login page. We've used the Bootstrap framework and some custom CSS to design this page. Along with these we've also used the Spring MVC form tag to POST user credentials. We've mapped /authenticateTheUser with the action value of the form tag - so that the Spring framework will do the rest of the job for us.

We are also checking the error and logout status using JSTL and showing the message to the user.

Now we'll update home.jsp to show the logout button and implement logout functionality. Here it is:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Home - Spring Security</title>
<style>
body {
	font-family: Arial, Helvetica, sans-serif;
	margin: 0;
}
.header {
	padding: 60px;
	text-align: center;
	background: #1abc9c;
	color: white;
	font-size: 30px;
}
.content {
	padding: 20px;
}
</style>
</head>
<body>
	<div class="header">
		<h1>Spring Security</h1>
		<p>Welcome to Spring Security with custom Login	page!</p>
	</div>

	<div class="content">
		<h1>Home</h1>
		<p>In this tutorial, we'll learn how to show our custom login form
			in-place of Spring Security provided login form and also implement
			logout facility.</p>
		<p>
			<form:form action="${pageContext.request.contextPath}/logout"
				method="POST">
				<input type="submit" value="LOGOUT">
			</form:form>
		</p>
	</div>
</body>
</html>
So this page is the same as our previous tutorial. We've just added a logout button. As we've discussed earlier, we a user presses the logout button, the request is submitted to the logout URL: /logout and then the request is processed by Spring Security itself.

Testing

Now run this application again and put this URL - http://localhost:8080/ssecure-spring-mvc-custom-login/ in the browser:
Spring Security using JAVA configuration in Spring - Custom Login Form

We can see that the application automatically redirected to our custom login page. Now enter admin as username and admin123 as password and press LOG IN to submit the page and the request will be redirected to the home page:
Spring Security using JAVA configuration in Spring - Custom Login Form

Here, on the home page, we can see the logout button and you can press the logout button to check the logout functionality.
Spring Security using JAVA configuration in Spring - Custom Login Form

As we've logged out from the application, a logout status message is given to the user.

So in this tutorial, we've learned how to configure our custom login page in place of Spring Security's in-built login page and logout functionality.

You can download the source code from here.
Happy coding!!! 😊
in

No comments:

Post a Comment

Popular posts