Creating Spring Boot REST api app with basic authentication – How To

Read Time:8 Minute, 21 Second

Hi Java enthusiasts! I will show you how to quickly create Spring Boot app which supports basic authentication and communicates via rest. Enjoy your reading!

What do we need here? I will be using Intelij Idea (you can use Eclipse if you wish) along with jdk 11 and Gradle as a project building tool (again, you can use eg. Maven if that’s your favorite one).

Run InteliJ and create new Gradle project. Add the following dependencies to build.gradle file.

compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.1.5.RELEASE'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.1.5.RELEASE'

Refresh gradle project.

Now let’s prepare the project tree, I’ve done it like this:

Firstly we need to create main class which will start up the Spring Boot application.

BasicRestApp.java

package com.rocketzki;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BasicRestApp {
    public static void main(String[] args) {
        SpringApplication.run(BasicRestApp.class, args);
    }


}

The @SpringBootAnnotation is an alias for @Configuration @EnableAutoConfiguration @ComponentScan annotations with their default attributes. It enables auto-configuration mechanisms, component scan and allows to add extra beans to the app context. So the application scans the main package, which in this case is com.rocketzki searching for components (like beans, components, services, repositories etc.).

Next thing we need to do is the configuration. I have coded something like this:

package com.rocketzki.config;

// imports ...

@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
@EnableWebSecurity
public class WebSecuritySettings extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(AuthenticationManagerBuilder authentication) throws Exception {
        authentication
                .inMemoryAuthentication()
                .withUser("johny")
                .password(passEncrypt().encode("ThePass12")).roles("USER")
                .and()
                .withUser("mary")
                .password(passEncrypt().encode("ThePass12")).roles("ADMIN");
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().and().authorizeRequests()
                .antMatchers("/rest/laptop/**", "/rest/laptops").permitAll()
                .antMatchers("/rest/admin/**", "/rest/laptop/add").hasRole("ADMIN")
                .and().csrf().disable().headers().frameOptions().disable().and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().logout();
    }

    @Bean
    public PasswordEncoder passEncrypt() {
        return new BCryptPasswordEncoder();
    }


}

The class is annotated with following annotations:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)

First one indicated that the following class is a configuration component and will be treated as such by the component scanner. The second one enables Spring Security features. Last but not at least – the WebSecuritySettings class extends WebSecurityConfigurerAdapter which provides base for Spring Security configuration object instantiation. @EnableGlobalMethodSecurity(prePostEnabled = true) – this annotation enforces authorization before method – each method invocation has to be authorized first.

The core and most interesting stuff lays within the methods. Let’s discuss them. The first overriden method sets up the AuthenticationBuilderManager – it will store our users. This is quite straightforward. The builder method built-in the class allows us to add users, their passwords and roles. I have also used BCryptPasswordEncoder by creating simple bean method within the configuration class – it will encode our password within the memory. User credentials aren’t usually hardcoded this way (they shouldn’t be at least) but this solution is just enough for sake of the demo.

The other method builds HttpSecurity object – it orchestrates our security details. I have set it like this:

httpBasic() – sets up the HTTP Basic authentication, authorizeRequests() and antMatchers(…) – we can specify custom permissions to URLs for our users based on their roles, .csrf().disable() – prevents from CSRF attacks, .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) – session configuration – here we are enforcing session creation, .logout()  – configures logout feature – user need to access /logout to logout the current session.

Controllers – our application needs a boundary through which it can communicate with the outer world. In this case I have created two controllers. First one just returns simple string for our admin user:

package com.rocketzki.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AdminController {

    @GetMapping("/rest/admin/me")
    @ResponseBody
    public String getAdminResponse() {
        return "Howdy you Admin Person.";
    }


}

Second one is more complex:

package com.rocketzki.controller;

// imports ...

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/rest")
public class LaptopController {

    private LaptopService laptopService;

    @Autowired
    private void setLaptopService(LaptopService laptopService) {
        this.laptopService = laptopService;
    }

    @GetMapping("/laptops")
    @ResponseBody
    public List<Laptop> getAllLaptops() {
        return laptopService.getLaptops();
    }

    @GetMapping("/laptop/{id}")
    @ResponseBody
    public Laptop getById(@PathVariable("id") Integer id) {
        return laptopService.getById(id);
    }

    @PostMapping("/laptop/add")
    @ResponseStatus(HttpStatus.CREATED)
    @ResponseBody
    public Map addOne(@RequestBody Laptop laptop) {
        laptopService.addLaptop(laptop);
        return Map.of("massage", "Laptop: " + laptop.toString() + " has been added.");
    }


}

What do we have here? The class is annotated as a rest controller and the mapping is set to /rest so that server knows that each method within the controller can be accesses with /rest/ prefix – eg. localhost:8080/rest/laptops

I have added the LaptopService dependency and by using method dependency injection wiring the service to the controller. The next three methods are responsible for: getting list of all the laptops, getting a specific laptop, adding laptop sent by REST. Important thigs here are the annotations – @PostMapping indicates that the method is responsible for POST Http method, @GetMapping – for GET method. You may also encounter @RequestMapping(value =”PATH”, RequestMethod.GET) means actually the same, it’s the older convention but still correct and in use.

@ResponseStatus(HttpStatus.CREATED)  – after successful laptop creation the application will respond with Http CREATED status – 201.

