Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public final class FeatureBuildItem extends MultiBuildItem {
public static final String JDBC_MYSQL = "jdbc-mysql";
public static final String JGIT = "jgit";
public static final String KAFKA_STREAMS = "kafka-streams";
public static final String KEYCLOAK_AUTHORIZATION = "keycloak-authorization";
public static final String KOGITO = "kogito";
public static final String KOTLIN = "kotlin";
public static final String KUBERNETES = "kubernetes";
Expand Down
56 changes: 44 additions & 12 deletions docs/src/main/asciidoc/security-keycloak-authorization.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ include::./attributes.adoc[]

This guide demonstrates how your Quarkus application can authorize access to protected resources using https://www.keycloak.org/docs/latest/authorization_services/index.html[Keycloak Authorization Services].

The `quarkus-keycloak-authorization` extension provides a policy enforcer that enforces access to protected resources based on permissions managed by Keycloak.
The `quarkus-keycloak-authorization` extension is based on `quarkus-oidc` and provides a policy enforcer that enforces access to protected resources based on permissions managed by Keycloak.
It provides a flexible and dynamic authorization capability based on Resource-Based Access Control.
In other words, instead of explicitly enforce access based on some specific access control mechanism (e.g.: RBAC), you just check whether or not a request is allowed to access a resource based on its name, identifier or URI.

Expand All @@ -28,6 +28,7 @@ To complete this guide, you need:
* JDK 1.8+ installed with `JAVA_HOME` configured appropriately
* Apache Maven 3.5.3+
* https://stedolan.github.io/jq/[jq tool]
* https://www.keycloak.org/docs/latest/server_installation/index.html[Keycloak]
* Docker

== Architecture
Expand Down Expand Up @@ -61,7 +62,7 @@ Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {q

The solution is located in the `security-keycloak-authorization-quickstart` {quickstarts-tree-url}/security-keycloak-authorization-quickstart[directory].

== Creating the Maven Project
== Creating the Project

First, we need a new project.
Create a new project with the following command:
Expand All @@ -77,8 +78,6 @@ cd security-keycloak-authorization-quickstart

This command generates a Maven project, importing the `keycloak-authorization` extension which is an implementation of a Keycloak Adapter for Quarkus applications and provides all the necessary capabilities to integrate with a Keycloak Server and perform bearer token authorization.

== Writing the application

Let's start by implementing the `/api/users/me` endpoint.
As you can see from the source code below it is just a regular JAX-RS resource:

Expand Down Expand Up @@ -153,12 +152,10 @@ public class AdminResource {
Note that we did not define any annotation such as `@RoleAllowed` to explicitly enforce access to a resource.
The extension will be responsible to map the URIs of the protected resources you have in Keycloak and evaluate the permissions accordingly, granting or denying access depending on the permissions that will be granted by Keycloak.

== Configuring the application
=== Configuring the application

The OpenID Connect extension allows you to define the adapter configuration using the `application.properties` file which should be located at the `src/main/resources` directory.

=== Configuring using the application.properties file

[source,properties]
----
# OIDC Configuration
Expand All @@ -170,11 +167,6 @@ quarkus.oidc.credentials.secret=secret
quarkus.keycloak.policy-enforcer.enable=true
----

=== Configuring CORS

If you plan to consume this application from another application running on a different domain, you will need to configure CORS (Cross-Origin Resource Sharing).
Please read the link:http-reference.html#cors-filter[HTTP CORS documentation] for more details.

== Starting and Configuring the Keycloak Server

To start a Keycloak Server you can use Docker and just run the following command:
Expand Down Expand Up @@ -285,6 +277,46 @@ export access_token=$(\
)
----

== Checking Permissions Programmatically

In some cases, you may want to programmatically check whether or not a request is granted to access a protected resource. By
injecting a `SecurityIdentity` instance in your beans, you are allowed to check permissions as follows:

[source,java]
----
@Path("/api/protected")
public class ProtectedResource {

@Inject
SecurityIdentity identity;


@GET
@Produces(MediaType.APPLICATION_JSON)
public CompletionStage<List<Permission>> get() {
return identity.checkPermission(new AuthPermission("{resource_name}"))
.thenCompose(granted -> {
if (granted) {
return CompletableFuture.completedFuture(doGetState());
}
throw new ForbiddenException();
});
}
}
----

== Mapping Protected Resources

By default, the extension loads all the protected resources managed by Keycloak where their `URI` are used to map the resources
in your application that should be protected. This is done during the application startup.

If you want to disable this behavior and map resources on-demand, you can use the following configuration:

[source,properties]
----
quarkus.keycloak.policy-enforcer.lazy-load-paths=true
----

== References

* https://www.keycloak.org/documentation.html[Keycloak Documentation]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.EnableAllSecurityServicesBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.oidc.runtime.OidcConfig;

public class KeycloakPolicyEnforcerBuildStep {

@BuildStep
FeatureBuildItem featureBuildItem() {
return new FeatureBuildItem(FeatureBuildItem.KEYCLOAK_AUTHORIZATION);
}

@BuildStep
public AdditionalBeanBuildItem beans() {
return AdditionalBeanBuildItem.builder().setUnremovable()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.quarkus.it.keycloak;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import javax.inject.Inject;
import javax.security.auth.AuthPermission;
Expand All @@ -22,11 +24,14 @@ public class ProtectedResource {

@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Permission> permissions() {
if (identity.checkPermissionBlocking(new AuthPermission("Permission Resource"))) {
return identity.getAttribute("permissions");
}
throw new ForbiddenException();
public CompletionStage<List<Permission>> permissions() {
return identity.checkPermission(new AuthPermission("Permission Resource"))
.thenCompose(granted -> {
if (granted) {
return CompletableFuture.completedFuture(identity.getAttribute("permissions"));
}
throw new ForbiddenException();
});
}

@Path("/claim-protected")
Expand Down