Using Security with an LDAP Realm
This guide demonstrates how your Quarkus application can use an LDAP server to authenticate and authorize your user identities.
Requisitos previos
To complete this guide, you need:
-
Roughly 15 minutes
-
An IDE
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.9
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
Arquitectura
In this example, we build a very simple microservice which offers three endpoints:
-
/api/public
-
/api/users/me
-
/api/admin
The /api/public
endpoint can be accessed anonymously.
The /api/admin
endpoint is protected with RBAC (Role-Based Access Control) where only users granted with the adminRole
role can access. At this endpoint, we use the @RolesAllowed
annotation to declaratively enforce the access constraint.
The /api/users/me
endpoint is also protected with RBAC (Role-Based Access Control) where only users granted with the standardRole
role can access. As a response, it returns a JSON document with details about the user.
By default, Quarkus will restrict the use of JNDI within an application, as a precaution to try and mitigate any future vulnerabilities similar to Log4Shell. Because LDAP based auth requires JNDI this protection will be automatically disabled. |
Solución
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
Clone el repositorio Git: git clone https://github.com/quarkusio/quarkus-quickstarts.git
o descargue un archivo.
The solution is located in the security-ldap-quickstart
directory.
Creación del proyecto Maven
En primer lugar, necesitamos un nuevo proyecto. Cree un nuevo proyecto con el siguiente comando:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=security-ldap-quickstart"
This command generates a project, importing the elytron-security-ldap
extension
which is a wildfly-elytron-realm-ldap
adapter for Quarkus applications.
If you already have your Quarkus project configured, you can add the elytron-security-ldap
extension
to your project by running the following command in your project base directory:
quarkus extension add elytron-security-ldap
./mvnw quarkus:add-extension -Dextensions='elytron-security-ldap'
./gradlew addExtension --extensions='elytron-security-ldap'
Esto añadirá lo siguiente a su archivo de construcción:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-ldap</artifactId>
</dependency>
implementation("io.quarkus:quarkus-elytron-security-ldap")
Writing the application
Let’s start by implementing the /api/public
endpoint. As you can see from the source code below, it is just a regular Jakarta REST resource:
package org.acme.elytron.security.ldap;
import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/api/public")
public class PublicResource {
@GET
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String publicResource() {
return "public";
}
}
The source code for the /api/admin
endpoint is also very simple. The main difference here is that we are using a @RolesAllowed
annotation to make sure that only users granted with the adminRole
role can access the endpoint:
package org.acme.elytron.security.ldap;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/api/admin")
public class AdminResource {
@GET
@RolesAllowed("adminRole")
@Produces(MediaType.TEXT_PLAIN)
public String adminResource() {
return "admin";
}
}
Finally, let’s consider the /api/users/me
endpoint. As you can see from the source code below, we are trusting only users with the standardRole
role.
We are using SecurityContext
to get access to the current authenticated Principal, and we return the user’s name. This information is loaded from the LDAP server.
package org.acme.elytron.security.ldap;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;
@Path("/api/users")
public class UserResource {
@GET
@RolesAllowed("standardRole")
@Path("/me")
public String me(@Context SecurityContext securityContext) {
return securityContext.getUserPrincipal().getName();
}
}
Configuring the Application
quarkus.security.ldap.enabled=true
quarkus.security.ldap.dir-context.principal=uid=admin,ou=system
quarkus.security.ldap.dir-context.url=ldaps://ldap.server.local (1)
quarkus.security.ldap.dir-context.password=secret
quarkus.security.ldap.identity-mapping.rdn-identifier=uid
quarkus.security.ldap.identity-mapping.search-base-dn=ou=Users,dc=quarkus,dc=io
quarkus.security.ldap.identity-mapping.attribute-mappings."0".from=cn
quarkus.security.ldap.identity-mapping.attribute-mappings."0".filter=(member=uid={0},ou=Users,dc=quarkus,dc=io) (2)
quarkus.security.ldap.identity-mapping.attribute-mappings."0".filter-base-dn=ou=Roles,dc=quarkus,dc=io
%test.quarkus.security.ldap.dir-context.url=ldap://127.0.0.1:10389 (3)
1 | You need to provide the URL to an LDAP server. This example requires the LDAP server to have imported this LDIF file. |
2 | {0} is substituted by the uid . |
3 | The URL used by our test resource. Tests may leverage LdapServerTestResource provided by Quarkus as we do in the test coverage of the example application. |
The elytron-security-ldap
extension requires a dir-context and an identity-mapping with at least one attribute-mapping to authenticate the user and its identity.
Testing the Application
The application is now protected and the identities are provided by our LDAP server. Let’s start the application in dev mode:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
The very first thing to check is to ensure the anonymous access works.
$ curl -i -X GET http://localhost:8080/api/public
HTTP/1.1 200 OK
Content-Length: 6
Content-Type: text/plain;charset=UTF-8
public%
Now, let’s try to hit a protected resource anonymously.
$ curl -i -X GET http://localhost:8080/api/admin
HTTP/1.1 401 Unauthorized
Content-Length: 14
Content-Type: text/html;charset=UTF-8
Not authorized%
So far so good, now let’s try with an allowed user.
$ curl -i -X GET -u adminUser:adminUserPassword http://localhost:8080/api/admin
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
admin%
By providing the adminUser:adminUserPassword
credentials, the extension authenticated the user and loaded their roles.
The adminUser
user is authorized to access to the protected resources.
The user adminUser
should be forbidden to access a resource protected with @RolesAllowed("standardRole")
because it doesn’t have this role.
$ curl -i -X GET -u adminUser:adminUserPassword http://localhost:8080/api/users/me
HTTP/1.1 403 Forbidden
Content-Length: 34
Content-Type: text/html;charset=UTF-8
Forbidden%
Finally, using the user standardUser
works and the security context contains the principal details (username for instance).
$ curl -i -X GET -u standardUser:standardUserPassword http://localhost:8080/api/users/me
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
user%
Referencia de configuración
Propiedad de configuración fijada en tiempo de compilación - Todas las demás propiedades de configuración son anulables en tiempo de ejecución
Configuration property |
Tipo |
Por defecto |
---|---|---|
The option to enable the ldap elytron module Environment variable: Show more |
boolean |
|
The elytron realm name Environment variable: Show more |
string |
|
Provided credentials are verified against ldap? Environment variable: Show more |
boolean |
|
The url of the ldap server Environment variable: Show more |
string |
required |
The principal: user which is used to connect to ldap server (also named "bindDn") Environment variable: Show more |
string |
|
The password which belongs to the principal (also named "bindCredential") Environment variable: Show more |
string |
|
how ldap redirects are handled Environment variable: Show more |
|
|
The connect timeout Environment variable: Show more |
|
|
The read timeout Environment variable: Show more |
|
|
If set to true, request to the LDAP server are cached Environment variable: Show more |
boolean |
|
The duration that an entry can stay in the cache Environment variable: Show more |
|
|
The maximum number of entries to keep in the cache Environment variable: Show more |
int |
|
The identifier which correlates to the provided user (also named "baseFilter") Environment variable: Show more |
string |
|
The dn where we look for users Environment variable: Show more |
string |
required |
If the child nodes are also searched for identities Environment variable: Show more |
boolean |
|
The roleAttributeId from which is mapped (e.g. "cn") Environment variable: Show more |
string |
required |
The identifier whom the attribute is mapped to (in Quarkus: "groups", in WildFly this is "Roles") Environment variable: Show more |
string |
|
The filter (also named "roleFilter") Environment variable: Show more |
string |
required |
The filter base dn (also named "rolesContextDn") Environment variable: Show more |
string |
required |
About the Duration format
To write duration values, use the standard You can also use a simplified format, starting with a number:
In other cases, the simplified format is translated to the
|