A Spring Boot demo project that provides authentication and authorization through a Keycloak server.
- Configures and runs a Keycloak server in a Docker container
- Configures a Spring Boot application to use the Keycloak server for authentication and authorization
- Provides a REST API to authenticate and authorize users
- Provides a REST API to access a protected resource
- Provides a REST API to refresh the access token
- Java 17
- Docker
- Docker Compose
This section provides a step-by-step guide on how to run the project.
- Clone the repository by executing the following command:
git clone https://github.com/andrecaiado/spring-boot-oauth2-keycloak.git- Navigate into the project directory:
cd your-repository-name
- Install the dependencies by executing the following command:
./mvnw clean install- Run the application by executing the following command:
./mvnw spring-boot:runThe best practices recommend not to store sensitive information in the source code or other project files, e.g., the
.envfile.One usually follows the following steps to secure sensitive information contained in the
.envfile:
Add the
.envfile to the.gitignorefile to prevent it from being committed to the repository.Provide a
.envtemplate file with empty values and instructions on how to fill it.For the sake of simplicity of this example project and because some of the sensitive data is provided in the sbok-dev-realm.json, the above steps were not followed.
Of course, if you are going to use this project in a production environment, please follow the best practices and change the sensitive data accordingly.
When running the application, Keycloak will be available at http://localhost:8080/auth. The default credentials are:
- Username:
admin - Password:
password
For more information on configuring Keycloak server, please refer to the Keycloak Getting started guides.
This section describes the Keycloak configuration used in the project.
The configuration was first created manually in the Keycloak server and then exported to a JSON files that is loaded when the Keycloak server starts.
The exported file is the following: sbok-dev-realm.json
Note:
It's possible to export realm settings through the Keycloak admin console however, the users are not included in the exported file.
To export all settings, including the users, the following command was executed inside the Keycloak server container:
/opt/keycloak/bin/kc.sh export --dir /opt/keycloak/data/import --users realm_file --realm sbok-devThe configurations contained in the exported file are:
- Realm:
sbok-dev - Client:
sbok-auth-srv - Realm roles:
adminanduser - Users and roles:
john:- Username:
john - Password:
password - Realm roles assigned:
user
- Username:
admin:- Username:
admin - Password:
admin - Realm roles assigned:
admin
- Username:
Solve the issue that impacts the userinfo endpoint
To solve the issue that impacts the userinfo endpoint (returning http status code 403), the following steps were taken:
- Access the Keycloak admin console
- Switch to the
sbok-devrealm - Click on
Clients scopesin left menu - Create a client scope with the following settings:
- Name:
openid - Assign type:
Default - Protocol:
OpenID Connect
- Name:
- Click on
Clientsin left menu and select thesbok-auth-srvclient in the list - Navigate to the
Client Scopestab - Add the
openidclient scope to the assigned scopes
The above steps were taken based on the following discussion:
These steps are also included in the exported Keycloak configuration file.
To automatically import the realm configuration when the Keycloak server starts, the following configuration was added to the docker-compose.yaml file:
keycloak:
...
command: "-v start-dev --import-realm"
...
volumes:
- ./sbok-dev-realm.json:/opt/keycloak/data/import/sbok-dev-realm.jsonAccess the OpenID Endpoint Configuration URL to get the necessary URLs to authenticate and authorize users.
The above URL can be found at the Realm settings page of the Keycloak server.
The URL to authenticate users is the token_endpoint URL. In this project, it is http://localhost:8080/realms/sbok-dev/protocol/openid-connect/token.
Create a POST request to the token_endpoint URL with the following parameters:
- Body type:
x-www-form-urlencoded - Key-value pairs:
- grant_type:
password - client_id:
sbok-auth-srv - username:
john - password:
password - client_secret:
p201PFVJpK5a5jRkkKcwzYZvO7Yc0lUT
- grant_type:
The client_secret can be found at the client details page, in the credentials tab, of the Keycloak server.
If the request is successful, the response will contain, among other fields, the access_token.
This section describes the implementation of the project focused on the integration with Keycloak.
The security configuration is defined in the SecurityConfig class.
In the filter chain, we added the oauth2ResourceServer so Spring Boot knows what resource server to use in order to validate the JWT token that will be received in the requests.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...
http
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.jwt(Customizer.withDefaults())
);
...
return http.build();
}
}The resource server properties are defined in the application.yml file:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${KEYCLOAK_URL}/realms/sbok-dev
jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certsSome properties are loaded from the .env file.
Once again, please refer to the OpenID Endpoint Configuration URL to get the necessary URIs.
This section describes how to implement role-based authorization in the project.
Enable the @PreAutorize annotation by adding the @WebMethodSecurity annotation in the SecurityConfig class:
@Configuration
@EnableWebSecurity
@EnableWebMethodSecurity
public class SecurityConfig {
...
}Add the @PreAuthorize annotation to the methods that need authorization:
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/info")
@PreAuthorize("hasRole('user')")
public ResponseEntity<JsonNode> getUserInfo(@RequestHeader("Authorization") String bearerToken) {
bearerToken = bearerToken.replace("Bearer ", "");
return ResponseEntity.ok(userService.getUserInfo(bearerToken));
}
}We need to implement a custom JwtAuthenticationConverter to extract the roles from the JWT token so the @PreAuthorize annotation can work properly.
The roles are extracted from the realm_access and resource_access claims of the JWT token.
The JwtAuthenticationConverter is defined in the JwtAuthConverter.java class.
The project provides the following endpoints:
/api/v1/auth/login: Authenticates a user and returns the access token/api/v1/auth/refresh: Refreshes the access token/api/v1/user/info: Returns the user information (users with roleuser)/api/v1/admin/info: Returns a protected resource (users with roleadmin)
A postman collection is provided to test the endpoints.