search
REST tutorial

Building a secure REST API with Spring Data REST and Java 8

Moritz Schulze
Building bricks image via Shutterstock

The team at techdev show us how they combined an AngularJS, Java 8 and Spring 4 backend with a REST API to build a office data-tracking tool.

REST APIs are a great interface for both, backend-to-backend communication and the quite popular Single Page Applications (SPAs). At techdev, we built trackr, our own tool to track our working times, vacation requests, travel expenses, invoices and more.

It’s an AngularJS application with a Java 8 and Spring 4 powered backend. The API is secured via OAuth2. If you’re interested, trackr is open source and the code is available here (backend) and here (frontend).

SEE ALSO: How to export data from REST

Frameworks like Ruby On Rails, Play etc. allow rapid development of modern web applications. In this article I’ll show how my experiences with trackr.

Why Spring?

From time to time I read statements on how Spring is too “enterprisy” and is all about some giant XML file, AbstractSingletonProxyFactorys and so on. In my experience those are false. Yes, it is a framework and will force some opinions on you. But at the same time it tries to stay out of your way (annotations vs inheritance of base classes).

Additionally it provides an almost insane amount of functionality that may arise in your applications, including messaging, scheduling or batch processing.

First step: A base Spring Boot application with Gradle

Let’s start with a very basic application (in terms of setup needed) that boots a Spring application context. Two tools will help me with that: Gradle (which I prefer to Maven because it’s less verbose) and Spring Boot.

Spring Boot is incredible at a lot of tasks that help you write a Spring application. Spring Boot provides meta packages for Maven, bundling common dependencies. This means your dependency section does not get cluttered with all those Spring dependencies anymore. There is a plugin that takes care of building a deployable JAR file. Last but maybe most important, Spring Boot has lots of convention over configuration. We’ll see a lot of examples for that.

In most code examples I skip lines that aren’t particularly interesting like the maven repository configuration. You can find the complete code at GitHub.

apply plugin: 'java'
apply plugin: 'spring-boot'
jar {
baseName = 'jaxenter-example'
version = '1.0'
}
dependencies {
compile("org.springframework.boot:spring-boot-starter")
compile("org.springframework.boot:spring-boot-starter-logging")
}

Only two dependencies are needed (and one Gradle plugin). The spring-boot plugin also handles the versions of the dependencies!

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(Application.class);
@Autowired
private SomeService someService;
@Override
public void run(String... args) throws Exception {
String foo = someService.foo();
logger.info("SomeService returned {}", foo);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Lots of the magic of Spring Boot comes from the @EnableAutoConfiguration annotation.

@Service
public class SomeService {
private Logger logger = LoggerFactory.getLogger(SomeService.class);
public String foo() {
logger.debug("Foo has been called");
return "bar";
}
}

This very basic example could already be extended with more functionality, e.g. I could add the @EnableScheduling annotation to the Application class and then declare a @Scheduled method to run some task periodically.

When I use the build task of Gradle I will get a standalone JAR file that starts and runs the complete Spring application. The JAR file could easily be deployed in a Docker container with Java as a microservice! If it would actually provide some service that is.

Second step: Adding a persistence layer and REST service

Now that I have the very base of a Spring application it’s time to add persistence and then a REST service that exposes the entities via HTTP. This step will add only three more dependencies!

When I add HSQL to the classpath the Spring Boot auto configuration will automatically pick it up and create a primary datasource from it. Absolutely no configuration is needed from my side, I can immediately start with the repositories and play around with the code.

To add Spring Data repositories to my application I just add this to my dependencies in build.gradle:

compile("org.springframework.boot:spring-boot-starter-data-jpa")
runtime("org.hsqldb:hsqldb")
compile("org.projectlombok:lombok:1.14.8")

Since entities in JPA still need to be Java Beans and I don’t want to deal with all these boilerplate getters and setters I add Lombok. This means other developers now need a IDE plugin but I think it’s worth the price.

I then add a second configuration class (not necessarily needed in the first step) to tell Spring Boot I want Spring Data repositories.

@Configuration
@EnableJpaRepositories
public class PersistenceConfiguration extends JpaRepositoryConfigExtension {
// I added some code to put two persons into the database here.
}

Since I enabled component scanning in my application it will automatically be picked up. Now I add an entity and a repository for it.

@Entity
@Data
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
}
public interface PersonRepository extends JpaRepository<Person, Long> {
List<Person> findByFirstNameLike(String firstName);
}

