The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

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.

先决条件

完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • JDK 17+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.8

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

应用结构

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.

解决方案

我们建议您按照下一节的说明逐步创建应用程序。然而,您可以直接转到已完成的示例。

克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git ,或下载一个 存档

The solution is located in the security-ldap-quickstart directory.

创建Maven项目

首先,我们需要一个新的工程项目。用以下命令创建一个新项目:

CLI
quarkus create app org.acme:security-ldap-quickstart \
    --extension='elytron-security-ldap,rest' \
    --no-code
cd security-ldap-quickstart

创建Grade项目,请添加 --gradle 或者 --gradle-kotlin-dsl 参数。

For more information about how to install and use the Quarkus CLI, see the Quarkus CLI guide.

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.15.1:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-ldap-quickstart \
    -Dextensions='elytron-security-ldap,rest' \
    -DnoCode
cd security-ldap-quickstart

创建Grade项目,请添加 -DbuildTool=gradle 或者 -DbuildTool=gradle-kotlin-dsl 参数。

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:

CLI
quarkus extension add elytron-security-ldap
Maven
./mvnw quarkus:add-extension -Dextensions='elytron-security-ldap'
Gradle
./gradlew addExtension --extensions='elytron-security-ldap'

这会将以下内容添加到你的构建文件中:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-ldap</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-elytron-security-ldap")

编写应用程序

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.

测试应用程序

The application is now protected and the identities are provided by our LDAP server. Let’s start the application in dev mode:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./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%

配置参考

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

类型

默认

The option to enable the ldap elytron module

Environment variable: QUARKUS_SECURITY_LDAP_ENABLED

Show more

boolean

false

The elytron realm name

Environment variable: QUARKUS_SECURITY_LDAP_REALM_NAME

Show more

string

Quarkus

Provided credentials are verified against ldap?

Environment variable: QUARKUS_SECURITY_LDAP_DIRECT_VERIFICATION

Show more

boolean

true

The url of the ldap server

Environment variable: QUARKUS_SECURITY_LDAP_DIR_CONTEXT_URL

Show more

string

required

The principal: user which is used to connect to ldap server (also named "bindDn")

Environment variable: QUARKUS_SECURITY_LDAP_DIR_CONTEXT_PRINCIPAL

Show more

string

The password which belongs to the principal (also named "bindCredential")

Environment variable: QUARKUS_SECURITY_LDAP_DIR_CONTEXT_PASSWORD

Show more

string

how ldap redirects are handled

Environment variable: QUARKUS_SECURITY_LDAP_DIR_CONTEXT_REFERRAL_MODE

Show more

ignore, follow, throw

ignore

The connect timeout

Environment variable: QUARKUS_SECURITY_LDAP_DIR_CONTEXT_CONNECT_TIMEOUT

Show more

Duration

5S

The read timeout

Environment variable: QUARKUS_SECURITY_LDAP_DIR_CONTEXT_READ_TIMEOUT

Show more

Duration

60S

If set to true, request to the LDAP server are cached

Environment variable: QUARKUS_SECURITY_LDAP_CACHE_ENABLED

Show more

boolean

false

The duration that an entry can stay in the cache

Environment variable: QUARKUS_SECURITY_LDAP_CACHE_MAX_AGE

Show more

Duration

60S

The maximum number of entries to keep in the cache

Environment variable: QUARKUS_SECURITY_LDAP_CACHE_SIZE

Show more

int

100

The identifier which correlates to the provided user (also named "baseFilter")

Environment variable: QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_RDN_IDENTIFIER

Show more

string

uid

The dn where we look for users

Environment variable: QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_SEARCH_BASE_DN

Show more

string

required

If the child nodes are also searched for identities

Environment variable: QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_SEARCH_RECURSIVE

Show more

boolean

false

The roleAttributeId from which is mapped (e.g. "cn")

Environment variable: QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__FROM

Show more

string

required

The identifier whom the attribute is mapped to (in Quarkus: "groups", in WildFly this is "Roles")

Environment variable: QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__TO

Show more

string

groups

The filter (also named "roleFilter")

Environment variable: QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__FILTER

Show more

string

required

The filter base dn (also named "rolesContextDn")

Environment variable: QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__FILTER_BASE_DN

Show more

string

required

About the Duration format

To write duration values, use the standard java.time.Duration format. See the Duration#parse() Java API documentation for more information.

You can also use a simplified format, starting with a number:

  • If the value is only a number, it represents time in seconds.

  • If the value is a number followed by ms, it represents time in milliseconds.

In other cases, the simplified format is translated to the java.time.Duration format for parsing:

  • If the value is a number followed by h, m, or s, it is prefixed with PT.

  • If the value is a number followed by d, it is prefixed with P.

Related content