DEV Community

Cover image for Spring Boot 3.4.1 Keycloak introspect and method level security
Binoy
Binoy

Posted on

Spring Boot 3.4.1 Keycloak introspect and method level security

Spring Boot 3.4.1 Keycloak introspect and method level security

This section outlines the process of implementing the Keycloak introspection flow in Spring Boot version 3.4.1 and demonstrates how to enable method-level security based on roles configured in Keycloak.

Objective

I am developing a project that provides APIs for creating services for the microservice-based Klight API gateway, which I built using the OpenResty web application. This API gateway delivers high performance compared to a Spring Boot API gateway because the OpenResty framework is built on top of the NGINX server and uses Lua, a language commonly used in game programming.

You might wonder why Kong API Gateway wasn't used. The reason is that the open-source version of Kong does not support OpenID Connect and pay to integrate with OpenID Connect. When building high-performance applications in a microservice architecture, it is crucial to choose an API gateway optimized for performance.

Integration

  • Spring Boot 3.4.1
  • Java 22
  • Keycloak Integration and RBAC implementation
  • Spring Oauth2 Resource Server and Method Level Security
  • MongoDB NoSQL DB and Auditing

Installing Dependencies Services

The following services are required dependencies before starting the Spring Boot application.

Keycloak Installation in Docker

Execute the following commands to start the Keycloak service in a Docker container. The admin username and password are set to "admin" and "admin," respectively.

docker run -d --name klight-authentication-server -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:26.0 start-dev
Enter fullscreen mode Exit fullscreen mode

MongoDB installation in Docker

Run the following commands to start the MongoDB service in a Docker container. The root username and password are both set to klight-api-gateway. After installing MongoDB, you need to create a database named "klight-api-gateway" to enable connection with the Spring Boot application, as this database is referenced in the application-dev.yaml file.

docker run --name klight-api-gateway-mongo -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=klight-api-gateway -e MONGO_INITDB_ROOT_PASSWORD=klight-api-gateway -d amd64/mongo:8.0.3
Enter fullscreen mode Exit fullscreen mode

Spring Security Dependencies

The following dependencies must be configured to enable the introspection flow. The admin service is responsible for validating tokens with the Keycloak service. A token must be generated from Keycloak and included in the Authorization header of the API request.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Keycloak configuration in YAML file

We have to configure the following attributes in the YAML file. This configuration uses in the SecurityConig.java class to configure the introspection flow.

keycloak:
  introspection-uri: http://127.0.0.1:8080/realms/klight-api-gateway/protocol/openid-connect/token/introspect
  client-id: klight-api-gateway-admin-connect
  client-secret: HDYMxWu2G2VWn5u59MJKwECIBd6VTO7E
Enter fullscreen mode Exit fullscreen mode

Security Configuration

The following attributes must be configured in the YAML file. This configuration is utilized in the SecurityConfig.java class to set up the introspection flow.

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

    @Value("${keycloak.introspection-uri}")
    private String introspectionUrl;

    @Value("${keycloak.client-id}")
    private String clientId;

    @Value("${keycloak.client-secret}")
    private String clientSecret;

    @Autowired
    private ExceptionAccessDeniedHandler accessDeniedHandler;

    @Autowired
    private ExcetionAuthenticationEntryPoint entryPoint;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, OpaqueTokenAuthenticationConverter converter) throws Exception {


        httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(auth->
                        auth.anyRequest().authenticated())
                .oauth2ResourceServer(auth->
                        auth.opaqueToken(op->
                                op.introspectionUri(this.introspectionUrl)
                                        .introspectionClientCredentials(
                                                this.clientId,
                                                this.clientSecret)
                                        .authenticationConverter(converter)
                                )

                                .authenticationEntryPoint(entryPoint.handler())
                                .accessDeniedHandler(accessDeniedHandler.handler())
                                )
                .exceptionHandling(ex ->
                        ex.authenticationEntryPoint(entryPoint.handler())
                                .accessDeniedHandler(accessDeniedHandler.handler()))
        ;

        return httpSecurity.build();
    }

}
Enter fullscreen mode Exit fullscreen mode

The key configurations in the above setup are as follows:

  • Enable method-level security in the Spring Boot application using @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true).
  • Implement the OpaqueTokenAuthenticationConverter converter to process realm_access and resource_access from the token and create a list of GrantedAuthority based on the roles configured in the Keycloak service.
  • Disable csrf protection for the REST API service.
  • Authenticate all incoming requests using authorizeHttpRequests.
  • Configure the introspection flow with oauth2ResourceServer.
  • Handle authentication exceptions using authenticationEntryPoint.
  • Set up an access denied handler with accessDeniedHandler.

Method Level Configuration

We are securing the functionality of the ApiService.java methods using the following configuration. The @PreAuthorize("hasAuthority('ROLE_ADMIN')") annotation should be added to service class methods to validate role permissions. We should mention ROLE_ADMIN role name in the annotation which we have created in the Keycloak service.

