This is the second part to a series of posts called Securing Microservices with Auth0. If you missed the previous post, I would suggest you go back and read that post first.
Overview
In this part of the Securing Microservices with Auth0 series, we're going to be creating the Resource Service microservice. The Resource Service will be our applications REST API and will perform CRUD operations on specific users Todos. By this I mean we will be able to:
- C: Create (POST)
- R: Read (GET)
- U: Update (PATCH)
- D: Delete (DELETE)
At first, this service will be insecure and will not need any sort of authentication. It's important that we see the issues that come with insecure applications both from the developer perspective and the user perspective. After creating our Auth Service, which will be a different microservice in another post in this series, we will then perform authorization on requests being sent to our Resource Service.
You can also go ahead and play around with the code for this post. This branch, bbenefield89/tutorial_pt2
, is the UI portion and the insecure RESTful API (Resource Service).
Creating the Resource Service
For this series, I've decided to go with the Spring Framework to create our backend. Microservices are not Java/Spring Framework specific and we can just as easily create our microservices in any language that has the ability to create a web server and make HTTP requests. This means we could potentially create our Resource Service using Express the Node Web Framework and then turn around and create our Auth Service using Django the Python Web Framework. This is one of many of the advantages of going with a microservice architecture when creating your applications.
Enough talk, it's time for action! Let's head over to Spring Initializr where you can quickly create the boilerplate code for your Spring application.
When you land on the Spring Initializr page go ahead and enter in the basic information for your project. As an example, my projects information will look like this:
And my chosen dependencies will be:
Go ahead and click on the green button at the bottom that says Generate the project
. This will prompt you to download your project as a zip folder.
Unzip your project, feel free to discard the zipped folder, and let's open up our project in our favorite IDE and get to work.
Inside our Resource Service
Now that we're ready to go, let's find our first file at TodoApp_API/src/main/resources/application.properties
and rename that to application.yml
as I'm a fan of YAML
when it comes to Springs configuration properties.
Inside our application.yml
file you'll notice it's empty. Go ahead and place the following text inside:
server:
port: 8080
It's not much, and to be honest Spring defaults it's PORT to 8080 but I like to be as clear as possible, especially when we have multiple services for the same application.
Creating the Todo
Entity
We've already discussed the application and yes, this is going to be yet another todo app but I believe creating something you're familiar with is best when learning about new technology. Might as well focus on the technology instead of the logic.
Create a new package at TodoApp_API/src/main/java/${}/${}/TodoApp_API
and name it Entities (TodoApp_API/src/main/java/${}/${}/TodoApp_API/Entities
). This package is where we're going to create all of our Entities which are basically just a Java representation of a row in our DB.
Inside the Entities folder, create a new Java file and name it Todo.java
and inside of it place the following code (filling in the ${} with your own path). Be sure to read the comments as I'll explain some of the code as we go.
Todo.java
package ${}.${}.TodoApp_API.Entities;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* This annotation comes from "Lombok" and allows us to forget about writing
* a lot of boilerplate code like "Constructors/Getters/Setter"
*/
@Data
// Creates this class as a Bean to be picked up by Spring
@Entity
public class Todo {
// Lets JPA know this is the unique identifier for our DB
@Id
// Sets the value that should be automatically generated for our ID in the DB
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
// We'll use the users' email address to find a user's todos
private String userEmailAddress;
/**
* Notice we don't have to write anything else
* Lombok will take care of this for us
*/
}
Creating the TodoRepository
"Repository"
The Repository for an Entity is going to be an interface that will extend another interface that comes with a ton of helpful methods to perform all of our CRUD operations.
Create another package named TodoRepositories
and place it at TodoApp_API/src/main/java/${}/${}/TodoApp_API/Repositories
. Inside create a new file named TodoRepository.java
and inside place the following code:
TodoRepository.java
package ${}.${}.TodoApp_API.Repositories;
import ${}.${}.TodoApp_API.Entities.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Sets this interface up to be found by Spring
* Later on we'll be taking advantage of the @Autowired annotation where this interface will then become a
* concrete class
*/
@Repository
/**
* Our repository interface needs to extend the JpaRepository interface and pass along two arguments
* 1. The Entity class that this repository is responsible for
* 2. The id data type we chose for the Entity this Repository is responsble for
* In this example, we've chosen to create our id as a Long data type
*/
public interface TodoRepository extends JpaRepository<Todo, Long> {
/**
* This is a custom method we'll be using to get a list of todos depending on the users email address
* JPA supports a type of DSL where we can create methods that relate to an Entity by using keywords
* 1. "findAll": returns a List of Todo
* 2. "By": This signifies that we are going to be giving something specific to look for in the DB
* 3. "UserEmailAddress": Find a Todo that contains the correct "userEmailAddress" in the DB
*/
public List<Todo> findAllByUserEmailAddress(String userEmailAddress);
/**
* Another custom method. This method will take the ID of a Todo and the users email address to return a
* single Todo
*/
public Todo findByIdAndUserEmailAddress(Long todoId, String userEmailAddress);
/**
* This custom method will delete a single Todo depending on the ID and the userEmailAddress
*/
public void deleteByIdAndUserEmailAddress(Long todoId, String userEmailAddress);
}
That's it for our Repository. We've only added a few methods but JpaRepository will still give us access to a lot more of the inner methods that we haven't defined.
Creating the TodoService
"Service"
The idea behind a Service in this context is to bridge the gap between a Controller and a Repository. This is also where you will write your business logic. Splitting up your code like this keeps things small and typically easier to reason about.
Go ahead and create another package named Services
and place it at TodoApp_API/src/main/java/${}/${}/TodoApp_API/Services
. Inside, create a file named TodoService.java
.
TodoService.java
package ${}.${}.TodoApp_API.Services;
import ${}.${}.TodoApp_API.Entities.Todo;
import ${}.${}.TodoApp_API.Repositories.TodoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Lets Spring know to pick this up at runtime
* You've probably noticed that so far we haven't really told Spring when to use any of our classes and that's
* because of "Component Scanning". To learn more about the Component Scanning go to the following URL
* https://www.baeldung.com/spring-component-scanning
*/
@Service
public class TodoService {
TodoRepository todoRepository;
/**
* @Autowired annotation sets this constructor to be called when booting our application and will automagically
* inject any dependencies that we specify in the arguments
* This is also known as "Dependency Injection" and is one of the more attractive aspects of the Spring Framework
*/
@Autowired
public TodoService(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
// Returns a List of all of a users Todos
public List<Todo> findAllByUserEmailAddress(String userEmailAddress) {
return todoRepository.findAllByUserEmailAddress(userEmailAddress);
}
// Return a single Todo
public Todo findByIdAndUserEmailAddress(Long todoId, String userEmailAddress) {
return todoRepository.findByIdAndUserEmailAddress(todoId, userEmailAddress);
}
// Create/Update a new Todo and returns that Todo
public Todo save(String userEmailAddress, Todo todo) {
todo.setUserEmailAddress(userEmailAddress);
return todoRepository.save(todo);
}
// Delete a Todo
public void deleteByIdAndUserEmailAddress(Long todoId, String userEmailAddress) {
todoRepository.deleteByIdAndUserEmailAddress(todoId, userEmailAddress);
}
}
Creating the TodoController
"Rest Controller"
Okay, we're almost finished with our first pass on our Resource Service. We just need to create the Controller that will determine our services' URL endpoints.
Create your final package named Controllers
and place it at TodoApp_API/src/main/java/${}/${}/TodoApp_API/Controllers
. Inside, create yet another file and name it TodoController.java
and place the following code inside.
TodoController.java
package io.github.bbenefield89.TodoApp_API.Controllers;
import io.github.bbenefield89.TodoApp_API.Entities.Todo;
import io.github.bbenefield89.TodoApp_API.Services.TodoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private TodoService todoService;
@Autowired
public TodoController(TodoService todoService) {
this.todoService = todoService;
}
// Returns a List of Todos
@GetMapping("/{userEmailAddress}")
public List<Todo> findAllByUserEmailAddress(@PathVariable String userEmailAddress) {
return todoService.findAllByUserEmailAddress(userEmailAddress);
}
// Returns a single Todo
@GetMapping("/{userEmailAddress}/{todoId}")
public Todo findByIdAndUserEmailAddress(@PathVariable String userEmailAddress, @PathVariable Long todoId) {
return todoService.findByIdAndUserEmailAddress(todoId, userEmailAddress);
}
// Creates a new Todo
@PostMapping("/{userEmailAddress}")
public Todo save(@PathVariable String userEmailAddress, @RequestBody Todo todo) {
return todoService.save(userEmailAddress, todo);
}
// Deletes a single Todo
@DeleteMapping("/{userEmailAddress}/{todoId}")
public void deleteByIdAndUserEmailAddress(@PathVariable String userEmailAddress, @PathVariable Long todoId) {
todoService.deleteByIdAndUserEmailAddress(todoId, userEmailAddress);
}
}
Manually testing our endpoints
Now that we've written our endpoints it's time we test them to make sure everything works. I would suggest downloading Postman for API testing.
Let's go ahead and start making some HTTP requests.
POST localhost:8080/api/todos/user@gmail.com
(Create Todo)
Example Request
{
"title": "Get a haircut",
"userEmailAddress": "user@gmail.com"
}
Example Response
{
"id": 1,
"title": "Get a haircut",
"userEmailAddress": "user@gmail.com"
}
GET localhost:8080/api/todos/user@gmail.com
(Get All Todos)
Example Request
Nothing required
Example Response
[
{
"id": 1,
"title": "Get a haircut",
"userEmailAddress": "user@gmail.com"
}
]
GET localhost:8080/api/todos/user@gmail.com/1
(Get a Single Todo)
Example Request
Nothing required
Example Response
{
"id": 1,
"title": "Get a haircut",
"userEmailAddress": "user@gmail.com"
}
DELETE localhost:8080/api/todos/user@gmail.com/1
(DELETE a Single Todo)
Example Request
Nothing required
Example Response
Nothing returned
Great, everything works! The only problem now is that our endpoints aren't secured (to be fair we don't really have any users either). This means, you as the user hacker_man@gmail.com
could easily access my data and vice versa.
Conclusion
In this post, you didn't learn much about Spring or Auth0 but you did learn about creating RESTful endpoints which is an important step to the process. Not to mention, you now see how easy it is for insecure endpoints to be accessed by the wrong people.
In the next section of this series (link coming soon), you'll get an introduction on how to create a very simple Auth Service that uses:
Spring Security: Prevent access to users that are not authed
Prehandle: A method to intercept requests to endpoints that we can use to run logic before all requests (the secret sauce of our auth)
Top comments (0)