Spring Boot, Security, PostgreSQL, and Keycloak REST API OAuth2

by Didin J. on Mar 07, 2022 Spring Boot, Security, PostgreSQL, and Keycloak REST API OAuth2

The step by step Spring Boot tutorial on securing REST API using Spring Security OAuth2 as resources and Keycloak as the Authorization server


Before this, Spring have its own OAuth2 Authorization Server module then it's deprecated. But, we have other options that have already been established like Gluu and Keycloak. For this tutorial, we will use Keycloak as an Authorization server since it's recommended by Spring when the OAuth2 support ends in Spring version 2.4.0. We will use Spring Boot and PostgreSQL as resources servers.

This tutorial is divided into several steps:

You can watch the video tutorial on our YouTube channel here.


Step #1: Setup Keycloak Authorization Server

Setup Keycloak

We will use a standalone Keycloak server that can be downloaded from the official Keycloak Download. Download the latest Keycloak server, for this time we will use version 17.0. 

keycloak-download

Extract to your project directory, open the terminal or command line then go to the extracted Keycloak directory. 

cd ~/SpringApps/keycloak-17.0.0

Edit the configuration file to use the PostgreSQL database.

nano conf/keycloak.conf

Uncomment db, db-username, db-password, and db-url then give them your PostgreSQL configuration parameters.

Save this file then go to the PostgreSQL console.

psql postgres

Next, type this command for creating a new user with a password then give access for creating the database.

CREATE ROLE djamware WITH LOGIN PASSWORD 'dj@mw@r3';
ALTER ROLE djamware CREATEDB;

Quit PostgreSQL console then log in again using the new user that was previously created.

\q
psql postgres -U djamware

Type this command to create a new database.

CREATE DATABASE keycloak;

Then give that new user privileges to the new database then quit the PostgreSQL console.

GRANT ALL PRIVILEGES ON DATABASE keycloak TO djamware;
\q

Run and Configure Keycloak

To run Keycloak as a standalone server, go to the bin directory the run the Keycloak server.

cd bin
./kc.sh start-dev

Open your browser then go to http://localhost:8080. 

keycloak-initial-page

Fill in the admin username, password, and password confirmation then click the Create button. Click the Administration console button the log in using the previously created username and password.

keycloak-admin-login

And here the Keycloak admin dashboard with the default Realm is master.

keycloak-admin-dashboard

To create a new realm, click the dropdown of the Master menu in the left menu then click Add Realm.

keycloak-add-realm-button

Fill in the name and enable it on then click the Create button.

keycloak-add-realm

Now, the new realm is created, and the dashboard change to the new realm.

keycloak-new-realm-dashboard

Next, create a new client by clicking the Clients menu on the left.

keycloak-clients-list

Click the Create button at the top right of the client's table.

keycloak-add-client

Fill in the required fields such as client ID, client protocol, and root URL then click the Save button.

keycloak-client-settings

In the client settings, leave everything as default except the Valid Redirect URL. Fill with your Spring Boot resource server URL with an Asterix.

keycloak-client-redirect-url

Next, create a new role by clicking on the Roles menu on the left.

keycloak-roles-list

Click the Add Role button in the top right of the roles table.

keycloak-add-role

Click the Save button and it will be redirected to the role details.

keycloak-role-details

Next, click the Users menu in the bottom left menu.

keycloak-users-list

Click the Add User button then fill in all required fields as below.

keycloak-add-user

Click the Save button and it will be redirected to the user details page.

keycloak-user-details

Click the role mappings tab then choose user in the Available Roles then click Add selected.

keycloak-role-mappings

Now, the user will appear in the Roles -> Users in the Role tab. Next, on the user details page, click the Credentials tab then fill the password and password confirmation fields with your password. Make sure the Temporary switch is OFF otherwise it will be required to reset the password before using it.

keycloak-user-credentials

Click the set password button then accept the confirmation dialog. Now, the Keycloak and users is ready to use. We can test to get the token using postman. In the postman, make a POST request with URL http://localhost:8080/realms/djamware/protocol/openid-connect/token. Fill the request body these x-www-form-urlencoded fields.

