Authentication against a RESTful Service with Spring Security

1. Overview

This article is focused on how to authenticate against a secure REST API that provides security services – mainly, a RESTful User Account and Authentication Service.

2. The Goal

First, let’s go over the actors – the typical Spring Security enabled application needs to authenticate against something – that something can be a database, LDAP or it can be a REST service. The database is the most common scenario; however, a RESTful UAA (User Account and Authentication) Service can work just as well.

For the purpose of this article, the REST UAA Service will expose a single GET operation on /authentication, which will return the Principal information required by Spring Security to perform the full authentication process.

3. The Client

Typically, a simple Spring Security enabled application would use a simple user service as the authentication source:

<authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref="customUserDetailsService" />
</authentication-manager>

This would implement the org.springframework.security.core.userdetails.UserDetailsService and would return the Principal based on a provided username:

@Component
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) { 
      ...
    }
}

When a Client authenticates against the RESTful UAA Service, working only with the username will no longer be enough – the client now needs the full credentials – both username and password – when it’s sending the authentication request to the service. This makes perfect sense, as the service itself is secured, so the request itself needs to contain the authentication credentials in order to be handled properly.

From the point of view or Spring Security, this cannot be done from within loadUserByUsername because the password is no longer available at that point – we need to take control of the authentication process sooner.

We can do this by providing the full authentication provider to Spring Security:

<authentication-manager alias="authenticationManager">
    <authentication-provider ref="restAuthenticationProvider" />
</authentication-manager>

Overriding the entire authentication provider gives us a lot more freedom to perform custom retrieval of the Principal from the Service, but it does come with a fair bit of complexity. The standard authentication provider – DaoAuthenticationProvider – has most of what we need, so a good approach would be to simply extend it and modify only what is necessary.

Unfortunately this is not possible, as retrieveUser – the method we would be interested in extending – is final. This is somewhat unintuitive (there is a JIRA discussing the issue) – it looks like the design intention here is simply to provide an alternative implementation which is not ideal, but not a major problem either – our RestAuthenticationProvider copy-pastes most of the implementation of DaoAuthenticationProvider and rewrites what it needs to – the retrieval of the principal from the service:

@Override
protected UserDetails retrieveUser(String name, UsernamePasswordAuthenticationToken auth){
    String password = auth.getCredentials().toString();
    UserDetails loadedUser = null;
    try {
        ResponseEntity<Principal> authenticationResponse = 
            authenticationApi.authenticate(name, password);
        if (authenticationResponse.getStatusCode().value() == 401) {
            return new User("wrongUsername", "wrongPass", 
                Lists.<GrantedAuthority> newArrayList());
        }
        Principal principalFromRest = authenticationResponse.getBody();
        Set<String> privilegesFromRest = Sets.newHashSet(); 
        // fill in the privilegesFromRest from the Principal
        String[] authoritiesAsArray = 
            privilegesFromRest.toArray(new String[privilegesFromRest.size()]);
        List<GrantedAuthority> authorities = 
            AuthorityUtils.createAuthorityList(authoritiesAsArray);
        loadedUser = new User(name, password, true, authorities);
    } catch (Exception ex) {
        throw new AuthenticationServiceException(repositoryProblem.getMessage(), ex);
    }
    return loadedUser;
}

Let’s start from the beginning – the HTTP communication with the REST Service – this is handled by the authenticationApi – a simple API providing the authenticate operation for the actual service. The operation itself can be implemented with any library capable of HTTP – in this case, the implementation is using RestTemplate:

public ResponseEntity<Principal> authenticate(String username, String pass) {
   HttpEntity<Principal> entity = new HttpEntity<Principal>(createHeaders(username, pass))
   return restTemplate.exchange(authenticationUri, HttpMethod.GET, entity, Principal.class);
}

HttpHeaders createHeaders(String email, String password) {
    HttpHeaders acceptHeaders = new HttpHeaders() {
        {
            set(com.google.common.net.HttpHeaders.ACCEPT, 
                MediaType.APPLICATION_JSON.toString());
        }
    };
    String authorization = username + ":" + password;
    String basic = new String(Base64.encodeBase64
        (authorization.getBytes(Charset.forName("US-ASCII"))));
    acceptHeaders.set("Authorization", "Basic " + basic);

    return acceptHeaders;
}

A FactoryBean can be used to set up the RestTemplate in the context.

Next, if the authentication request resulted in a HTTP 401 Unauthorized, most likely because of incorrect credentials from the client, a principal with wrong credentials is returned so that the Spring Security authentication process can refuse them:

return new User("wrongUsername", "wrongPass", Lists.<GrantedAuthority> newArrayList());

Finally, the Spring Security Principal needs some authorities – the privileges which that particular principal will have and use locally after the authentication process. The /authenticate operation had retrieved a full principal, including privileges, so these need to be extracted from the result of the request and transformed into GrantedAuthority objects, as required by Spring Security.

The details of how these privileges are stored is irrelevant here – they could be stored as simple Strings or as a complex Role-Privilege structure – but regardless of the details, we only need to use their names to construct the GrantedAuthoritiy objects. After the final Spring Security principal is created, it is returned back to the standard authentication process:

List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(authoritiesAsArray);
loadedUser = new User(name, password, true, authorities);

4. Testing the Authentication Service

Writing an integration test that consumes the authentiction REST service on the happy-path is straightforward enough:

@Test
public void whenAuthenticating_then200IsReceived() {
    // When
    ResponseEntity<Principal> response = 
        authenticationRestTemplate.authenticate("admin", "adminPass");

    // Then
    assertThat(response.getStatusCode().value(), is(200));
}

Following this simple test, more complex integration tests can be implemented as well – however this is outside of the scope of this post.

5. Conclusion

This article explained how to authenticate against a REST Service instead of doing so against a local system such as a database. For a full implementation of a secure RESTful service which can be used as an authentication provider, check out the github project.
 

Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


eight + 4 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books