Planning to create a full functioning REST API can be fairly intimidating, but with the right tools it isn’t really that hard. In order to achieve our goal we found Spring Boot to meet all of our requirements. Spring does not only provide easy ways to create RESTful APIs with Spring MVC, but it allows us to conveniently access a database of our choice with Spring Data and Hibernate as well as it provides a whole Security framework with Spring Security which we would need to authenticate the users accessing our API. All of that already sounds like a lot to master, but going step by step this happens almost automatically.
In order to start a project with Spring Boot we can make use of the Spring Initializr (https://start.spring.io). It provides an easy to use UI to configure and generate the project as we want it to be and let us choose all the dependencies we are going to need. We chose our project to be a Maven Project using the latest version of Spring and include the Spring Web Starter dependency. Additionally, we are going to use Eclipse IDE but feel free to use your preferred IDE as we are not relying on any exclusive features of Eclipse.
Before creating the controller, let's observe the situation: Since we already have Spring Web Starter in our dependencies, Spring will automatically start a tomcat server on the default port 8080 once we launch the application. If you want to change the port, just go into your src/main/resources/application.properties
file and change the port to whatever you need.
Now you can already go with your browser to http://localhost:8080
(or the port you just changed it to) and are going to receive an error page:
Since we don’t have a controller to handle our request yet, Spring responds with its default error controller. So let’s create a controller:
All we have to do is to create a new class and annotate it with Springs built in annotation @RestController
.
Now we can declare as many methods on the controller as we like, and those we annotate with @GetMapping(value=”/api-path”)
will automatically be called once a GET request to the specified path is made. Similarly we would use @PostMapping for POST requests and so on…
As you can see in the example above, we declare a method with a return type of type String
. Once our method returns, Spring automatically sets a Content-Type
header of text/plain
, sets a http status code of 200
and off goes the response. Spring does all of this for us, but we can manipulate the request as well as the response directly if we choose so. Just tell spring to give us the request and response objects by declaring them as parameters of our method.
This comes in handy, especially when you want to return a specific status code other then 200
, or want to access the request headers for example. Luckily, we are not limited to returning Strings from our methods and we can choose to return objects as well. Spring will automatically convert the object to JSON and set the appropriate Content-Type
header.
As you would expect, the response from this method is {"message":"pong"}
. One of the last important features we frequently use throughout our controllers, is the convenient handling of path variables and request parameters. Spring provides two annotations @PathVariable
and @RequestParam(value=”name”, defaultValue=”default”)
for that purpose. Simply give the method a parameter, annotate it accordingly, and it will receive the value, once a request is made.
This approach of creating our API is known as “API-Second” approach, which means that we are implementing the methods first before assigning them to a specific path of our API. This may not be the best way to go for designing APIs but as our whole project and its requirements evolved in some way, it was the way that suited our immediate needs best.
Now that we are able to handle requests to our API, we want the possibility to create a user, store all of its properties in a database and modify these properties once we created it. For this purpose we need to include the Spring Data dependency in the pom.xml
file.
In order to achieve the behaviour mentioned above, we first create a model of the user, a class basically, that we have to annotate accordingly with the annotations provided by the JPA. We can now choose which properties we want to be represented by a column, what the column should be named or if the property should be unique for example. Eventually Spring Data will create a new table from our user model, so everything we can configure a SQL table to look and behave like, we can do with our model as well.
As we want the ability to store the created model in a database, we extend Springs JpaRepository
interface with our own interface MovieUserRepository
and annotate it with @Repository
. Through this repository we can tell Spring by which properties we want to be able to select and find users in the database. As long as we provide the right method signature, Spring will magically implement the methods to work as intended.
Now, as we obviously cannot instantiate interfaces and instead want spring to implement the methods of our repository, we make use of a service that handles database interactions for us. Thus, we create a class, annotate it with @Service
and autowire an instance of the MovieUserRepository
(implemented by Spring) into it by using one of Springs powerful annotations @Autowired
. Spring will inject the MovieUserRepository
at runtime. All we have to do now is to create some methods to act on our repository which will then further interact with the database. Methods that save or delete Users from our database additionally need to be annotated with @Transactional
.
In order to get all of this to work, we still need to tell Spring how to connect to our database. We do this by setting some properties in the application.properties
file. Apart from url, username and password we give Spring the driver of our database. It is important to add the driver as dependency to our pom.xml
, otherwise Spring doesn’t know where to look for it. As we are using an oracle database, this is the driver we need to include:
In addition, we need to tell Spring to update the database, based on our model, and create a table for it.
As you can see, through Spring Data we are completely disconnected from any specifics of different databases and could easily switch between databases just by providing a corresponding driver.
To finally see all of this in action, let's add a signUp()
method to our REST controller and map it to a POST
request on “/account”
. In order to interact with the database we need access to the MovieUserService
inside the controller. We achieve this by autowiring the service into the controller by annotating it with @Autowired
. Additionally, we create a new SignupResponse
model so we can return a useful message alongside a property success
, which indicates, if the operation was successful or not.
The controller now looks like this:
Even though the implementation above already works, it still needs some improvements, such as checking if the parameters username and password are empty or match a specific pattern, if the user already exists in the database as well as better exception handling. But as this is not a very Spring related matter, it is left to you to hammer out the details to match your requirements.
In order to implement authentication for our users, we found JWT tokens to meet our requirements. Authentication via JWT is not only an industry standard, but it allows our API to be stateless as well. The behaviour we want is as follows: A user retrieves a JWT token by providing his username and password. From now on, all requests to the API he makes include a header with the token. Thus every request handler of our API cannot only be sure that the request is indeed made by an authorized user, it can retrieve the username from the token as well.
We achieve all of this by adding Spring Security to our project dependencies and customizing some of its configurations as well as adding some filters to handle the JWT tokens.
First, we need to extend Springs WebSecurityConfigurerAdapter
to create our custom SecurityConfiguration
class. Here, we can configure which filters we want to apply to which requests, as well as some other security related settings such as CORS handling.
Furthermore, we provide a CustomAuthenticationProvider
which extends Springs AuthenticationProvider
to manually check if the credentials a user wants to authenticate with, match the ones stored in the database.
To finally get to the JWT token handling, let's look at two filters we declare: At first, let’s declare a few constants we are going to use.
And add the libraries we are using for generating and parsing JWT tokens.
Now we are going to create the JwtAuthenticationFilter
which extends Springs UsernamePasswordAuthenticationFilter
. This filter is given a specific URL and method to filter on, so whenever a user wants to receive a JWT by sending a request with his username and password to this URL, the filter attempts to authenticate the user by calling the attemptAuthentication()
method. Depending on whether the authentication succeeds or not, it calls the appropriate methods to handle the situation. If the authentication is successful it then generates the JWT token and returns it in the response.
The second filter filters every request but the ones we explicitly excluded in the SecurityConfiguration
and checks if they contain a header with a valid JWT token. If the request passes the filter, it moves on to the REST Controller. Otherwise a 403 Forbidden
response code is returned.
Note, that we do not have to call all those methods ourselves, instead we just provide the implementations of what we want to happen if a user attempts to authenticate, for example. Usually we just have to return null
, if we want the authentication to fail. Spring then acts accordingly and calls the unsuccessfulAuthentication()
method we implemented. Furthermore, we almost at all times have the possibility to interact with the request and the response in order to get or set headers as well as status codes, for example.
Now that we can create and authorize users, we need to address some security concerns: As you may already have noticed, we are storing all of our users data in plain text, even the passwords. What we really want is to only store a hash of the password. Fortunately, we can use Spring Security’s built in BCryptPasswordEncoder
for this purpose. All we have to do is modify the MovieUser
class a little, so if a password is set, it gets hashed automatically. In addition, we provide a matchPassword()
method, to determin if a password matches the stored hash.
In order for our authentication to work with the hashed passwords, we need to modify the CustomAuthenticationProvider
class as well, so it uses the newly implemented matchPassword()
method.