Spring Security for a REST API

1. Overview

In this tutorial, we learn how to Secure a REST API using Spring and Spring Security 5.

We will set up the security using Java configuration and will be using a Login and Cookie approach for authentication.

Further reading:

Spring Security Authentication Tutorial

How to build a production-grade Registration process for new users, and Login flow for existing users.

Read more

Granted Authority Versus Role in Spring Security

A quick guide to the difference between a granted authority and a role in Spring Security.

Read more

Spring Security Basic Authentication

Set up Basic Authentication in Spring – the XML Configuration, the Error Messages, and example of consuming the secured URLs with curl.

Read more

2. Enable Spring Security

The architecture of Spring Security is based entirely on Servlet Filters. Therefore, this comes before Spring MVC in regards to the processing of HTTP requests.

The simplest option to register the Spring security filter is by annotating our config class with @EnableWebSecurity:

@Config
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {

    // ...
}

For a non-Spring Boot application, we can extend the AbstractSecurityWebApplicationInitializer and pass our config class in its constructor:

public class SecurityWebApplicationInitializer
  extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(SecurityJavaConfig.class);
    }
}

Or else we can declare it in web.xml of the application:

<filter>
   <filter-name>springSecurityFilterChain</filter-name>
   <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
   <filter-name>springSecurityFilterChain</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

We should name the filter ‘springSecurityFilterChain’ to match the default bean created by Spring Security in the container.

Note that the defined filter is not the actual class implementing the security logic. Instead, it is a DelegatingFilterProxy that delegate the filter’s methods to an internal bean. This is done so that the target bean can still benefit from the Spring context lifecycle and flexibility.