And with that I now have access to a database containing a table with persons and I can query them by their first name (and all the other basic methods Spring Data JPA provides).

And now comes the probably most stunning step. I will add one more dependency, change one line in the repository and after that I will be able to

  1. Create, update and delete persons via HTTP
  2. Query persons complete with pagination
  3. Use finders

So, here are the changes

compile("org.springframework.boot:spring-boot-starter-data-rest")

The PersonRepository needs a new annotation:

List<Person> findByFirstNameLike(@Param("firstName") String firstName);

If I start the application the following cURL statements work

curl localhost:8080
curl localhost:8080/persons
curl -X POST -H "Content-Type: application/json" -d "{\"firstName\": \"John\"}"
localhost:8080/persons
curl localhost:8080/persons/search/findByFirstNameLike\?firstName=J%25
curl -X PUT localhost:8080/persons/1 -d "{\"firstName\": \"Jane\"}" -H "Content-Type:
application/json"
curl -X DELETE localhost:8080/persons/1

Note: My entity model is really simple here, I don’t have any relationship between entities, though it’s easy Spring Data REST also can handle that.

Short recap

My IDE tells me I’m at 104 lines of code (including imports – otherwise it’s just 78). We have a fully functional REST API and could very easily add more entities and repositories. We have the full power of Spring Web MVC at our fingertips to add custom controllers. Gradle still can create a single JAR file that can be deployed without the need for a servlet container.

While Spring Boot took a lot of work from us we’re still incredibly flexible. Sometimes you just have to add some configuration (e.g. for another non-embedded datasource) to a properties file or extends some configuration base class and overwrite methods.

Third step: Securing the REST service with Spring Security

My REST API is completely public, let’s secure it so you need some kind of login. As you may have guessed, I just add another dependency and Spring Boot will take care of a basic setup for me.

compile("org.springframework.boot:spring-boot-starter-security")

When I start my application I will now get a log entry like this:

Using default security password: ed727172-deff-4789-8f79-e743e5342356

The user is user and the whole REST API will be secured. So now I can use cURL like this

curl user:ed727172-deff-4789-8f79-e743e5342356@localhost:8080/persons

Of course for real security this is not very helpful. Let’s say I want multiple users and some sort of roles that can be applied to certain methods. For example, only admins should be able to list all people or search for them.

This will require a bit more involvement, I cannot expect Spring Boot to figure out where our users are located and to what roles they map. First of all, I add my own security configuration:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private FakeUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.httpBasic();
http.csrf().disable();
}
}

For the sake of simplicity in this article I disabled CSRF protection. The FakeUserDetailsService is a very simple and not-so-flexible implementation of how to map usernames to our existing persons.

@Service
public class FakeUserDetailsService implements UserDetailsService {
@Autowired
private PersonRepository personRepository;
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
Person person = personRepository.findByFirstNameEquals(username);
if (person == null) {
throw new UsernameNotFoundException("Username " + username + " not
found");
}
return new User(username, "password", getGrantedAuthorities(username));
}
private Collection<? extends GrantedAuthority> getGrantedAuthorities(String
username) {
Collection<? extends GrantedAuthority> authorities;
if (username.equals("John")) {
authorities = asList(() -> "ROLE_ADMIN", () -> "ROLE_BASIC");
} else {
authorities = asList(() -> "ROLE_BASIC");
}
return authorities;
}
}

Here you can see our first real use of Java 8: Lambdas. I found them really helpful when creating mocks like this. Sure, you only save a few lines, but I think it pays. Finally, I change the Spring Data repository to represent our new security requirements.