postman-post-oauth-token

Click the Send button, and you will see this Postman response from the Keycloak server.

postman-response


Step #2: Create Spring Boot Application

To generate a Spring Boot starter application and Spring Boot OAuth2 Resource Server, go to Spring Initializer. Choose Maven Project, Java Language, Spring Boot version 2.6.4, or stable version. In Project Metadata, create your own group or default package (ours: com.djamware), Artifact is oauthresource, Name is oauthresource, Packaging is Jar, and Java version is 11. In Dependencies, choose Web, OAuth2 Resource Server, Spring Security, PostgreSQL Driver, and Spring Data JPA.

spring-initializer

Click the Generate button to download the initial Spring Boot project then extract it to your Spring Boot projects folder. Open this Spring Boot application project using your IDE (Netbeans, IntelliJIDEA, Eclipse, or VSCode). Next, open and edit pom.xml file then add this dependency.

        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>3.0.0</version>
        </dependency>

Reload Maven changes to download and install the newly added dependency.


Step #3: Configure Spring Security and Oauth2

We will use application.yml instead of the application.properties file. For that, rename the application.properties file to application.yml then replace/add these lines of YML configurations which are server port, PostgreSQL connection, and Spring Security OAuth2 resource server.

server:
  port: 8083

spring:
  jpa:
    database: POSTGRESQL
    show-sql: true
    hibernate:
      ddl-auto: create
  datasource:
    platform: postgres
    url: jdbc:postgresql://localhost:5432/myoauth2
    username: djamware
    password: dj@mw@r3
    driver-class-name: org.postgresql.Driver
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:8080/realms/djamware
          jwk-set-uri: http://localhost:8080/realms/djamware/protocol/openid-connect/certs

As you can see, we are using port 8083 to run this Spring Boot resource server because the default port 8080 is used by Keycloak standalone server. We need to create a database for this resource server. For that, enter the PostgreSQL console from the terminal.

Type this command to create a new database.

CREATE DATABASE myoauth2;

Then give that user privileges to the new database then quit the PostgreSQL console.

GRANT ALL PRIVILEGES ON DATABASE myoauth2 TO djamware;
\q

To find out Keycloak JWT issuer-ur and jwk-set-uri, you can check it using postman to this URL http://localhost:8080/realms/djamware/.well-known/openid-configuration using GET method.

Next, we need to add Spring Security config by creating a new package com/djamware/oauthresource/config and a file SecurityConfig.java inside that package. Replace the codes in this file with these codes.

package com.djamware.oauthresource.config;

import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public ModelMapper modelMapper() {
        return new ModelMapper();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/api/boards")
                .hasAuthority("user")
                .anyRequest()
                .authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt();
    }
}

The ModelMapper inject into this configuration because will be used in entity to DTO converter. The Spring Security is securing /api/boards endpoint with Authority user that previously created in Keycloak. Also, configure Oauth2ResourceServer and JWT as tokens that will be used when the secure endpoint is accessed.


Step #4: Create Spring Data Entity, Repository, Service, and Rest Controller

Before creating an entity or models that will use in Spring Boot OAuth2 resource server, create a new package with the name com/djamware/oauthresource/entity and a Java file with the name Board.java. Replace all Java codes with this.

package com.djamware.oauthresource.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;

@Entity
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;
    private String author;
    private Date createdAt;

    public Board() {}

    public Board(Long id, String title, String content, String author, Date createdAt) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.author = author;
        this.createdAt = createdAt;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
}

If you want less code for entity or model class, you can use Lombok.

Next, before creating a repository that will use by the service or controller, create a new package with the name com/djamware/oauthresource/repository and a Java interface file with the name BoardRepository.java. Replace all Java interface codes with this.

package com.djamware.oauthresource.repository;

import com.djamware.oauthresource.entity.Board;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface IBoardRepository extends PagingAndSortingRepository<Board, Long> {
}

That repository is very simple because we just need to get a list from the model. Next, create a com/djamware/oauthresource/service package, an interface file IBoardService.java, and a class file BoardServiceImpl.java. Replace all codes in IBoardService.java with this.

