Using Security with an LDAP Realm
本指南演示了 Quarkus 应用程序如何使用 LDAP 服务器对用户身份进行身份验证和授权。
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
Architecture
在此示例中,我们构建了一个非常简单的微服务,它提供三个端点:
-
/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,此保护将被自动禁用。
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
克隆 Git 存储库: git clone $${quickstarts-base-url}.git
,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。
该解决方案位于`security-ldap-quickstart` directory中。
Creating the Maven Project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令生成一个项目,导入`elytron-security-ldap`扩展 - 它是一个适用于 Quarkus 应用程序的`wildfly-elytron-realm-ldap`适配器。
如果你已配置好 Quarkus 项目,可以通过在项目基本目录中运行以下命令将`elytron-security-ldap`扩展添加到项目中:
quarkus extension add {add-extension-extensions}
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
./gradlew addExtension --extensions='{add-extension-extensions}'
这会将以下内容添加到构建文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-ldap</artifactId>
</dependency>
implementation("io.quarkus:quarkus-elytron-security-ldap")
Writing the application
首先,实现 /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();
}
}
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 | 你需要向 LDAP 服务器提供一个 URL。此示例要求 LDAP 服务器已导入 this LDIF file。 |
2 | `{0}`被`uid`代替。 |
3 | 由测试资源使用的 URL。测试可以利用 Quarkus 提供的`LdapServerTestResource`作为示例应用程序的测试覆盖率中的 we do。 |
`elytron-security-ldap`扩展需要一个目录上下文以及一个包含至少一个属性映射的标识映射,以验证用户及其标识。
Testing the Application
该应用程序现在已受到保护,并且标识由我们的 LDAP 服务器提供。让我们在开发模式下启动应用程序:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
要检查的第一件事是确保匿名访问有效。
$ 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%
现在,我们尝试以匿名方式查找受保护的资源。
$ 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%
目前为止还不错,现在我们尝试使用允许的用户。
$ 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`用户被授权访问受保护的资源。
用户 `adminUser`应被禁止访问受 `@RolesAllowed("standardRole")`保护的资源,因为它没有这个角色。
$ 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`会起作用,并且安全上下文包含主体详细信息(例如用户名)。
$ 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 include directive in modules/ROOT/pages/security-ldap.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-elytron-security-ldap.adoc[]