@Override
@PreAuthorize("hasRole('ROLE_ADMIN')")
Page<Person> findAll(Pageable pageable);
@Override
@PostAuthorize("returnObject.firstName == principal.username or
hasRole('ROLE_ADMIN')")
Person findOne(Long aLong);
@PreAuthorize("hasRole('ROLE_ADMIN')")
List<Person> findByFirstNameLike(@Param("firstName") String firstName);

Only admins may query all persons or search by name. If my username is the first name of a single queried person I’m allowed to access the object. Let’s try it out:

% curl Mary:password@localhost:8080/persons/1
{"timestamp":1414951322459,"status":403,"error":"Forbidden","exception":"org.springfra
mework.security.access.AccessDeniedException","message":"Access is
denied","path":"/persons/1"}

Neat – I get a 403 if I try to access John with Mary‘s account!

(You may notice the “default security password” log entry is still present – but it does not work anymore.)

You may have noticed I only secured the GET requests. What about POST, PUT, DELETE and PATCH? I could override the save and delete method of the repository and add security annotations. For POST and PUT this has a drawback: I cannot differentiate between them! I found using the Spring Data REST event handlers is a good way to deal with these security requirements.

@Component
@RepositoryEventHandler(Person.class)
public class PersonEventHandler {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@HandleBeforeSave
public void checkPUTAuthority(Person person) {
// only security check
}
}

Similarly I can add security checks for CREATE and DELETE. Now Mary can’t update any person!

Fourth step: Adding OAuth

Until now it was child’s play. The software could easily be extended with further entities, web-exposed finders and so on. Each method can be secured. But I want to drive the REST API even further: What about multiple clients? OAuth2 is perfectly suited for that. So let’s add it.

OAuth usually has an authorization server and resource servers. My API is a resource server. Now, I could add the authentication server inside the same application but I don’t like that approach. I’m going to use different applications for that. The only way these two applications need to communicate is through a shared database for the authorization tokens for which I will use SQLite.

The OAuth authorization server application has less dependencies. I leave out logging, Spring Data and Spring Data REST, HSQL and Lombok.

Of course I have to use Spring Security OAuth.

The configuration is pretty straight forward: A database for the tokens and some example clients that I define in memory. As this is not an article on OAuth I will not explain what things like authorizedGrantTypes mean.

@Configuration
@EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws
Exception {
endpoints.tokenStore(tokenStore());
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("curl")
.authorities("ROLE_ADMIN")
.resourceIds("jaxenter")
.scopes("read", "write")
.authorizedGrantTypes("client_credentials")
.secret("password")
.and()
.withClient("web")
.redirectUris("http://github.com/techdev-solutions/")
.resourceIds("jaxenter")
.scopes("read")
.authorizedGrantTypes("implicit");
}
}

The above configuration is used to hand out tokens to access resource servers. For example, the client with the client_credentials grant can get a token directly from the /oauth/token endpoint.

The client with the implicit grant sends a user to the /oauth/authorize page (which will be secured in the next step) where the user can authorize the client to access the data on the resource server. The cool thing is, all these endpoints and needed web pages are in a default version included in Spring Security OAuth!

Let’s add a security configuration so our persons from before can login:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("John").roles("ADMIN").password("password")
.and()
.withUser("Mary").roles("BASIC").password("password");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated()
.and().httpBasic().realmName("OAuth Server");
}
}

The authorization server is now complete. I also need to let our REST API application know it is now a resource server that uses the same token database as the authorization server.

@Configuration
@EnableResourceServer
public class OAuthConfiguration extends ResourceServerConfigurerAdapter {
@Value("${oauth_db}")
private String oauthDbJdbc;
@Bean
public TokenStore tokenStore() {
DataSource tokenDataSource =
DataSourceBuilder.create().driverClassName("org.sqlite.JDBC").url(oauthDbJdbc).build()
;
return new JdbcTokenStore(tokenDataSource);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception
{
resources.resourceId("jaxenter")
.tokenStore(tokenStore());

As you can see this configuration file now takes over configuring HttpSecurity. I use another feature of OAuth, scopes so I can create read-only clients.

The old security configuration can be almost completely discarded, I only keep it for the method security configuration.

Wow, that was a lot of work. Let’s find out if it works. Both application must now be started. I configured the authorization server so it runs on port 8081 and initializes the token database if necessary. When the authorization server is running I can request a token with the following request that uses basic authentication

curl curl:password@localhost:8081/oauth/token\?grant_type=client_credentials

In response I will get a token that I can use like this

curl -H "Authorization: Bearer $token" localhost:8080

I gave the cURL client the admin role and read and write scope, so everything can be executed.

Next, the web client. I access the URL http://localhost:8081/oauth/authorize?client_id=web&response_type=token in a browser. Now I log in as John and get to an authorization page. If I had an actual web client I would have configured the return URL so I would be taken back there.

Since I don’t have one I used my company’s GitHub page, but the token will be included in the redirection URL and I can extract it by hand to use it in cURL. This time the token does not have the write scope and if I would have logged in as Mary I would not be admin. Both works as expected when using the token for corresponding requests!

I had to add a lot of stuff (even a second application!), but you may have noticed I barely touched the REST API. In fact there I only added a configuration and reduced another.

Author
Moritz Schulze
Moritz Schulze is a Software Engineering consultant with techdev solutions.

Comments
comments powered by Disqus