使用 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 项目
首先,我们需要一个新项目。使用以下命令创建一个新项目
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数包装在双引号中,例如"-DprojectArtifactId=security-ldap-quickstart"
此命令会生成一个项目,导入 elytron-security-ldap
扩展,该扩展是 wildfly-elytron-realm-ldap
适配器,适用于 Quarkus 应用程序。
如果您已经配置了 Quarkus 项目,可以通过在项目基本目录中运行以下命令来将 elytron-security-ldap
扩展添加到您的项目中
quarkus extension add elytron-security-ldap
./mvnw quarkus:add-extension -Dextensions='elytron-security-ldap'
./gradlew addExtension --extensions='elytron-security-ldap'
这会将以下内容添加到您的构建文件中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-ldap</artifactId>
</dependency>
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 中的更改生效需要一段时间。 要启用缓存,请在配置文件中设置 默认缓存的最大年龄为 缓存条目数受 |
将 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 服务器提供。让我们在开发模式下启动应用程序。
quarkus dev
./mvnw quarkus:dev
./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 验证提供的凭据? 环境变量: 显示更多 |
布尔值 |
|
字符串 |
必需 |
|
用于连接到 ldap 服务器的主体:用户(也称为 "bindDn") 环境变量: 显示更多 |
字符串 |
|
与主体关联的密码(也称为 "bindCredential") 环境变量: 显示更多 |
字符串 |
|
如何处理 ldap 重定向 环境变量: 显示更多 |
|
|
连接超时 环境变量: 显示更多 |
|
|
读取超时 环境变量: 显示更多 |
|
|
如果设置为 true,则缓存到 LDAP 服务器的请求 环境变量: 显示更多 |
布尔值 |
|
|
||
整数 |
|
|
与提供的用户关联的标识符(也称为 "baseFilter") 环境变量: 显示更多 |
字符串 |
|
我们搜索用户的 dn 环境变量: 显示更多 |
字符串 |
必需 |
是否也搜索子节点以查找身份 环境变量: 显示更多 |
布尔值 |
|
映射的 roleAttributeId(例如 "cn") 环境变量: 显示更多 |
字符串 |
必需 |
映射属性的标识符(在 Quarkus 中为 "groups",在 WildFly 中为 "Roles") 环境变量: 显示更多 |
字符串 |
|
过滤器(也称为 "roleFilter") 环境变量: 显示更多 |
字符串 |
必需 |
过滤器基 dn(也称为 "rolesContextDn") 环境变量: 显示更多 |
字符串 |
必需 |
关于 Duration 格式
要编写 duration 值,请使用标准的 您还可以使用简化的格式,以数字开头
在其他情况下,简化格式将被转换为
|