Skip to content

4. Security

Jorge edited this page Jan 9, 2018 · 2 revisions

QuizZz makes use of the Spring Security and Spring AOP frameworks to provide secure Authentication and Authorization throughout the application.

Introduction

In QuizZz, we require the user to be logged in in order to create new quizzes and see, edit or publish his own quizzes. Any attempt to access data the user shouldn't have access to should result in HTTP 401 Unauthorized for reading and HTTP 403 Forbidden for writing.

We use Spring Security for login purposes and to provide some high level access control. We then use Spring AOP to fine tune authorization closer to the access to critical data.

Spring Security

We'll start by configuring Spring Security in our Spring Boot envirnoment and setting up some basic access control rules using the @PreAuthorize() annotation.

Configuration

First of all, let's add Spring Security to our Maven dependencies:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

The configuration for Spring Security is available in jorge.rv.quizzz.config.SecurityConfig.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)

We are using the annotation @EnableWebSecurity to tell Spring we want to use Spring Security on our project.

We are using the annotation @EnableGlobalMethodSecurity(prePostEnabled = true) to enable access control via annotations. Spring Security offers a couple of methods to manage access control. The first one is using ant matchers in the configuration itself and the second one is via annotations throughout controllers. Using annotations couples the security framework with your code a bit more but I found it to be a bit more flexible. We'll be covering more on Access Control in the next section.

Login is performed through a web form. We don't want login re-directions to be automatically set up by Spring Security when we are using the Rest API. For this reason, we are setting up two different configurations for the Web and Rest controllers. To achieve this, we'll create two inner classes that extend WebSecurityConfigurerAdapter with the @Configuration annotation.

The Rest config is applied to /api/ urls and permits all requests since they will be handled later on via @PreAuthorize() annotations.

	http
	  .antMatcher("/api/**")
	  .authorizeRequests().anyRequest().permitAll()
	  .and().httpBasic()
	  .and().csrf().disable();

The Web config expands the previous configuration to add a login page and remember me functionality.

	http
	  .formLogin().loginPage("/user/login").defaultSuccessUrl("/", true)
	  .and().rememberMe().tokenRepository(persistentTokenRepository)
	  .and().csrf().disable()
	  .logout().logoutSuccessUrl("/").deleteCookies("JSESSIONID").invalidateHttpSession(true);

The last thing to do is setting up persistentTokenRepository to return a JdbcTokenRepository linked to our database so Spring Security can use it to take advantage of the Remember Me Functionality:

	@Autowired
	DataSource dataSource;

	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
		JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
		db.setDataSource(dataSource);
		return db;
	}

Access Control

As mentioned in the Configuration section, we will be using @PreAuthorize() to manage Access Control at the controller level. We will use @PreAuthorize("permitAll") for endpoints that can be accessible to everyone and @PreAuthorize("isAuthenticated()") for endpoints that require the user to be logged in.

This is just a preliminary check to avoid unwanted requests to go through. However, at this point we don't yet know whether the logged user is authorized to access the data it's requesting. For this kind of checks we will use Spring AOP just before the calls to the Repository layer.

If you want to read further on how to set up access control with annotations, check this article.

Spring AOP

As mentioned above, we'll be using Spring AOP to fine tune authorization once users have passed through the Access Control rules.

Configuration

Lets add the Spring AOP dependency to Maven:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

The configuration for Spring AOP is in jorge.rv.quizzz.config.AopConfig. Configuring Spring AOP is pretty straight forward:

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("jorge.rv.quizzz.service")
public class AopConfig {

}

We use @EnableAspectJAutoProxy to enable Spring AOP with AspectJ support.

We use @ComponentScan() to tell Spring AOP where to find our custom aspects.

AccesControl Services

Located in the jorge.rv.quizzz.service.accesscontrol package, we can find a set of access control services extending from AccessControlService. These classes contain rules for the CRUD operations for each of our entities. All these services are marked with @Service so we can auto-wire them wherever required.

public interface AccessControlService<T extends BaseModel> {
	void canUserCreateObject(AuthenticatedUser user, T object) throws UnauthorizedActionException;
	void canCurrentUserCreateObject(T object) throws UnauthorizedActionException;
	void canUserReadObject(AuthenticatedUser user, Long id) throws UnauthorizedActionException;
	void canCurrentUserReadObject(Long id) throws UnauthorizedActionException;
	void canUserReadAllObjects(AuthenticatedUser user) throws UnauthorizedActionException;
	void canCurrentUserReadAllObjects() throws UnauthorizedActionException;
	void canUserUpdateObject(AuthenticatedUser user, T object) throws UnauthorizedActionException;
	void canCurrentUserUpdateObject(T object) throws UnauthorizedActionException;
	void canUserDeleteObject(AuthenticatedUser user, T object) throws UnauthorizedActionException;
	void canCurrentUserDeleteObject(T object) throws UnauthorizedActionException;
}

The entities we are most interested in protecting all extend UserOwned and, with a few exceptions, follow the same rules. For this reason, most of the rules can be found in the abstraction AccessControlServiceUserOwned:

public abstract class AccessControlServiceUserOwned<T extends BaseModel & UserOwned>
		implements AccessControlService<T> {
...
}

Each service can then override any rule if, for any reason, it applies in a different way for the entity it deals with.

AccessControl Aspects

Located in the jorge.rv.quizzz.service.accesscontrol.aspects package, we can find the actual aspects. These aspects make use of the AccessControl services mentioned above to make decissions. The interface defining these aspects is AccessControlAspects.

Example of an aspect handling a logged in user attempting to get information of a particular question:

	@Around("execution(* jorge.rv.quizzz.repository.QuestionRepository.find(Long)) && args(id)")
	public Object find(ProceedingJoinPoint proceedingJoinPoint, Long id) throws Throwable {
		accessControlService.canCurrentUserReadObject(id);

		return proceedingJoinPoint.proceed();
	}