编辑此页面

使用 LDAP Realm 进行安全

本指南演示了您的 Quarkus 应用程序如何使用 LDAP 服务器来认证和授权您的用户身份。

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

架构

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

  • /api/public

  • /api/users/me

  • /api/admin

/api/public 端点可以匿名访问。/api/admin 端点受到 RBAC(基于角色的访问控制)的保护,只有获得 adminRole 角色的用户才能访问。在此端点,我们使用 @RolesAllowed 注解来声明性地强制执行访问约束。/api/users/me 端点也受到 RBAC(基于角色的访问控制)的保护,只有获得 standardRole 角色的用户才能访问。作为响应,它返回一个包含用户详细信息的 JSON 文档。

默认情况下,Quarkus 将限制应用程序内 JNDI 的使用,以作为预防措施,尝试减轻未来类似 Log4Shell 的漏洞。由于基于 LDAP 的身份验证需要 JNDI,因此此保护将自动禁用。

解决方案

我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。

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

解决方案位于 security-ldap-quickstart 目录中。

创建 Maven 项目

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

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

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

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

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数包装在双引号中,例如 "-DprojectArtifactId=security-ldap-quickstart"

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

如果您已经配置了 Quarkus 项目,可以通过在项目基本目录中运行以下命令来将 elytron-security-ldap 扩展添加到您的项目中

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")

编写应用程序

让我们从实现 /api/public 端点开始。从下面的源代码中可以看到,它只是一个普通的 Jakarta REST 资源

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 角色的用户才能访问该端点。

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 服务器加载的。

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();
    }
}

配置应用程序

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)
%test.quarkus.security.ldap.dir-context.url=ldap://127.0.0.1:10389 (2)
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) (3)
quarkus.security.ldap.identity-mapping.attribute-mappings."0".filter-base-dn=ou=Roles,dc=quarkus,dc=io
1 您需要提供 LDAP 服务器的 URL。此示例要求 LDAP 服务器已导入此 LDIF 文件
2 我们的测试资源使用的 URL。测试可以利用 Quarkus 提供的 LdapServerTestResource,就像我们在示例应用程序的测试覆盖范围中所做的那样 一样
3 {0}uid 替换。

quarkus-elytron-security-ldap 扩展需要一个 dir-context 和一个 identity-mapping,其中至少包含一个 attribute-mapping,用于认证用户及其身份。

默认情况下,Quarkus 不会缓存从 LDAP 目录获得的凭据。每次请求您的服务都会导致与 LDAP 服务器进行额外的往返。

缓存这些结果以提高性能是一种常见做法,但代价是 LDAP 中的更改生效需要一段时间。

要启用缓存,请在配置文件中设置 quarkus.security.ldap.cache.enabled=true

默认缓存的最大年龄为 60s。可以通过设置 quarkus.security.ldap.cache.max-age 来配置。

缓存条目数受 quarkus.security.ldap.cache.size 限制,默认为 100

将 LDAP 组映射到 SecurityIdentity 角色

先前描述的应用程序配置显示了如何将 LDAP 专有名称组的 CN 属性映射到 Quarkus SecurityIdentity 角色。更具体地说,standardRole CN 被映射到 SecurityIdentity 角色,从而允许访问 UserResource#me 端点。但是,所需的 SecurityIdentity 角色可能因应用程序而异,您可能需要将 LDAP 组映射到本地 SecurityIdentity 角色,如下例所示。

quarkus.http.auth.roles-mapping."standardRole"=user (1)
1 standardRole 角色映射到应用程序特定的 SecurityIdentity 角色 user

测试应用程序

现在应用程序受到保护,身份由我们的 LDAP 服务器提供。让我们在开发模式下启动应用程序。

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

首先要检查的是确保匿名访问正常工作。

$ curl -i -X GET https://:8080/api/public
HTTP/1.1 200 OK
Content-Length: 6
Content-Type: text/plain;charset=UTF-8

public%

现在,让我们尝试访问受保护的资源。不进行身份验证。

$ curl -i -X GET https://:8080/api/admin
HTTP/1.1 401 Unauthorized
Content-Length: 14
Content-Type: text/html;charset=UTF-8

Not authorized%

到目前为止一切顺利,现在让我们尝试使用允许的用户。

$ curl -i -X GET -u adminUser:adminUserPassword https://:8080/api/admin
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8