The URL pattern used to configure the Filter is /* even though the entire web service is mapped to /api/*. This gives the security configuration an option to secure other possible mappings as well if required.

3. Spring Security Java Configuration

We can do Security configurations entirely in a Java class by creating a config class that extends WebSecurityConfigurerAdapter and annotating it with @EnableWebSecurity:

@Configuration
@EnableWebSecurity
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {

    // ...
}

Now, let’s create users with different roles in SecurityJavaConfig that we will be using to authenticate our API endpoints:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN")
        .and()
        .withUser("user").password(encoder().encode("userPass")).roles("USER");
}

@Bean
public PasswordEncoder  encoder() {
    return new BCryptPasswordEncoder();
}

Next, let’s configure security for our API endpoints:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .csrf().disable()
    .exceptionHandling()
    .authenticationEntryPoint(restAuthenticationEntryPoint)
    .and()
    .authorizeRequests()
    .antMatchers("/api/foos").authenticated()
    .antMatchers("/api/admin/**").hasRole("ADMIN")
    .and()
    .formLogin()
    .successHandler(mySuccessHandler)
    .failureHandler(myFailureHandler)
    .and()
    .logout();
}

3.1. The http Element

The http element is the starting element for security configuration and provides us with fluent and flexible methods to configure security.

In our implementation, we are creating secured mappings /api/foos and /api/admin/** using antMatchers. 

The /api/foos pattern is accessible to any authenticated user. On the other hand, /api/admin/** will only be accessible to ADMIN role users.

3.2. The Entry Point

In a standard web application, the authentication process may automatically trigger when an un-authenticated client tries to access a secured resource. This process usually redirects to a login page so that the user can enter credentials.

However, for a REST Web Service,this behaviour doesn’t make much sense. We should be able to authenticate only by a request to the correct URI and if the user is not authenticated all requests should simply fail with a 401 UNAUTHORIZED status code.

Spring Security handles this automatic triggering of the authentication process with the concept of an Entry Point – this is a required part of the configuration, and can be injected via the authenticationEntryPoint method.

Keeping in mind that this functionality doesn’t make sense in the context of the REST Service, we define the new custom entry point to simply return 401 when triggered:

@Component
public final class RestAuthenticationEntryPoint
  implements AuthenticationEntryPoint {

    @Override
    public void commence(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException authException) throws IOException {

        response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
          "Unauthorized");
    }
}

A quick side note here is that the 401 is sent without the WWW-Authenticate header, as required by the HTTP Spec. We can, of course, set the value manually if we need to.

3.3. The Login Form for REST

There are multiple ways to do Authentication for a REST API. One of the defaults Spring Security provides is Form Login – which uses an authentication processing filter – org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.

The formLogin element will create this filter and also provides additional methods successHandler and failureHandler to set our custom authentication success and failure handlers respectively.

Note that for a standard web application, the @EnableWebSecurity annotation configures a lot of default configurations itself.

3.4. Authentication Should Return 200 Instead of 301

By default, form login will answer a successful authentication request with a 301 MOVED PERMANENTLY status code; this makes sense in the context of an actual login form which needs to redirect after login.

However, for a RESTful web service, the desired response for a successful authentication should be 200 OK.

We do this by injecting a custom authentication success handler in the form login filter, to replace the default one. The new handler implements the exact same login as the default org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler with one notable difference – it removes the redirect logic:

public class MySavedRequestAwareAuthenticationSuccessHandler
  extends SimpleUrlAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(
      HttpServletRequest request,
      HttpServletResponse response,
      Authentication authentication)
      throws ServletException, IOException {

        SavedRequest savedRequest
          = requestCache.getRequest(request, response);

        if (savedRequest == null) {
            clearAuthenticationAttributes(request);
            return;
        }
        String targetUrlParam = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl()
          || (targetUrlParam != null
          && StringUtils.hasText(request.getParameter(targetUrlParam)))) {
            requestCache.removeRequest(request, response);
            clearAuthenticationAttributes(request);
            return;
        }

        clearAuthenticationAttributes(request);
    }

    public void setRequestCache(RequestCache requestCache) {
        this.requestCache = requestCache;
    }
}

3.5. Failed Authentication Should Return 401 Instead of 302

Similarly – we configured the authentication failure handler – the same way we did with the success handler.

Luckily – in this case, we don’t need to actually define a new class for this handler – the standard implementation – SimpleUrlAuthenticationFailureHandler – does just fine.

3.6. The Authentication Manager and Provider

The authentication process uses an in-memory provider to perform authentication. This is meant to simplify the configuration as a production implementation of these artifacts is outside the scope of this article.

We have created two users namely user having role USER and admin with role ADMIN.

3.7. Finally – Authentication Against the Running REST Service

Now let’s see how we can authenticate against the REST API for different users.

The URL for login is /login – and a simple curl command performing login for the user of name user and password userPass would be:

curl -i -X POST -d username=user -d password=userPass
http://localhost:8080/spring-security-rest/login

This request will return the Cookie which we can use for any subsequent request against the REST Service.

We can use curl to authentication and store the cookie it receives in a file:

curl -i -X POST -d username=user -d password=userPass -c /opt/cookies.txt
http://localhost:8080/spring-security-rest/login

Then we can use the cookie from the file to do further authenticated requests:

curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt
http://localhost:8080/spring-security-rest/api/foos

Since the user can access the /api/foos/ endpoint, this authenticated request will correctly *result in a 200 OK:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jul 2013 20:31:13 GMT

[{"id":0,"name":"JbidXc"}]

Similarly, for admin user we can use curl for authentication:

curl -i -X POST -d username=admin -d password=adminPass -c /opt/cookies.txt
http://localhost:8080/spring-security-rest/login

and then updated cookies for accessing admin endpoints /api/admin/*:

curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt
http://localhost:8080/spring-security-rest/api/admin/x

Since admin user can access endpoint /api/admin/* this will result in success response:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=ISO-8859-1
Content-Length: 5
Date: Mon, 15 Oct 2018 17:16:39 GMT

Hello

4. The XML Security Configuration

We can also do all the above security configuration using XML instead of Java configuration:

<http entry-point-ref="restAuthenticationEntryPoint">
    <intercept-url pattern="/api/admin/**" access="ROLE_ADMIN"/>

    <form-login
      authentication-success-handler-ref="mySuccessHandler"
      authentication-failure-handler-ref="myFailureHandler" />

    <logout />
</http>

<beans:bean id="mySuccessHandler"
  class="org.rest.security.MySavedRequestAwareAuthenticationSuccessHandler"/>
<beans:bean id="myFailureHandler" class=
  "org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>

<authentication-manager alias="authenticationManager">
    <authentication-provider>
        <user-service>
            <user name="admin" password="adminPass" authorities="ROLE_ADMIN"/>
            <user name="user" password="userPass" authorities="ROLE_USER"/>
        </user-service>
    </authentication-provider>
</authentication-manager>
 +
Most of the configuration is done using the security namespace. To enable this, we need to define the schema locations.

The namespace is designed so that it expresses the common use cases of Spring Security while still providing hooks for raw beans to accommodate more advanced scenarios.

5. Conclusion

In this tutorial, we covered the basic security configuration and implementation for a RESTful Service using Spring Security 5.

We learned how to do Security configuration for our REST API entirely via Java config and also looked at its web.xml configuration alternative.

Next, we discussed how to create users and roles for our secured application and also how to map these users to specific endpoints of our application.

Finally, we also looked into creating custom Authentication Entry Point and a custom Success Handler that gave our application a better flexibility in terms of controlling security.

The full implementation is available over on Github.

Leave a Reply

Your email address will not be published.