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:
- Step #1: Setup Keycloak Authorization Server
- Step #2: Create Spring Boot Application
- Step #3: Configure Spring Security and Oauth2
- Step #4: Create Spring Data Entity, Repository, Service, and Rest Controller
- Step #5: Run and Test using Postman
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.
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.
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.
And here the Keycloak admin dashboard with the default Realm is master.
To create a new realm, click the dropdown of the Master menu in the left menu then click Add Realm.
Fill in the name and enable it on then click the Create button.
Now, the new realm is created, and the dashboard change to the new realm.
Next, create a new client by clicking the Clients menu on the left.
Click the Create button at the top right of the client's table.
Fill in the required fields such as client ID, client protocol, and root URL then click the Save button.
In the client settings, leave everything as default except the Valid Redirect URL. Fill with your Spring Boot resource server URL with an Asterix.
Next, create a new role by clicking on the Roles menu on the left.
Click the Add Role button in the top right of the roles table.
Click the Save button and it will be redirected to the role details.
Next, click the Users menu in the bottom left menu.
Click the Add User button then fill in all required fields as below.
Click the Save button and it will be redirected to the user details page.
Click the role mappings tab then choose user in the Available Roles then click Add selected.
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.
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.
Click the Send button, and you will see this Postman response from the Keycloak server.
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.
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.
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.
When hitting the Send button, it should respond to the JWT token like this.
Just copy the access_token value to the Get Boards request headers with key name Authorization and value Bearer + JWT token.
When hitting the Send button, it should respond to the JSON array like this.
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:
- Java basics, Java in Use //Complete course for beginners
- Java Programming: Master Basic Java Concepts
- Master Java Web Services and REST API with Spring Boot
- JDBC Servlets and JSP - Java Web Development Fundamentals
- The Complete Java Web Development Course
- Spring MVC For Beginners: Build Java Web App in 25 Steps
- Practical RESTful Web Services with Java EE 8 (JAX-RS 2.1)
Thanks!