Authentication Against a REST Service with Spring Security

1. Overview

This article is focused on how to authenticate against a secure REST
API
that provides 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

  • 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 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 an 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 are 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 authentication 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 API 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.

Leave a Reply

Your email address will not be published.