We are going to restrict access to all our web pages using Spring Security. Only an
authorised user or administrator with a valid username and password can access our web pages from a browser:
Open pom.xml; you can find pom.xml under the project root folder itself.
1.
You should see some tabs at the bottom of pom.xml; select the Dependencies tab
2. and click the add button in the Dependencies section.
A Select Dependency window will appear; enter Group Id as
3.
org.springframework.security, Artifact Id as spring-security-config, Version as 4.1.1.RELEASE, select Scope as compile, and click the OK button. Similarly, add one more dependency Group Id as
4.
org.springframework.security, Artifact Id as spring-security-web, Version as 4.1.1.RELEASE, select Scope as compile, and click the OK button. And most importantly, save pom.xml.
Now create one more controller class called LoginController under the
5.
com.packt.webstore.controller package in the src/main/java source folder. Add the following code to it:
package com.packt.webstore.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class LoginController {
@RequestMapping(value = "/login", method =
RequestMethod.GET)
public String login() {
return "login";
}
}
Add one more JSP view file called login.jsp under the
6. src/main/webapp/WEB-INF/views/ directory, add the following code snippets into it, and save it:
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form"
uri="http://www.springframework.org/tags/form"%>
Incorporating Spring Security
<%@ taglib prefix="spring"
uri="http://www.springframework.org/tags"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=ISO-8859-1">
<link rel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css
/bootstrap.min.css">
<title>Products</title>
</head>
<body>
<section>
<div class="jumbotron">
<div class="container">
<h1>Welcome to Web Store!</h1>
<p>The one and only amazing web store</p>
</div>
</div>
</section>
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Please sign in</h3>
</div>
<div class="panel-body">
<c:url var="loginUrl" value="/login" />
<form action="${loginUrl}" method="post"
class="form-horizontal">
<c:if test="${param.error != null}">
<div class="alert alert-danger">
<p>Invalid username and password.
</p>
</div>
</c:if>
<c:if test="${param.logout != null}">
<div class="alert alert-success">
<p>You have been logged out successfully.</p>
</div>
</c:if>
<c:if test="${param.accessDenied !=
Incorporating Spring Security
[ 178 ]
null}">
<div class="alert alert-danger">
<p>Access Denied: You are not authorised! </p>
</div>
</c:if>
<div class="input-group input-sm">
<label class="input-group-addon" for="username"><i
class="fa fa-user"></i></label> <input type="text" class="form-control"
id="userId" name="userId" placeholder="Enter Username"
required>
</div>
<div class="input-group input-sm">
<label class="input-group-addon" for="password"><i
class="fa fa-lock"></i></label> <input type="password"
class="form-control" id="password" name="password" placeholder="Enter Password" required>
</div>
<div class="form-actions">
<input type="submit"
class="btn btn-block btn-primary btn-default" value="Log in">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
Now create one more configuration file called SecurityConfig.java under the
7. com.packt.webstore.config package in the src/main/java source folder, add the following content into it, and save it:
package com.packt.webstore.config;
import org.springframework.beans.factory
.annotation.Autowired;
import org.springframework.context
Incorporating Spring Security
.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;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Autowired
public void
configureGlobalSecurity(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("john").password("pa55word ").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("root123 ").roles("USER","ADMIN");
}
@Override
protected void configure(HttpSecurity httpSecurity)
throws Exception {
httpSecurity.formLogin().loginPage("/login")
.usernameParameter("userId")
.passwordParameter("password");
httpSecurity.formLogin().defaultSuccessUrl
("/market/products/add")
.failureUrl("/login?error");
httpSecurity.logout().logoutSuccessUrl("/login?
logout");
httpSecurity.exceptionHandling().accessDeniedPage
("/login?accessDenied");
httpSecurity.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/**/add").access("hasRole('ADMIN')")
Incorporating Spring Security
[ 180 ]
.antMatchers("/**/market/**").access
("hasRole('USER')");
httpSecurity.csrf().disable();
}
}
Create one more initializer class called SecurityWebApplicationInitializer
8. under the com.packt.webstore.config package in the src/main/java source folder, add the following content into it, and save it:
package com.packt.webstore.config;
import org.springframework.security.web.context
.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends
AbstractSecurityWebApplicationInitializer {
}
Now open your addProduct.jsp file and add the following code after the <a
9.
href="?language=en" >English</a>|<a href="?language=nl"
>Dutch</a> anchor tag:
<a href="<c:url value="/logout" />">Logout</a>
Now run your application and enter the URL,
10.
http://localhost:8080/webstore/; you will see the welcome screen.
Now try to access the products page by entering the URL,
11.
http://localhost:8080/webstore/market/products; a login page will be shown. Enter an arbitrary username and password; you will see an Invalid username and password error:
Incorporating Spring Security
Login page showing error messages for invalid credentials
Now enter the username john and the password pa55word, and press the Log in
12. button; you should be able to see the regular products page.
Now try to access the add products page by entering the URL,
13.
http://localhost:8080/webstore/market/products/add; again the login page will be shown with the error message Access Denied: You are not
authorised!
Login page showing error messages for unauthorised users
Incorporating Spring Security
[ 182 ]
Now enter the username admin and the password root123, and press the Log in
14. button; you should be able to see the regular add products page with a logout button in the top-right corner.
What just happened?
As usual, in order to use Spring Security in our project, we need some Spring Security- related JARs; from steps 1 to 4 we just added those JARs as Maven dependencies.
In step 5, we created one more controller called LoginController to handle all login- related web requests. It simply contains a single request mapping method to handle login- login failure and log out requests. Since the request mapping method returns a view
named login, we need to create a view file called login.jsp, which is what we did in step 6.
login.jsp contains many tags with the bootstrap style class applied to enhance the look and feel of the login form; we don't need to concentrate on those tags. But some important tags are used to understand the flow; the first one is the <c:if> tag:
<c:if test="${param.error != null}">
<div class="alert alert-danger">
<p>Invalid username and password.</p>
</div>
</c:if>
<c:if > is a special JSTL tag to check a condition; it is more like an if...else condition that we use in our programming language. Using this <c:if> tag we are simply checking whether the page request parameter contains a variable called error; if the request
parameter contains a variable called error we simply show an error message, Invalid username and password, within the <p> tag using the <spring:message> tag.
Similarly, we are also checking whether the request parameter contains variables called logout and accessDenied; if so we show the corresponding message, also within the <P> tag:
<c:if test="${param.logout != null}">
<div class="alert alert-success">
<p>You have been logged out successfully.</p>
</div>
</c:if>
<c:if test="${param.accessDenied != null}">
<div class="alert alert-danger">
<p>Access Denied: You are not authorised! </p>
Incorporating Spring Security
</div>
</c:if>
So now we facilitated all possible login-related messages being shown in the login page. The other important tag in login.jsp is the form tag, which represents the login form. Notice the action attribute of the form tag:
<c:url var="loginUrl" value="/login" />
<form action="${loginUrl}" method="post" class="form-horizontal">
We are simply posting our login form values, such as username and password, to the Spring Security authentication handler URL, which is stored in the variable called
${loginUrl}. Here the special JSTL tag <c:url> is used to encode the URL.
Okay, now we have created a controller (LoginController) to dispatch the login page. But we need to tell Spring to present this login page to users if they try to access a page without logging in. How can we enforce that? This is where
the WebSecurityConfigurerAdapter class comes in; by extending
WebSecurityConfigurerAdapter, we can configure the HttpSecurity object for various security-related settings in our web application. So in step 7, we are simply creating a class called SecurityConfig to configure the security-related aspects of our web application.
One of the important methods in the SecurityConfig class is
configureGlobalSecurity; under this method we are simply configuring
AuthenticationManagerBuilder to create two users, john and admin, with a specified password and roles:
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("john")
.password("pa55word")
.roles("USER");
auth.inMemoryAuthentication().withUser("admin")
.password("root123")
.roles("USER","ADMIN");
}
The next important method is configure; within this method we are doing some
authentication-and authorization-related configuration and we will see these one by one. The first configuration tells Spring MVC that it should redirect the users to the login page if authentication is required; here the loginpage attribute denotes to which URL it should forward the request to get the login form.
Incorporating Spring Security
[ 184 ]
Remember this request path should be the same as the request mapping of the login() method of LoginController. We are also setting the user name parameter and password parameter name in this configuration:
httpSecurity.formLogin().loginPage("/login")
.usernameParameter("userId")
.passwordParameter("password");
With this configuration, while posting the username and password to the Spring Security authentication handler through the login page, Spring expects those values to be bound under the variable name userId and password respectively; that's why, if you notice, the input tags for username and password carry the name attributes userId and password in step 6:
<input type="text" class="form-control" id="userId" name="userId"
placeholder="Enter Username" required>
<input type="password" class="form-control" id="password" name="password" placeholder="Enter Password" required>
Similarly, Spring handles the log out operation under the /logout URL; that's why in step
9 we formed the logout link on the add products page, as follows:
<a href="<c:url value="/logout" />">Logout</a>
Next, we are just configuring the default success URL, which denotes the default landing page after a successful login; similarly the authentication failure URL indicates to which URL the request needs to be forwarded in the case of login failure:
httpSecurity.formLogin().defaultSuccessUrl("/market/products/add")
.failureUrl("/login?error");
Notice we are setting the request parameter to error in the failure URL; thus when the login page is rendered, it will show the error message Invalid username and password in the case of login failure. Similarly, we can also configure the logout success URL, which denotes where the request needs to be forwarded to after a logout:
httpSecurity.logout().logoutSuccessUrl("/login?logout");
You can also see that we are setting the request parameter as logout for the logout success URL to match the condition checking that we performed in login.jsp:
<c:if test="${param.logout != null}">
<div class="alert alert-success">
<p>You have been logged out successfully.</p>
</div>
</c:if>
Incorporating Spring Security
And similarly, we also configured the redirection URL for the access denied page in the case
of authorization failure as follows:
httpSecurity.exceptionHandling().accessDeniedPage("/login?accessDenied");
The request parameter we are setting here for the access denied page URL should match the parameter name that we are checking in the login.jsp page:
<c:if test="${param.accessDenied != null}">
<div class="alert alert-danger">
<p>Access Denied: You are not authorised! </p>
</div>
</c:if>
So now we have configured almost all redirection URLs that specify to which page the user should get redirected in the case of login success, login failure, logout success, and access denial. The next configuration is the more important as it defines which user should get access to which page:
httpSecurity.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/**/add").access("hasRole('ADMIN')")
.antMatchers("/**/market/**").access("hasRole('USER')");
The preceding configuration defines three important authorization rules for our web
application, in terms of Ant pattern matchers. The first one allows the request URLs that end with /, even if the request doesn't carry any roles. The next rule allows all request URLs that end with /add, if the request has the role ADMIN. The third rule allows all request URLs that have the path /market/ if they have the role USER.
Okay we defined all security-related configurations in the security context file, but Spring should know about this configuration file and will have to read this configuration file before booting the application. Only then can it create and manage those security-related configurations. How can we instruct Spring to pick up this file? The answer is
AbstractSecurityWebApplicationInitializer; by extending the
AbstractSecurityWebApplicationInitializer class, we can instruct Spring MVC to pick up our SecurityConfig class during bootup. So in step 8, that's what we are doing.
After finishing all the steps, if you run your application and enter the
http://localhost:8080/webstore/ URL, you are able to see the welcome screen. Now try to access the products page by entering the
http://localhost:8080/webstore/market/products URL; a login page will be shown. Enter an arbitrary username and password; you will see an Invalid username and password error.
Incorporating Spring Security
[ 186 ]
Now enter the username john and the password pa55word, and press the Log in button; you should be able to see the regular products page. Now try to access the add products page by entering the http://localhost:8080/webstore/market/products/add URL; again a login page will be shown with an error message, Access Denied: You are not
authorised! Now enter the username admin and the password root123, and press the Log
in button; you should be able to see the regular add products page with a logout button in the top-right corner.