@PathVariable(“id”) – which is the method parameter annotation – binds the annotated variable to the one specified in the route – @GetMapping(“/laptop/{id}“) – “id” in this case.

Domain – the classes that describes laptop – this package contains classes responsible for storing laptop information, service and domain model.

package com.rocketzki.domain;


// import ...

import java.util.List;
import java.util.NoSuchElementException;

@Service
public class LaptopService {

    private InMemoRepository<Laptop> repo;

    @Autowired
    private void setRepo(InMemoRepository<Laptop> repo) {
        this.repo = repo;
    }


    public List<Laptop> getLaptops() {
        return repo.getItems();
    }

    public void addLaptop(Laptop laptop) {
        repo.addItem(laptop);
    }

    public Laptop getById(Integer id) {
        List<Laptop> theList = repo.getItems();
        if (!theList.isEmpty() && theList.size() > id) {
            return theList.get(id);
        } else {
            throw new NoSuchElementException("There's no such element in the InMemo DB");
        }
    }
}

Firstly, I am adding dependency to the lower level – the repository in this case. This is a simple interface which provides abstraction for fetching and adding data to the InMemory DB (an ArrayList :)).

Service handles the logic – the getById method takes care of the secure repository searching – prevents from trying to get object that doesn’t exist.

Repository

package com.rocketzki.domain;

import com.rocketzki.domain.model.Laptop;
import com.rocketzki.repository.InMemoRepository;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class LaptopInMemoRepository implements InMemoRepository<Laptop> {

    private List<Laptop> laptops;

    LaptopInMemoRepository() {
        laptops = new ArrayList<>();
        laptops.add(new Laptop("MSI", "GE62", 4556.3, "AMD"));
        laptops.add(new Laptop("Lenovo", "G60", 3000.0, "Intel"));
    }

    public List<Laptop> getItems() {
        return laptops;
    }

    @Override
    public void addItem(Laptop item) {
        laptops.add(item);
    }
}

This is the “fake” database annotated with @Component so it will be detected by component scan and registered as a bean – it implements my InMemoRepository interface which provides method to communicate with the implemented repository:

package com.rocketzki.repository;

import java.util.List;

public interface InMemoRepository<T> {

    List<T> getItems();
    void addItem(T item);

}

The important part is the model, it has only the no args constructor, all args constructor, getter methos (we don’t need setters) and overriden toString() method to print object’s properties.

package com.rocketzki.domain.model;


public class Laptop {
    private String producer;
    private String model;
    private Double price;
    private String processorManufacturer;

    public Laptop() {

    }

    public Laptop(String producer, String model, Double price, String processorManufacturer) {
        this.model = model;
        this.producer = producer;
        this.price = price;
        this.processorManufacturer = processorManufacturer;
    }

    public String getModel() {
        return model;
    }

    public String getProducer() {
        return producer;
    }

    public Double getPrice() {
        return price;
    }

    public String getProcessorManufacturer() {
        return processorManufacturer;
    }

    @Override
    public String toString() {
        return "Laptop{" +
                "producer='" + producer + '\'' +
                ", model='" + model + '\'' +
                ", price=" + price +
                ", processorManufacturer='" + processorManufacturer + '\'' +
                '}';
    }
}

Most of those boiler plate methods can be easily generated by using InteliJ features. Just hit Alt+insert and generate them by yourself!

Last package contains ErrorController – it handles general error mapping and catches the only exception I am throwing consciously NoSuchElementException. 

package com.rocketzki.error;

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Map;
import java.util.NoSuchElementException;

@RestControllerAdvice
public class ErrorController {

    @GetMapping("/error")
    public Map handleError() {
        return Map.of("error", "An error occurred.");
    }

    @ExceptionHandler(NoSuchElementException.class)
    public Map handleNoElementException(NoSuchElementException ex) {
        return Map.of("error", ex.toString());

    }
}


The first new thing here is the annotation: @RestControllerAdvice  –
it’s a special controller that supports exception handling and model attributes globaly – to be used across all classes within the app. Here with “Rest” prefix it adds @ResponseBody – the return of the controller method is printed within the browser – JSON objects in your case.

As you can see I have used Map.of static factory method – it returns us immutable map of given parameters – this method is available since Java 9.

@ExceptionHandler(NoSuchElementException.class) – if the application throws the exception specified as a parameter of the annotation – it calls the annotated method. In this case it gives back the JSON with the stack trace.

@GetMapping(“/error”) – method annotated with “/error” route is called when server encounters any error that is not handled specifically (for instance by using ExceptionHandler). In this case I am printing a vague error message.

That’s it. It is that simple! The Spring framework let’s us implement apps quite quickly as you can see. Let’s run the app and see it in action.

In order to run it type gradle bootRun in the InteliJ terminal.

I am using Postman to test my REST api.

Correct GET request to /laptop/{id}

Correct POST request to /laptop/add

Incorrect GET request to /laptop/{id}

Incorrect GET request to /laptop/{id} – unauthorized – bad password





Check out some reference docs for more about Spring Security and Spring Boot

Here’s the link for full code repo

Should you have any questions don’t hesitate contacting me 🙂

Happy
Happy
0 %
Sad
Sad
0 %
Excited
Excited
100 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
0 0 votes
Article Rating
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
trackback

[…] tool so you need to create a project first. You can read more about it in my other articles (Spring Boot, […]

1
0
Would love your thoughts, please comment.x
()
x