package com.djamware.oauthresource.service;

import com.djamware.oauthresource.entity.Board;

public interface IBoardService {
    Iterable<Board> findAll();
}

Next, replace all codes in BoardServiceImpl.java with this.

package com.djamware.oauthresource.service;

import com.djamware.oauthresource.entity.Board;
import com.djamware.oauthresource.repository.IBoardRepository;
import org.springframework.stereotype.Service;

@Service
public class BoardServiceImpl implements IBoardService {
    private final IBoardRepository boardRepository;

    public BoardServiceImpl(IBoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    @Override
    public Iterable<Board> findAll() {
        return boardRepository.findAll();
    }
}

Next, create a new Rest Controller by creating a new package first com/djamware/oauthresource/controller and then a class file BoardController.java inside that package. Replace all codes inside BoardController.java with this.

package com.djamware.oauthresource.controller;

import com.djamware.oauthresource.dto.BoardDto;
import com.djamware.oauthresource.entity.Board;
import com.djamware.oauthresource.service.IBoardService;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
public class BoardController {
    private IBoardService boardService;

    public BoardController(IBoardService boardService) {
        this.boardService = boardService;
    }

    @Autowired
    private ModelMapper modelMapper;

    @GetMapping("/api/boards")
    public Collection<BoardDto> findAll() {
        Iterable<Board> boards = this.boardService.findAll();
        List<BoardDto> boardDtos = new ArrayList<>();
        boards.forEach(b -> boardDtos.add(convertToDto(b)));
        return boardDtos;
    }

    protected BoardDto convertToDto(Board board) {
        BoardDto boardDto = modelMapper.map(board, BoardDto.class);
        return boardDto;
    }
}

Also, create a DTO file by creating a new package first com/djamware/oauthresource/dto then a class file BoardDto.java inside that package. Replace all codes inside BoardDto.java with this.

package com.djamware.oauthresource.dto;

import com.fasterxml.jackson.annotation.JsonAutoDetect;

import java.util.Date;

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class BoardDto {
    private long id;
    private String title;
    private String content;
    private String author;
    private Date createdAt;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
}

That REST controller mapping the board list result to secure /api/boards endpoint. The model result is converted to JSON DTO before returning to the REST response. 


Step #5: Run and Test using Postman

Before running this Spring Boot OAuth2 resource server, make sure the Keycloak standalone authorization server is running. Inside the keycloak-17.0.0/bin directory type this command.

./kc.sh start-dev

To run Spring Boot OAuth2 resource server, simply type this command.

mvn clean spring-boot:run

After running the Spring Boot application, load this dummy data to the database.

INSERT INTO board(title, content, author, created_at) VALUES('New Spring Boot Articles', 'Didin J.', 'The new Spring Boot article is available on Djamware.com', current_timestamp);
INSERT INTO board(title, content, author, created_at) VALUES('Live Tutorial on YouTube', 'Didin J.', 'Live Tutorial about basic Java on YouTube at May 21, 2022', current_timestamp);
INSERT INTO board(title, content, author, created_at) VALUES('The next stream on Monday', 'Didin J.', 'The next stream on Monday', current_timestamp);

Next, open the postman then make a GET request to http://localhost:8083/api/boards/ with response type JSON.

unauthenticated-postman-get

That request should respond with status 401 Unauthorized. To make an Authorized request, first, we need to get a token from the Keycloak by making a new Postman request with POST method and Url http://localhost:8080/realms/djamware/protocol/openid-connect/token. Use x-ww-form-urlencoded body with these variables.

postman-get-token

When hitting the Send button, it should respond to the JWT token like this.

jwt-response

Just copy the access_token value to the Get Boards request headers with key name Authorization and value Bearer + JWT token.

authorized-request

When hitting the Send button, it should respond to the JSON array like this.

authorized-response

That it's, Spring Boot, Security, PostgreSQL, and Keycloak REST API OAuth2. You can get the source code from our GitHub.

That just the basic. If you need more deep learning about Java and Spring Framework you can take the following cheap course:

Thanks!

Loading…