Using Security with an LDAP Realm

本指南演示了 Quarkus 应用程序如何使用 LDAP 服务器对用户身份进行身份验证和授权。

This guide demonstrates how your Quarkus application can use an LDAP server to authenticate and authorize your user identities.

Prerequisites

Unresolved directive in security-ldap.adoc - include::{includes}/prerequisites.adoc[]

Architecture

在此示例中,我们构建了一个非常简单的微服务,它提供三个端点:

In this example, we build a very simple microservice which offers three endpoints:

  • /api/public

  • /api/users/me

  • /api/admin

可以匿名访问 /api/public`端点。/api/admin`端点受 RBAC(基于角色的访问控制)保护,只有被授予`adminRole`角色的用户才能访问。在此端点,我们使用 @RolesAllowed`注释以声明方式强制访问约束。/api/users/me`端点也受 RBAC(基于角色的访问控制)保护,只有被授予`standardRole`角色的用户才能访问。作为响应,它返回一个 JSON 文档,其中包含有关用户的详细信息。

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.

默认情况下,Quarkus 将限制应用程序中 JNDI 的使用,旨在缓解类似于 Log4Shell 的任何未来漏洞。由于基于 LDAP 的身份验证需要 JNDI,此保护将被自动禁用。

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.

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

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.

克隆 Git 存储库: git clone {quickstarts-clone-url},或下载 {quickstarts-archive-url}[存档]。

Clone the Git repository: git clone {quickstarts-clone-url}, or download an {quickstarts-archive-url}[archive].

该解决方案位于`security-ldap-quickstart` directory中。

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

Creating the Maven Project

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

First, we need a new project. Create a new project with the following command:

Unresolved directive in security-ldap.adoc - include::{includes}/devtools/create-app.adoc[]

此命令生成一个项目,导入`elytron-security-ldap`扩展 - 它是一个适用于 Quarkus 应用程序的`wildfly-elytron-realm-ldap`适配器。

This command generates a project, importing the elytron-security-ldap extension which is a wildfly-elytron-realm-ldap adapter for Quarkus applications.

如果你已配置好 Quarkus 项目,可以通过在项目基本目录中运行以下命令将`elytron-security-ldap`扩展添加到项目中:

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:

Unresolved directive in security-ldap.adoc - include::{includes}/devtools/extension-add.adoc[]

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

This will add the following to your build file:

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

Writing the application

首先,实现 /api/public 端点。正如您从下面的源代码中所见,它仅仅是一个常规的 Jakarta REST 资源:

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";
   }
}

/api/admin`端点的源代码也很简单。此处的关键区别在于我们使用@RolesAllowed`注释确保只有被授予`adminRole`角色的用户可以访问端点:

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";
    }
}

最后,让我们考虑一下`/api/users/me`端点。正如你从以下源代码中看到的那样,我们只信任具有`standardRole`角色的用户。我们使用`SecurityContext`获取当前经过身份验证的主体,然后返回用户的姓名。此信息从 LDAP 服务器加载。

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.

`elytron-security-ldap`扩展需要一个目录上下文以及一个包含至少一个属性映射的标识映射,以验证用户及其标识。

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

该应用程序现在已受到保护,并且标识由我们的 LDAP 服务器提供。让我们在开发模式下启动应用程序:

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

Unresolved directive in security-ldap.adoc - include::{includes}/devtools/dev.adoc[]

要检查的第一件事是确保匿名访问有效。

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%

通过提供 `adminUser:adminUserPassword`凭据,该扩展名对用户进行了身份验证并加载了他们的角色。`adminUser`用户被授权访问受保护的资源。

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.

用户 `adminUser`应被禁止访问受 `@RolesAllowed("standardRole")`保护的资源,因为它没有这个角色。

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%

最后,使用用户 `standardUser`会起作用,并且安全上下文包含主体详细信息(例如用户名)。

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 Reference

Unresolved directive in security-ldap.adoc - include::{generated-dir}/config/quarkus-elytron-security-ldap.adoc[]