1. About
1.1. Summary
This project shows how Keycloak and Dropwizard can be used together. At the time I wrote this there was no open source integration of the two, so I set up this project.
To read the latest version of this tutorial, please visit: https://ahus1.github.io/keycloak-dropwizard-integration/tutorial.html.
Keycloak provides a standalone OAuth 2.0 and Open ID Connect server. It handles user credentials for your application, so you can focus on business requirements.
Dropwizard is a Java framework for developing ops-friendly, high-performance, REST-ful web services.
The versions 1.0.x and 2.0.x of this project showed alternative ways to integrate Keycloak’s client libraries with Dropwizard versions 1.x to 3.x. With Dropwizard version 4.x, Dropwizard upgraded from Java EE to Jakarta EE. As this would have required additional migration efforts which an unclear added value to users, those have been removed. They might be added in the future if there is co
1.2. How to use
The module keycloak-dropwizard
is a ready-to-use Dropwizard module.
The releases are available from Maven central.
The releases depend on a version of Dropwizard and Keycloak that was current at release time. To use a more recent release, please add them as an explicit dependency to your project, as this project will not release new versions on every minor or patch release of its dependencies.
As Keycloak removed some of its client adapters, the latest version of the Keycloak client libraries to use is 24.0.5.
-
Version 0.7.x is tested with Keycloak 1.9.x and Dropwizard 0.9.x
-
Version 0.8.x is tested with Keycloak 2.x.x and Dropwizard 0.9.x
-
Version 0.9.x is tested with Keycloak 2.x.x/3.x.x and Dropwizard 1.0.x
-
Version 1.0.x is tested with Keycloak 3.x.x and Dropwizard 1.1.x/1.2.x/1.3.x
-
Version 1.1.x/1.2.x is tested with Keycloak 4.x-21.x and Dropwizard 1.3.x/2.0.x/2.1.x
-
Version 2.x is tested with Keycloak 4.x-23.x and Dropwizard 3.0.x
-
Version 3.x is tested with Keycloak 23.x-25.x and Dropwizard 4.0.x
Starting with Dropwizard 2.0 and the included version of Jersey, a login performed during a POST for a form will not recover the contents of the POST. This is wired into Keycloak’s JettyAdapterSessionStore (that restores the content type and the parameters to the request), but Jersey’s InboundMessageContext that wants to read the information in the request’s header and the body. See the shouldLoginFromPost() test case for an example. |
<dependencies>
<dependency>
<groupId>de.ahus1.keycloak.dropwizard</groupId>
<artifactId>keycloak-dropwizard</artifactId>
<version>x.x.x</version>
</dependency>
</dependencies>
The most recent development version (based on the master branch on GitHub) is available from the Sonatype OSS Snapshot Repository. To use it, include the following repository in your pom.xml.
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
1.3. Prerequisites
These examples need a local Keycloak instance with Realm test
and user demo
with password demo
.
Download the Keycloak distribution matching your keycloak-dropwizard-integration version from http://keycloak.org and extract it to a subfolder keycloak-server
of this directory.
Then call keycloak-server.bat
to import an already configured realm. Using this startup file the configuration will be reset every time you start Keycloak.
1.4. Parts
These examples will guide you through setting up Dropwizard and Keycloak in several configurations:
-
JAX-RS stateful server sessions
see module keycloak-dropwizard-jaxrs-example. -
Bearer-Only REST services for Dropwizard
see module keycloak-dropwizard-ajax-example.
1.5. License
Copyright 2015-2023 Alexander Schwartz and the individual contributors.
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
2. Preparing a Dropwizard Bundle for Keycloak
2.1. What you can see here
This is a Dropwizard bundle that provides a ready-to-use Keycloak integration.
2.2. Step by Step
This shows the main integration points
-
Setup keycloak as an Authenticator to handle pre-auth integration:
KeycloakBundle.javaKeycloakJettyAuthenticator keycloak = new KeycloakDropwizardAuthenticator(); keycloak.setAdapterConfig(getKeycloakConfiguration(configuration)); ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); environment.getApplicationContext().setSecurityHandler(securityHandler); environment.getApplicationContext().getSecurityHandler().setAuthenticator(keycloak);
-
Setup the authentication factory
KeycloakBundle.javaenvironment.jersey().register(new AuthDynamicFeature( createAuthFactory(configuration))); // To use @RolesAllowed annotations environment.jersey().register(RolesAllowedDynamicFeature.class); // To use @Auth to inject a custom Principal type into your resource environment.jersey().register(new AuthValueFactoryProvider.Binder<>(getUserClass()));
2.3. How to use it
To use it:
-
register the KeycloakBundle
-
override getKeycloakConfiguration() to provide the configuration information.
Optionally (if you want to use a different class instead of User to wrap access to Keycloak):
-
override getUserClass() to return the class you are using
-
override createAuthenticator() to provide a factory to create the instances of your user class
-
override createAuthorizer() to implement the role checking used by the @RolesAllowed annotation.
See the default implementations User, KeycloakAuthenticator, and UserAuthorizer as a guide.
Please see the next chapter how to integrate.
3. Dropwizard integration with session-based JAX-RS
3.1. What you can see here
This provides a JAX-RS application with server managed session.
It uses Dropwizard’s @Auth
annotation.
3.2. Setup for Keycloak
The following elements add Keycloak authentication to Dropwizard and are identical to the simple setup:
-
Add Keycloak information to
config.yml
config.ymlserver: applicationConnectors: - type: http port: 9090 adminConnectors: - type: http port: 9091 keycloakConfiguration: realm: test auth-server-url: http://localhost:8080 ssl-required: none register-node-at-startup: true register-node-period: 600 resource: test credentials: secret: 7abd7d08-b10f-4513-bbba-aebebddabb45
-
Add Keycloak as a security constraint to
LotteryConfiguration.java
, but without the role and URL mappings.LotteryConfiguration.javapublic class LotteryConfiguration extends Configuration { private KeycloakConfiguration keycloakConfiguration = new KeycloakConfiguration(); public KeycloakConfiguration getKeycloakConfiguration() { return keycloakConfiguration; } public void setKeycloakConfiguration(KeycloakConfiguration keycloakConfiguration) { this.keycloakConfiguration = keycloakConfiguration; } }
-
Add the Keycloak bundle:
LotteryApplication.javabootstrap.addBundle(new KeycloakBundle<LotteryConfiguration>() { @Override protected KeycloakConfiguration getKeycloakConfiguration(LotteryConfiguration configuration) { return configuration.getKeycloakConfiguration(); } /* OPTIONAL: override getUserClass(), createAuthorizer() and createAuthenticator() if you want to use * a class other than de.ahus1.keycloak.dropwizard.User to be injected by @Auth */ });
Once this is set up, Dropwizard’s @Auth
annotation can be used as usual in resources:
@Path("/")
@Produces(MediaType.TEXT_HTML)
public class DrawRessource {
@Context
private HttpServletRequest request;
@POST
@Path("/draw")
@RolesAllowed("user")
public DrawView draw(@FormParam("date") String dateAsString, @Auth User auth) { (1)
DrawBean bean = new DrawBean();
LocalDate date = LocalDate.parse(dateAsString);
bean.setDraw(DrawingService.drawNumbers(date));
DrawView view = new DrawView(bean);
bean.setName(auth.getName());
return view;
}
@GET
@Path("/logout")
public LogoutView logout(@Context SecurityContext context) throws ServletException { (2)
if (context.getUserPrincipal() != null) {
request.logout();
}
return new LogoutView();
}
}
1 | This enforces authentication of the user, possibly triggering a full OAuth redirect flow |
2 | Access the security context directly to find out if the user is logged in without forcing authentication |
3.3. How to run
Use the following command line to start it from the parent’s directory
mvn test -pl keycloak-dropwizard-jaxrs-example -am -Pkeycloak-dropwizard-jaxrs-example
Once it is started, point your browser to http://localhost:9090 to see the application.
Enter a date like 2015-01-01
to see the predicted results of the given date.
4. Dropwizard integration with bearer tokens
4.1. What you can see here
This is a setup that includes a small JavaScript client that interfaces with a REST backend. The client authenticates all requests using a Bearer Token.
4.2. Setup for Keycloak
The following elements add Keycloak authentication to Dropwizard and are identical to the simple setup:
-
Add Keycloak
config-bearer.yml
with bearer only authentication. This way the Keycloak will not initiate the OAuth redirecting flow. The Keycloak Dropwizard module does not interfere with any OAuth redirect initiated by the frontend.config-bearer.ymlkeycloakConfiguration: bearer-only: true
-
Add Keycloak as a security constraint to
LotteryConfiguration.java
, but without the role and URL mappings.LotteryConfiguration.javapublic class LotteryConfiguration extends Configuration { private KeycloakConfiguration keycloakConfiguration = new KeycloakConfiguration(); public KeycloakConfiguration getKeycloakConfiguration() { return keycloakConfiguration; } public void setKeycloakConfiguration(KeycloakConfiguration keycloakConfiguration) { this.keycloakConfiguration = keycloakConfiguration; } }
-
Add Keycloak as a filter to the REST stack.
Be aware that all calls to REST resources now require a Bearer Token only if
-
the method or class is annotated with @RolesAllowed OR
-
has a method parameter annotated with @Auth.
Methods and classes that have neither will proceed when the user hasn’t sent a Bearer Token or is not authenticated, or if the user has sent an (optional) valid Bearer Token. To check if the user has sent an (optional) valid token, add a parameter @Context SecurityContext context and check the logged-in user via context.getUserPrincipal().
LotteryApplication.javabootstrap.addBundle(new KeycloakBundle<>() { @Override protected KeycloakConfiguration getKeycloakConfiguration(LotteryConfiguration configuration) { return configuration.getKeycloakConfiguration(); } /* OPTIONAL: override getUserClass(), createAuthorizer() and createAuthenticator() if you want to use * a class other than de.ahus1.keycloak.dropwizard.User to be injected by @Auth */ });
-
A simple JavaScript client is located in src/main/resources/assets/ajax
.
4.3. How to run
Use the following command line to start it from the parent’s directory
mvn test -pl keycloak-dropwizard-bearermodule -am -Pkeycloak-dropwizard-bearermodule
Once it is started, point your browser to http://localhost:9090/ajax/index.html to see the application.
Enter a date like 2015-01-01
to see the predicted results of the given date.