Wednesday, January 24, 2018

Spring 4 Security Basic Example

In this post, I am going to demonstrate Spring Security 4 usage to secure a Spring MVC web application, securing URL access with authentication. We will develop a simple example to learn Spring Security 4 basics. This post uses Spring Annotation-based configuration for Servlet 3.0 containers (So we don't need web.xml). So in this application, we are going to follow zero XML configuration.

Spring Security is a lightweight security framework that provides authentication and authorization support in order to Secure Spring-based applications. It integrates well with Spring MVC and comes bundled with popular security algorithm implementations. Today I am going to show Spring Security 4 basics & advanced usage, securing URL, view’s & methods in your Spring MVC/Hibernate based application. Let’s get started.

Following technologies being used:

  • Spring 4.3.13.RELEASE
  • Spring Security 4.2.3.RELEASE
  • Maven 3.3.9
  • JDK 1.8
  • Tomcat 8.0.21
  • Eclipse/STS (Based on your preference)

Project directory structure:



























Updating pom.xml to include required dependencies:

<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.javajagran</groupId>
<artifactId>Spring-Security-Annotation-Example</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>Spring-Security-Annotation-Example Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.13.RELEASE</spring.version>
<springsecurity.version>4.2.3.RELEASE</springsecurity.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${springsecurity.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${springsecurity.version}</version>
</dependency>

<!-- java servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<finalName>Spring-Security-Annotation-Example</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
<warName>Spring-Security-Annotation-Example</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>


The first thing to notice here is the maven-war-plugin declaration. As we are using full annotation configuration, we don’t even use web.xml so we will need to configure this plugin in order to avoid maven failure to build war package. Along with that, we have also included JSP/Servlet/JSTL dependencies which we will be needing as we are going to use servlet API and JSTL view in our code.


Adding Spring Security Configuration Class

The first and foremost step to add spring security in our application is to create Spring Security Java Configuration. This configuration creates a Servlet Filter known as the springSecurityFilterChain which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the login form, etc) within our application.

org.javajagran.springsecurity.configration.SecurityConfiguration

package org.javajagran.springsecurity.configration;

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.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 SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("java").password("jagran123").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("manager").password("manager123").roles("ADMIN", "MANAGER");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll()
                                                       .antMatchers("/admin/**").access("hasRole('ADMIN')")
                                                       .antMatchers("/manager/**").access("hasRole('ADMIN') and hasRole('MANAGER')")
                                                       .and().formLogin().and().exceptionHandling().accessDeniedPage("/access_denied");
}
}

Method configureGlobalSecurity in above class configures AuthenticationManagerBuilder with user credentials and allowed roles. This AuthenticationManagerBuilder creates AuthenticationManager which is responsible for processing any authentication request. Notice that in above example, we have used in-memory authentication while you are free to choose from JDBC, LDAP and other authentications.

The overridden Method Configure configures HttpSecurity which allows configuring web-based security for specific HTTP requests. By default, it will be applied to all requests, but can be restricted using requestMatcher(RequestMatcher)/antMathchers or other similar methods.

In above configuration, we say that URL’s ‘/’ & ‘/home’ are not secured, anyone can access them. URL ‘/admin/**’ can only be accessed by someone who has ADMIN role. URL ‘/manager/**’ can only be accessed by someone who has both ADMIN and MANAGER roles.

Method formLogin provides support for form-based authentication and will generate a default form asking for user credentials. You are allowed to configure your own login form. We will see examples for the same in subsequent posts.

We have also used exceptionHandling().accessDeniedPage() which in this case will catch all 403 (HTTP access denied code) exceptions and display our user-defined page instead of showing default HTTP 403 page.

Registering the springSecurityFilter with war

Below specified initializer class registers the springSecurityFilter (class SecurityConfiguration) with application war.

org.javajagran.springsecurity.configration.SecurityWebApplicationInitializer

package org.javajagran.springsecurity.configration;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer{ }

Adding Controller

org.javajagran.springsecurity.configration.SpringSecurityExampleController

package org.javajagran.springsecurity.controller;

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

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class SpringSecurityExampleController {

@RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET)
public String homePage(ModelMap model) {
model.addAttribute("msg", "Hi, Welcome to Java Jagran");
return "home";
}

@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage(ModelMap model) {
model.addAttribute("username", getPrincipal());
return "admin";
}

@RequestMapping(value = "/manager", method = RequestMethod.GET)
public String dbaPage(ModelMap model) {
model.addAttribute("username", getPrincipal());
return "manager";
}

@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "home";
}

@RequestMapping(value = "/access_denied", method = RequestMethod.GET)
public String accessDeniedPage(ModelMap model) {
model.addAttribute("username", getPrincipal());
return "accessDenied";
}

private String getPrincipal() {
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
userName = ((UserDetails) principal).getUsername();
} else {
userName = principal.toString();
}
return userName;
}
}

Methods in controller class are trivial. Method getPrincipal is a generic function which returns the logged in user name from Spring SecurityContext. Method logoutPage handles the logging out with a simple call to SecurityContextLogoutHandler().logout(request, response, auth);. It’s handy and saves you from putting cryptic logout logic in your JSP’s which is not really manageable. You might have noticed that ‘/login’ is missing, it is because it will be generated and handled by default by Spring Security.

Adding SpringMVC Configuration Class

org.javajagran.springsecurity.controller.SpringSecurityExampleConfiguration

package org.javajagran.springsecurity.configration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.javajagran.springsecurity")
public class SpringSecurityExampleConfiguration {

@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");

return viewResolver;
}
}

Adding Initializer class

org.javajagran.springsecurity.configration.SpringMvcInitializer

package org.javajagran.springsecurity.configration;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringSecurityExampleConfiguration.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

}

We can notice that above initializer class extends AbstractAnnotationConfigDispatcherServletInitializer which is the base class for all WebApplicationInitializer implementations. Implementations of WebApplicationInitializer configures ServletContext programmatically, for Servlet 3.0 environments. It means we won’t be using web.xml and we will deploy the app on Servlet 3.0 container.

Adding Views

home.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Home page</title>
</head>
<body>
<p>Greeting : ${msg}. You developed Spring security example using annotation.</p>
</body>
</html>

admin.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Admin page</title>
</head>
<body>
<p>Dear <strong>${username}</strong>, Welcome to Admin Page.
<a href="<c:url value="/logout" />">Logout</a>
</p>
</body>
</html>

manager.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Manager page</title>
</head>
<body>
<p>Dear <strong>${username}</strong>, Welcome to manager Page. 
<a href="<c:url value="/logout" />">Logout</a>
</p>
</body>
</html>

accessDenied.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Access Denied page</title>
</head>
<body>
<p>Dear <strong>${username}</strong>, You are not authorized to access this page
<a href="<c:url value="/logout" />">Logout</a>
</p>
</body>
</html>

Output screens