Tuesday, December 22, 2015

Securing REST APIs with Spring Security (Basic Authentication)

In the previous post we added persistence to our REST API tutorial using Spring Data and MongoDB.  Next we will secure the API, starting from the simplest security mechanism, basic access authentication. In basic authentication, clients of services provide username and password along with their requests. In this tutorial, we will use Spring Security to implement the authentication and restrict access requests on the URL level. The tutorial builds on the initial REST API with the added persistence.

The structure of the project is depicted in the following figure:


We have extended the pom.xml file to add the Spring dependencies for security as shown in the source code below:
<!-- spring security -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>${spring.security.version}</version>
    </dependency>
    <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>       

Our security will essentially be implemented by filtering requests which are sent to the servlets. Since Spring 3.2 the configuration of the security has been simplified and can be achieved using annotations. The SecurityConfig class will extend the WebSecurityConfigurerAdapter and use the @EnableWebSecurity annotation to well, enable web security. In the SecurityConfig class we will override a few methods to grant access credentials to users (persisted in-memory) and to specify the HTTP requests and method that have to be authenticated and who has access to them. All these configurations are shown in the class below:
package com.tasosmartidis.rest_api_tutorial.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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 {

    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {      
        auth
            .inMemoryAuthentication()
                .withUser("tasos").password("tasos").roles("USER","ADMIN")
                .and()
                .withUser("guest").password("guest").roles("USER");
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {      
        http
            .httpBasic()
            .and()
            .authorizeRequests() 
            .antMatchers(HttpMethod.DELETE).hasRole("ADMIN")
            .antMatchers(HttpMethod.GET).hasAnyRole("USER","ADMIN")
            .antMatchers(HttpMethod.POST).hasAnyRole("USER","ADMIN")
            .antMatchers(HttpMethod.PUT).hasAnyRole("USER","ADMIN")
            .and()
            .requiresChannel()
            .antMatchers("/").requiresSecure()
            .and()
            .csrf().disable();
            
    }
}

As shown in the source code above, we use in-memory persistence for assigning roles to pairs of usernames and their passwords (of the users) and we configure the security details for the intercepted incoming requests. We define basic authentication, and based on the type of request we allocate rights to "simple" users and administrators. In our example we allow users to do create, retrieve and update data, but only administrators can delete them. The rights allocation is not really intended to make sense, but we showcase how different rights can be provided to different kind of users. Next,we define that any request mapped to the URL must use a secure channel. Finally, for sake of simplicity we disable csrf (cross-site request forgery) which is enabled by default from WebSecurityConfigurerAdapter.

We also need to configure the DelegatingFilterProxy which is a special servlet filter to intercept incoming requests and apply the security we enabled in the previous class. Once again, Spring makes this easy for us. We just need to extend AbstractSecurityWebApplicationInitializer which implements WebApplicationInitializer and will be discovered by Spring to register the DelegatingFilterProxy with the web container. 
package com.tasosmartidis.rest_api_tutorial.config;

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

public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

Finally we have to register our security configuration to WebAppInitializer class we defined in the previous tutorials for our REST API and we are good to go! The updated WebAppInitializer is:
package com.tasosmartidis.rest_api_tutorial.config;


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

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

}

Everything else stays the same, and we are ready to test our application. We will again use POSTMAN for calling the services. First let's try to create new entries with 'bad' credentials

As is shown in the figure above, the attempt to create new diary entry with bad credentials resulted in an HTTP 401 (bad credentials) response. Let's try to do the same request with our credentials for guest users:
  The guest user, which is assigned with the role 'USER' can perform as configured GET, POST and PUT requests and thus the POST operation above was successful! We will update the entry trying one of the other allowed operations:

Again, as expected, the operation was successful! But what would happen if the user guest would attempt a DELETE request?

Access was denied! This is actually what we wanted to see, since only users with role ADMIN are allowed to delete entries in our application. We have the user tasos, who has ADMIN role. Let's try again the request with his credentials:

The DELETE request was successful! And this concludes this tutorial on basic authentication of REST APIs using Spring. Next posts will help us document our REST API and then we will explore other mechanisms for security. As always, the code is available on GitHub.

No comments:

Post a Comment