@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public void create(ApiServiceRequest request) {
    log.info("Service creating");

    ApiServiceCreate service = createFactory.getObject();

    map(service, request);

    service.create();

}
Enter fullscreen mode Exit fullscreen mode

Keycloak Configuration

Following configurations need to setup in the Keycloak service. We have to login into Keycloak http://localhost:8080 in the browser with admin user and password.

Create the Realm klight-api-gateway

Keycloak supports multi-tenancy, enabling us to create application-specific configurations through realm setup. For the Klight API Gateway, we maintain a dedicated realm. This realm, we are using to maintain the clients, user, group and roles.

  • Click on the "Keycloak master" realm in the left corner.
  • Click the "Create realm" button.
  • Enter the realm name as "klight-api-gateway."
  • Enable the realm by toggling the "Enabled" option.
  • Click the "Create" button.

Note: Ensure that the "klight-api-gateway" realm is selected before configuring anything, as the default realm is set to "master."

Create the Client klight-api-gateway-admin-connect

Following setup to create new client. This client is support OpenID connection where admin service can connect the Keycloak server through this client.

  • Click "Clients" in the left sidebar menu.
  • Click the "Create Client" button.
  • Configure the following "General Settings":
    • Set the client type to "OpenID Connect"
    • Enter the Client ID: klight-api-gateway-admin-connect.
    • Enter the Client Name: Klight API Gateway Admin Connect
    • Click the "Next" button.
  • Configure the following "Capability Configuration":
    • Enable "Client Authentication"
    • Select only the "Direct access grants" flow. This flow allows generating tokens using the "username" and "password" method, which is ideal for trusted clients such as mobile apps or backend services.
    • Click the "Next" button.
  • Configure the "Login Settings":
    • No additional configuration is needed here for now.
  • Click the "Save" button.
  • After the client is created, select the "Credentials" tab.
    • Set the "Client Authenticator" to "Client ID and Secret"
    • Click "Generate" to create a new "Client Secret."
  • Select the "Roles" tab
    • Click the "Create Role" button
    • Enter the role name ROLE_ADMIN, this is the role name is configuring in the method level
    • Click "Save" button

Note: This client id and client secret are configured in the application-dev.yaml file

We have to create the ROLE_ADMIN in the client Roles tab

Create the User

In this flow, we need to create a new user to authenticate the server.

  • Click "Users" in the left sidebar menu.
  • Click the "Create new user" button.
  • Enable the "Email Verified" option.
  • Enter the following "General" information:
    • Username: "api-admin"
    • Email: "my-user@test.com"
    • First Name: "my"
    • Last Name: "user"
  • Click the "Create" button.
  • Select the user `api-admin' from the user list to configure password and role
  • Click the "Credentials" tab.
    • Click the "Set password" button.
    • Enter "test" for both the "Password" and "Password Confirmation" fields.
    • Set "Temporary" to Off.
    • Click the "Save" button.
    • Click the "Save password" button.
  • Click the "Role Mapping" tab
    • Click the "Assign Role" button
    • Search the ROLE_ADMIN in the search box
    • Select the check box of ROLE_ADMIN
    • Click on the Assign button

Testing Endpoint

Once configuration done, we can test the API to test the authentication and authorization

Get the access token

Run the following command to get the access token from Keycloak
shell
curl --location 'http://127.0.0.1:8080/realms/klight-api-gateway/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=klight-api-gateway-admin-connect' \
--data-urlencode 'client_secret=HDYMxWu2G2VWn5u59MJKwECIBd6VTO7E' \
--data-urlencode 'scope=openid' \
--data-urlencode 'username=api-admin' \
--data-urlencode 'password=test'

Test endpoint of admin service

Run the following command to test the endpoint to create the service
shell
curl --location 'http://localhost:8082/admin/services' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiI...' \
--header 'Cookie: JSESSIONID=7B6F7EB454EF0392D6EB21658A922900' \
--data '{
"name": "Test Service",
"host": "localhost",
"path": "test-service",
"protocol": "https"
}'

Other Coding Pattern

This source contains following coding pattern implemented as well

  • Single Responsible of business logic for service ApiService.java, this help to maintain the readability, maintainability, reuse this business this class in different functionality, for example
    • ApiServiceCreate.java for creating the service
    • ApiServiceUpdate.java for creating the service
  • @EnableMongoAuditing enabled auditing MongoDbConfig.java
    • CreatedDate, LastModifiedDate, CreatedBy, LastModifiedBy
  • Added the logs with help Slf4j for debugging purpose
  • Maintain the unique error code for exception, implemented global exception handling ExceptionControllerAdvice.java

Source Code in Github 'klight-api-gateway-admin'

References

Top comments (0)