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.
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:
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:
Here, on the home page, we can see the logout button and you can press
the logout button to check the logout functionality.
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!!! 😊
No comments:
Post a Comment