admin%

通过提供 adminUser:adminUserPassword 凭据,该扩展会验证用户并加载其角色。adminUser 用户有权访问受保护的资源。

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

$ curl -i -X GET -u adminUser:adminUserPassword https://:8080/api/users/me
HTTP/1.1 403 Forbidden
Content-Length: 34
Content-Type: text/html;charset=UTF-8

Forbidden%

最后,使用用户 standardUser 可以正常工作,并且安全上下文包含主体详细信息(例如用户名)。

$ curl -i -X GET -u standardUser:standardUserPassword https://:8080/api/users/me
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8

user%

配置参考

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

启用 ldap elytron 模块的选项

环境变量:QUARKUS_SECURITY_LDAP_ENABLED

显示更多

布尔值

false

elytron 域名称

环境变量:QUARKUS_SECURITY_LDAP_REALM_NAME

显示更多

字符串

Quarkus

是否根据 ldap 验证提供的凭据?

环境变量:QUARKUS_SECURITY_LDAP_DIRECT_VERIFICATION

显示更多

布尔值

true

ldap 服务器的 url

环境变量:QUARKUS_SECURITY_LDAP_DIR_CONTEXT_URL

显示更多

字符串

必需

用于连接到 ldap 服务器的主体:用户(也称为 "bindDn")

环境变量:QUARKUS_SECURITY_LDAP_DIR_CONTEXT_PRINCIPAL

显示更多

字符串

与主体关联的密码(也称为 "bindCredential")

环境变量:QUARKUS_SECURITY_LDAP_DIR_CONTEXT_PASSWORD

显示更多

字符串

如何处理 ldap 重定向

环境变量:QUARKUS_SECURITY_LDAP_DIR_CONTEXT_REFERRAL_MODE

显示更多

ignore, follow, throw

ignore

连接超时

环境变量:QUARKUS_SECURITY_LDAP_DIR_CONTEXT_CONNECT_TIMEOUT

显示更多

Duration 

5S

读取超时

环境变量:QUARKUS_SECURITY_LDAP_DIR_CONTEXT_READ_TIMEOUT

显示更多

Duration 

60S

如果设置为 true,则缓存到 LDAP 服务器的请求

环境变量:QUARKUS_SECURITY_LDAP_CACHE_ENABLED

显示更多

布尔值

false

条目可以在缓存中保留的时间

环境变量:QUARKUS_SECURITY_LDAP_CACHE_MAX_AGE

显示更多

Duration 

60S

要保留在缓存中的条目的最大数量

环境变量:QUARKUS_SECURITY_LDAP_CACHE_SIZE

显示更多

整数

100

与提供的用户关联的标识符(也称为 "baseFilter")

环境变量:QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_RDN_IDENTIFIER

显示更多

字符串

uid

我们搜索用户的 dn

环境变量:QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_SEARCH_BASE_DN

显示更多

字符串

必需

是否也搜索子节点以查找身份

环境变量:QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_SEARCH_RECURSIVE

显示更多

布尔值

false

映射的 roleAttributeId(例如 "cn")

环境变量:QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__FROM

显示更多

字符串

必需

映射属性的标识符(在 Quarkus 中为 "groups",在 WildFly 中为 "Roles")

环境变量:QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__TO

显示更多

字符串

groups

过滤器(也称为 "roleFilter")

环境变量:QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__FILTER

显示更多

字符串

必需

过滤器基 dn(也称为 "rolesContextDn")

环境变量:QUARKUS_SECURITY_LDAP_IDENTITY_MAPPING_ATTRIBUTE_MAPPINGS__ATTRIBUTE_MAPPINGS__FILTER_BASE_DN

显示更多

字符串

必需

关于 Duration 格式

要编写 duration 值,请使用标准的 java.time.Duration 格式。有关更多信息,请参阅 Duration#parse() Java API 文档

您还可以使用简化的格式,以数字开头

  • 如果该值仅为一个数字,则表示以秒为单位的时间。

  • 如果该值是一个数字后跟 ms,则表示以毫秒为单位的时间。

在其他情况下,简化格式将被转换为 java.time.Duration 格式以进行解析

  • 如果该值是一个数字后跟 hms,则在其前面加上 PT

  • 如果该值是一个数字后跟 d,则在其前面加上 P

相关内容