In this tutorial I will show you the most useful annotation is @PreAuthorize which decides whether a method can actually be invoked or not based on user’s role and permission. hasRole() method returns true if the current principal has the specified role and hasPermission() method returns true if the current user’s rola has the specific permission such as READ, WRITE, UPDATE or DELETE. By default if the supplied role does not start with ROLE_ will be added. This can be customized by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.

You can check my previous tutorial on hasRole @PreAuthorize annotation – hasRole example in Spring Security

This @PreAuthorize annotation will be applied on the method as a Method Security Expression. For example,

@PreAuthorize("hasRole('ADMIN') and hasPermission('hasAccess','WRITE')")
public void create(Contact contact);

which means that access will only be allowed for users with the role ROLE_ADMIN and has WRITE permission. Obviously the same thing could easily be achieved using a traditional configuration and a simple configuration attribute for the required role.

Let’s see the working example below

Below is the project’s pom file

<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>jeejava</groupId>
	<artifactId>spring-auth-annotations</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-auth-annotations Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<java.version>1.8</java.version>
		<jackson.version>2.5.3</jackson.version>
		<spring.version>4.3.8.RELEASE</spring.version>
		<spring.security.version>4.2.2.RELEASE</spring.security.version>
	</properties>

	<dependencies>
		<!-- Spring mvc -->
		<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>${spring.security.version}</version>
		</dependency>

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

		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>2.3.1</version>
			<scope>provided</scope>
		</dependency>

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

		<!-- jackson -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.version}</version>
		</dependency>
	</dependencies>

	<build>
		<finalName>spring-auth-annotations</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</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>2.2</version>
				<configuration>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

Create the below required class. For more information please read http://docs.spring.io/spring-security/site/docs/3.2.3.RELEASE/apidocs/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.html

package jeejava.security.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "jeejava.controller", "jeejava.security" })
public class WebMvcConfig {

}

We need to create below class in order to use hasPermission() method in @PreAuthorize annotation.

The use of the hasPermission() expression has different look.

hasPermission() expressions are delegated to an instance of PermissionEvaluator. It is intended to bridge between the expression system and Spring Security’s ACL system, allowing you to specify authorization constraints on domain objects, based on abstract permissions. It has no explicit dependencies on the ACL module, so you could swap that out for an alternative implementation if required. The interface has two methods:

boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);

  boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);

which map directly to the available versions of the expression, with the exception that the first argument (the Authentication object) is not supplied. The first is used in situations where the domain object, to which access is being controlled, is already loaded. Then expression will return true if the current user has the given permission for that object. The second version is used in cases where the object is not loaded, but its identifier is known. An abstract “type” specifier for the domain object is also required, allowing the correct ACL permissions to be loaded.

To use hasPermission() expressions, you have to explicitly configure a PermissionEvaluator in your application context.

package jeejava.security;

import java.io.Serializable;

import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

@Component
public class BasePermissionEvaluator implements PermissionEvaluator {

	@Override
	public boolean hasPermission(Authentication authentication, Object accessType, Object permission) {
		if (authentication != null && accessType instanceof String) {
			if ("hasAccess".equalsIgnoreCase(String.valueOf(accessType))) {
				boolean hasAccess = validateAccess(String.valueOf(permission));
				return hasAccess;
			}
			return false;
		}
		return false;
	}

	private boolean validateAccess(String permission) {
		// ideally should be checked with user role, permission in DB
		if ("READ".equalsIgnoreCase(permission)) {
			return true;
		}
		return false;
	}

	@Override
	public boolean hasPermission(Authentication authentication, Serializable serializable, String targetType,
			Object permission) {
		return false;
	}

}

Enable Web Security using below class. Configure global-method-security pre-post-annotations using Java configuration. Here, the in-memory authentication has been provided. In real life application, the authentication should happen through database or LDAP or any other third party API etc. Here, the static resources like css, js etc. files under static folder are excluded from spring security authentication.

package jeejava.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
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.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import jeejava.security.BasePermissionEvaluator;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private BasePermissionEvaluator permissionEvaluator;

	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/static/**");
	}

	@Autowired
	public void registerGlobal(AuthenticationManagerBuilder auth) throws Exception {
		// in reality DB auth is required
		auth.inMemoryAuthentication().withUser("user").password("user").roles("USER").and().withUser("admin")
				.password("admin").roles("USER", "ADMIN");
	}

	@Bean
	public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
		DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
		handler.setPermissionEvaluator(permissionEvaluator);
		return handler;
	}

}

Here in the above class, admin has USER as well as ADMIN roles but user has only one role USER. Therefore admin can access its own URL as well as user’s URL but user can access only its own URL but not admin’s URL.

Below class is required in order to load the Dispatcher Servlet with mapping / and other configurations.

package jeejava.security.config;

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

public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

}

Now create below REST Controller class to test the user’s access to a particular URL based on role using @PreAuthorize annotation.

package jeejava.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

	@PreAuthorize("hasRole('USER')")
	@RequestMapping("/user")
	public String userRole() {
		return "You have USER role";
	}

	@PreAuthorize("hasRole('ADMIN')")
	@RequestMapping("/admin")
	public String adminRole() {
		return "You have ADMIN role";
	}

	@PreAuthorize("hasRole('ADMIN') and hasPermission('hasAccess','READ')")
	@RequestMapping("/admin/access")
	public String adminAccess() {
		return "You have ADMIN role and READ access";
	}

}

Now when you deploy the application on Tomcat server and access the URL – http://localhost:8085/spring-auth-annotations/admin/access using credentials admin/admin then you will see the output You have ADMIN role and READ access. Now if you change the permission as hasPermission('hasAccess','WRITE') then you will get HTTP Status 403 – Access is denied because admin does not have the WRITE permission.

Thanks for reading.

Tags:

I am a professional Web developer, Enterprise Application developer, Software Engineer and Blogger. Connect me on Roy Tutorials | TwitterFacebook Google PlusLinkedin | Reddit | Email Me

Leave a Reply

Your email address will not be published. Required fields are marked *