Introduction
A short while ago, I posted an article called "Creating MVC Web Application using Spring Boot with JSP and WAR Archive". In it, I described how to create an MVC application using Spring Boot. It was a pretty cool way of creating a web application. Now, I am going one step further, to lock down the application with Spring Security.
For the sample application in this tutorial, I am going to add a login page, a user service for authenticating and authorizing user, a way to log out, and pages that can only be accessed by users with specific roles. I also throw in two pages, one for login failure and the other for unauthorized access error. As I walk through the code, you the reader will see how fun and awesome it is to get such an application working.
The File Structure
Before I delve into the actual code, I'd like to show you the directory structure and location files:
<base-dir>/src/main/java/org/hanbo/boot/app/App.java
<base-dir>/src/main/java/org/hanbo/boot/app/security/UserAuthenticationService.java
<base-dir>/src/main/java/org/hanbo/boot/app/controllers/LoginController.java
<base-dir>/src/main/java/org/hanbo/boot/app/controllers/SecuredPageController.java
<base-dir>/src/main/java/org/hanbo/boot/app/config/WebAppSecurityConfig.java
<base-dir>/src/main/resources/application.properties
<base-dir>/src/main/resources/static/test.html
<base-dir>/src/main/resources/static/assets/bootstrap/<all the Bootstrap files in some sub folders>
<base-dir>/src/main/resources/static/css/index.css
<base-dir>/src/main/resources/static/jquery/js/jquery.min.js
<base-dir>/src/main/resources/static/js/test.js
<base-dir>/src/main/webapp/WEB-INF/jsp/<all the jsp template files>
<base-dir>/pom.xml
In my previous article, I said that with Spring Boot, web application becomes really simple. This is not true. Any application that deals with security and multiple pages would have a lot of files in the project. These files would show some increased complexity to the entire project. Don't let this scare you. The whole project is still simple.
The POM File
The POM file is the file that specifies how the project is compiled and packaged. Just like what I did in my last tutorial, the sample application in this tutorial is still packaged as a war file. It is running in the same way. But it did add some extra functionality. Before I get into these new functionalities, let's see the content of the POM files:
="1.0"="UTF-8"
<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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>boot-war</artifactId>
<packaging>war</packaging>
<name>Hanbo Boot War Sample App</name>
<description>An example of Spring Boot, JSP and WAR</description>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
In this POM file, the only addition I have made are two new dependencies, the Spring Security and Spring Security taglib. They are:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
The reason these two dependencies are necessary is:
- We need spring-boot-starter-security to set up the core security configuration for the web application.
- We need the spring-security-taglibs so that we can use Spring Security tags in the jsp file.
I will point out where each was used when we get to the code walk through.
Beside these two new dependencies, the file is the same as the POM file from the last tutorial I did. Next, I am going to show you the security configuration for the sample application.
Spring Security Configuration
Before I get into the security configuration, I just want to point out that the main entry class of this sample application is the same as the one from my previous tutorial. I am not listing it out here. You can check it out there. Besides this main entry class, I have to add another class for the security configuration.
This new class I added is called WebAppSecurityConfig
. It is located in:
<base-dir>/src/main/java/org/hanbo/boot/app/config/WebAppSecurityConfig.java
This class is the most complicated class in this sample application. You will see that even this is fairly simple. And to get Spring Security configuration correctly done, you need to know the combination of what methods to call. Before I get to that, I just want to show you the code:
package org.hanbo.boot.app.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.hanbo.boot.app.security.UserAuthenticationService;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
private UserAuthenticationService authenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.antMatchers("/public/**", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/secure/index", true).failureUrl("/public/authFailed")
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
.and()
.exceptionHandling().accessDeniedPage("/public/accessDenied")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/public/logout").permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder authMgrBuilder)
throws Exception
{
authMgrBuilder.authenticationProvider(authenticationProvider);
}
}
This is a typical class, it has three annotations:
@Configuration
: This means when the Spring application starts, it will find configuration information from this class. This class has a method called configure()
which will be invoked during start up. @EnableWebSecurity
: This tells Spring that when application starts, web security will be enabled. That is, the method configure()
will setup the security configuration to be used to secure the web application. @EnableGlobalMethodSecurity
: This is something very special. In order to secure the action methods in my web application controller classes, I have to use annotations like @PreAuthorize()
. This configuration annotation would enable me to do this.
The most complicated portion is the method configure()
. It takes a parameter of type HttpSecurity
. And we can chain calling a series of methods of the object of this type to secure the web application. The goal is to set what pages in the application can be accessed by all, and what pages can only be accessed by users who logged in. That is a high level overview, here is the code of the configure()
method:
@Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.antMatchers("/public/**", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/secure/index", true).failureUrl("/public/authFailed")
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
.and()
.exceptionHandling().accessDeniedPage("/public/accessDenied")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/public/logout").permitAll();
}
What this method does is to configure the way user's http requests are handled. Here are the rules I have configured: First, any request to <context root>/public/** and <context root>/assets/**, would be handled without the need to be authenticated. All other requests, user must be authenticated in order to get access:
http.authorizeRequests()
.antMatchers("/public/**", "/assets/**").permitAll()
.anyRequest().authenticated()
The form based login would be handled by an http post request to <context root>/login, the login page is accessible with http get request to <context root>/login. On the login form, there should be text input called "username
" and another one called "password
". If the user hits the login page directly, after logging in successfuly, the user will see the default page at <context root>/secure/index. And when user login attempt fails, the user will be redirected to <context root>/public/authFailed page. If the user has a specific secure page that he/she wanted to access, after logging in successfully, the successHandler
would use an object of type SavedRequestAwareAuthenticationSuccessHandler
to accomplish this:
.formLogin()
.loginPage("/login")
.permitAll()
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/secure/index", true).failureUrl("/public/authFailed")
.successHandler(new SavedRequestAwareAuthenticationSuccessHandler())
For an authenticated user that tried to access a page that the user has no right to access, I added an exception handler for this:
.exceptionHandling().accessDeniedPage("/public/accessDenied")
For logging out, it will be an http get request to <context root>/logout. And if the logout operation is successful, the user will be redirect to page <context root>/public/logout:
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/public/logout").permitAll();
So, what does the other method in this class do? The other method is used to specify how the user authentication works. In this case, I have created a dummy authentication service. It will check user's name and password against some predefined user names and corresponding passwords. Once the user credential (both user name and password) is verified, the user is assigned with one or more roles. The roles determines what pages user can access.
User Authentication Service
My user authentication service is pretty simple. The service object gets the user name and password, matches against three sets of predefined user name and passwords. If a match found, a set of roles associated with the user name and password would be assigned to the user.
Before I explain the code, I would like to:
package org.hanbo.boot.app.security;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
@Service
public class UserAuthenticationService
implements AuthenticationProvider
{
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException
{
Authentication retVal = null;
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
if (auth != null)
{
String name = auth.getName();
String password = auth.getCredentials().toString();
if (name.equals("admin") && password.equals("admin12345"))
{
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_STAFF"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
retVal = new UsernamePasswordAuthenticationToken(
name, "", grantedAuths
);
}
else if (name.equals("staff1") && password.equals("staff12345"))
{
grantedAuths.add(new SimpleGrantedAuthority("ROLE_STAFF"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
retVal = new UsernamePasswordAuthenticationToken(
name, "", grantedAuths
);
}
else if (name.equals("user1") && password.equals("user12345"))
{
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
retVal = new UsernamePasswordAuthenticationToken(
name, "", grantedAuths
);
}
}
else
{
retVal = new UsernamePasswordAuthenticationToken(
null, null, grantedAuths
);
}
return retVal;
}
@Override
public boolean supports(Class<?> tokenType)
{
return tokenType.equals(UsernamePasswordAuthenticationToken.class);
}
}
Let's start with the easiest part of this new class. My class implements AuthenticationProvider
, which needs me to implement two methods:
Authentication authenticate(Authentication auth)
boolean supports(Class<?> tokenType)
The method supports()
is used to tell whether this AuthenticationProvider
can process the Authentication
object. For this sample application, the login form will return an object of type UsernamePasswordAuthenticationToken
. So this supports()
will only return true
if the Authentication
object has the right type. Then the method authenticate()
can process the authentication request.
The method authenticate()
first gets the user name and password (password value is from the getCredential()
). Then, match the user name and password against three sets of user name and password combination:
admin
: If this matches, the user will get three roles: ROLE_ADMIN
, ROLE_STAFF
, and ROLE_USER
staff
: If this matches, the user will get two roles: ROLE_STAFF
, and ROLE_USER
user
: If this matches, the user will get just one role: ROLE_USER
The return value is an object of type UsernamePasswordAuthenticationToken
. The object of this type can contain three different types of values:
- User name
- Credential. It usually is the password. So I leave it as
null
. - The last one is the list of roles. The roles are basically
string
s which start with the prefix "ROLE_
". And the role objects are of type SimpleGrantedAuthority
.
It is important to have all the role names start with the prefix "ROLE_
". Without this prefix, the role authorization with @PreAuthorize()
annotations will not work.
The code that creates the role objects and adds to the list is like this:
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
...
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_STAFF"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Creating the UsernamePasswordAuthenticationToken
object with user name and roles:
retVal = new UsernamePasswordAuthenticationToken(
name, "", grantedAuths
);
....
return retVal;
Secure the Web Pages
The last part of coding is to secure the web pages. More precisely, we want to secure the action methods that would result in web pages being sent back to user browser. To show how this is done, I create a controller
class called SecuredPageController
. In this, there are three action methods (a term often used in ASP.NET MVC), each would display a page. And users can only get to these pages when they are logged in and assigned with the right user role. Here is the full source of this class:
package org.hanbo.boot.app.controllers;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class SecuredPageController
{
@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping(value="/secure/adminPage", method = RequestMethod.GET)
public ModelAndView adminPage()
{
ModelAndView retVal = new ModelAndView();
retVal.setViewName("webAccess");
retVal.addObject("pageInfo", "The AWESOME Admin Page");
retVal.addObject("userInfo", "Awesome Admin User.");
return retVal;
}
@PreAuthorize("hasRole('ROLE_STAFF')")
@RequestMapping(value="/secure/staffPage", method = RequestMethod.GET)
public ModelAndView staffPage()
{
ModelAndView retVal = new ModelAndView();
retVal.setViewName("webAccess");
retVal.addObject("pageInfo", "The SUPPORTING Staff Page");
retVal.addObject("userInfo", "T.L.C Staff User.");
return retVal;
}
@PreAuthorize("hasRole('ROLE_USER')")
@RequestMapping(value="/secure/userPage", method = RequestMethod.GET)
public ModelAndView userPage()
{
ModelAndView retVal = new ModelAndView();
retVal.setViewName("webAccess");
retVal.addObject("pageInfo", "The LAMMO User Page");
retVal.addObject("userInfo", "an ordinary User.");
return retVal;
}
}
I am not going over every single method here. They are essentially doing the same thing. But, the @PreAuhorize()
annotation is different for each method. I will explain how one of them works:
@PreAuthorize("hasRole('ROLE_STAFF')")
@RequestMapping(value="/secure/staffPage", method = RequestMethod.GET)
public ModelAndView staffPage()
{
ModelAndView retVal = new ModelAndView();
retVal.setViewName("webAccess");
retVal.addObject("pageInfo", "The SUPPORTING Staff Page");
retVal.addObject("userInfo", "T.L.C Staff User.");
return retVal;
}
The annotation @PreAuhorize()
defines that only the user with role "ROLE_STAFF
" can access this method. The annotation @RequestMapping()
defines the URL route which is handled by this action method. In this case, the following URL path would be handled by this:
- http://localhost:8080/secure/staffPage
And it only handles HTTP GET request to this url. The method body creates a ModelAndView
object, which uses a view template called "webAccess.jsp" The ModelAndView
object also contains two string
values that can be used by the template to create the actual page. The other two methods do the same thing with the sample view template, but the URL is different and the user role needed to access each of the pages is different as well. They are:
- http://localhost:8080/secure/adminPage, accessible only by admin user
- http://localhost:8080/secure/userPage, accessible only by regular user
The Page Template
Now that we have seen how the secured pages are served, it is time to take a look at the actual page template. As you can see from the action methods (listed in previous section), they all reference the same JSP view template, which is called "webAccess.jsp". Here is what it looks like:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<!DOCTYPE html>
<html lang="en">
<head>
<sec:authorize access="hasRole('ROLE_ADMIN')">
<title>Admin Logged in - Home</title>
</sec:authorize>
<sec:authorize access="hasRole('ROLE_STAFF')">
<title>Staff Logged in - Home</title>
</sec:authorize>
<sec:authorize access="hasRole('ROLE_USER')">
<title>Normal User Logged in - Home</title>
</sec:authorize>
<link href="${pageContext.request.contextPath}/assets/bootstrap/css/bootstrap.min.css"
rel="stylesheet">
<link href="${pageContext.request.contextPath}/assets/css/index.css" rel="stylesheet">
</head>
<body>
<div class="row top-margin">
<div class="col-xs-offset-1 col-xs-10 col-sm-offset-2 col-sm-8 col-md-offset-3 col-md-6">
<div class="panel panel-default">
<div class="panel-body">
<ol class="breadcrumb">
<sec:authorize access="hasRole('ROLE_ADMIN')">
<li><a href="${pageContext.request.contextPath}/secure/adminPage">
Admin Access</a></li>
</sec:authorize>
<sec:authorize access="hasRole('ROLE_STAFF')">
<li><a href="${pageContext.request.contextPath}/secure/staffPage">
Staff Access</a></li>
</sec:authorize>
<sec:authorize access="hasRole('ROLE_USER')">
<li><a href="${pageContext.request.contextPath}/secure/userPage">
User Access</a></li>
</sec:authorize>
<li><a href="${pageContext.request.contextPath}/logout">
<i class="glyphicon glyphicon-log-out"></i> Log Out</a></li>
</ol>
<h3>You are seeing this ${pageInfo} because...</h3>
<p>You are logged in as ${userInfo}</p>
</div>
</div>
</div>
</div>
<script type="text/javascript"
src="${pageContext.request.contextPath}/assets/jquery/js/jquery.min.js"></script>
<script type="text/javascript"
src="${pageContext.request.contextPath}/assets/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
When you log in as a staff user (user name: staff1
; password: staff12345
), then navigate to the page via the following URL...
http://localhost:8080/secure/staffPage
...you will see a screenshot like the following:
The main body of the page is just this:
<h3>You are seeing this ${pageInfo} because...</h3>
<p>You are logged in as ${userInfo}</p>
There is nothing unusual about the above JSP template code, just two lines, each has a place holder that can be replaced by real values.
In the same template, the part above that is more complicated is a simple menu. The menu items are displayed based on the user roles. Only the user that possesses the right roles, would see the menu item. In the above screenshot, there are two items available:
- Staff Access: If you click on this link, you will see the same page as the above screenshot.
- User Access: If you click on this link, you will see the page that can be accessible by user with only normal user role.
The way to put restrictions on these links is to use Spring Security tags (which is why I put Spring Security taglib in my Maven Pom). Here is the way to allow only the admin user to see a link:
<sec:authorize access="hasRole('ROLE_ADMIN')">
<li><a href="${pageContext.request.contextPath}/secure/adminPage">Admin Access</a></li>
</sec:authorize>
That is about all you can do with a page template. Next, I am going to show you the login page.
The Login Page
When the application is running, and user attempts to access the secure pages as defined in the SecuredPageController
class, the user will see the login page. Here is a screenshot of it:
Unlike a normal page (whether it is secure or public), the most likely way a login page is displayed is when Spring Security triggers it, like when user attempts to access a secure page without logging in. The way it works is that every request passes through HTTP filters, in this sample project the only filters are security filters. When unauthorized request is detected, instead of sending back the page which user desired, the login page is sent back. The user uses the page to authenticate and authorize him/herself, and then the SavedRequestAwareAuthenticationSuccessHandler
object will return the desired page back to the user.
The login page HTML code looks like this:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Login Page</title>
<link href="${pageContext.request.contextPath}/assets/bootstrap/css/bootstrap.min.css"
rel="stylesheet">
<link href="${pageContext.request.contextPath}/assets/css/index.css" rel="stylesheet">
</head>
<body>
<div class="row top-margin">
<div class="col-xs-offset-1 col-xs-10 col-sm-offset-2 col-sm-8 col-md-offset-3 col-md-6">
<div class="panel panel-default">
<div class="panel-body">
<form id="login_form"
action="${pageContext.request.contextPath}/login" method="post">
<div class="form-group">
<label for="username">User Name:</label>
<input type="text" class="form-control" id="username"
name="username" placeholder="User Name...">
</div>
<div class="form-group">
<label for="username">Password:</label>
<input type="password" class="form-control"
id="password" name="password" placeholder="Password...">
</div>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<div class="form-group">
<button class="btn btn-primary form-control" type="submit">Login</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript"
src="${pageContext.request.contextPath}/assets/jquery/js/jquery.min.js"></script>
<script type="text/javascript"
src="${pageContext.request.contextPath}/assets/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
The login page is very simple. There are two input fields; one is for entering the user name; and one is for entering the password. The input fields must have the names of "username
" and "password
". Form authentication with Spring Security is expecting these input parameters.
One important thing is that Spring Security uses CSRF for verification. When the login form is created at the server side, the CSRF token value is added as a hidden input. When the form is posted to the backend server, Spring Security will verify this hidden input value. It is like a secret hand shake, once verified, the user name and password are passed to my UserAuthenticationService
object to authenticate and authorize.
CSRF is not necessary, but is turned on by default. If you want to shut it off, you can do it in the class WebAppSecurityConfig
.
Testing the Application
At this point, I have covered all the most important aspects of this sample application. Now it is time to test it. After downloading the sample application, and unzipping it, the first thing you do is to rename all the *.sj files to *.js in the project zip file. Do a search of all these files in the resources/static folder and its sub folders. All the *.sj files are there.
To build the application, go to the folder where the project files have been unzipped. You should see the pom.xml in it. From this folder path, open cmd.exe or terminal window. Run the following command:
mvn clean install
The build will succeed. Next, you can run the packaged WAR file with the following command at the same folder location:
java -jar target\boot-war-1.0.0.war
The run will succeed and will be waiting for user interactions. You should see the following output in the commandline prompt:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.5.RELEASE)
2018-10-31 22:24:34.092 INFO 9784 --- [ main] org.hanbo.boot.app.App:
Starting App v1.0.0 on U3DTEST-PC with PID 9784
(C:\Users\u3dadmin\workspace-mars8\SpringBootAuth\target\boot-war-1.0.0.war
started by u3dadmin in C:\Users\u3dadmin\workspace-mars8\SpringBootAuth)
2018-10-31 22:24:34.128 INFO 9784 --- [ main] org.hanbo.boot.app.App:
No active profile set, falling back to default profiles: default
...
<Skipped a lot of lines>
...
2018-10-31 22:24:57.076 INFO 9784 --- [ main] org.hanbo.boot.app.App:
Started App in 27.075 seconds (JVM running for 30.198)
Now, it is time to test the application out. First, try the default index page:
http://localhost:8080/
Login page would be first to show up:
As I have mentioned before, there are three different user credentials you can use to test this application:
- User name:
admin
; password: admin12345
- User name:
staff1
; password: staff12345
- User name:
user1
; password: user12345
Regardless of which one you use to login, you will see the secured index page, like this:
The above screenshot shows the user logged in is a staff user. And two roles are associated with this user. The simple header displays only two links. If the user logged in as an admin, there will be three links in the header and three roles displayed on the page, like this:
And if the user logged in as a normal user, the index page will look like this:
Try the links listed in the header. These links are only available to the user with the specific user role. For example, the link called "Staff Access", it is only visible to the user with staff role. When user clicks on it, it will display this:
Now, if the user with just normal user role tries to access this page, you will see the "Access Denied" page. The only way to try this scenario is first logging in as a normal user. Then navigate to this URL:
http://localhost:8080/secure/staffPage
Then, you will see the following page:
One last test you might want to do is try user name with random password, which will result in displaying the login error page, like this:
That is all regarding this sample application.
Points of Interest
This article expands on my previous article on Spring Boot. However, the content is really nothing new. I wrote another article a while ago on Spring Security integration with Spring MVC. If you stuck with this article, please use this other one as a reference. There are also a lot of reference materials on the 'net that can get you out of trouble.
I did fix the issue with the HTTP security filter configuration. In my previous article on Spring Security, the configuration was not correctly done. So the sample application wasn't working correctly. This time, I made the configuration correctly. The reason I was able to get this correct is that I didn't include the RESTFul API controller in this sample application. For RESTFul APIs, the Spring Security configuration is different. Maybe I will write another article about this.
Overall, using Spring Boot to design a sample application like this is much simpler than doing it with Spring MVC, and Spring Security and pack in a WAR archive to be deployed to an application container. Running it is simpler too. The whole WAR archive is run as a JAR executable. I enjoyed this tutorial. There will be more articles on Spring Boot and web application development. I hope you enjoyed reading this tutorial. Thank you.
History
- 10/29/2018 - Initial draft