安全架构


Red Hat build of Quarkus 3.27

Red Hat Customer Content Services

摘要

Quarkus 安全架构通过 HttpAuthenticationMechanism 接口为 HTTP 应用提供通用身份验证机制和可自定义的安全性。探索根据您的需要量身定制的身份验证选项的范围,并了解身份提供程序的角色,以验证用户身份并使用 SecurityIdentity 实例管理访问权限。该框架还支持主动身份验证,允许在各种应用程序上下文中进行定制设置和高效的异常处理。

第 1 章 Quarkus 安全架构

Quarkus 安全架构提供了几个内置身份验证机制,并高度自定义。在 Quarkus 中保护 HTTP 应用的主要机制是 HttpAuthenticationMechanism 接口。

1.1. Quarkus 安全架构概述

当客户端发送 HTTP 请求时,Quarkus 安全通过与几个内置核心组件交互来编配安全身份验证和授权,包括 HttpAuthenticationMechanismIdentityProviderSecurityIdentityAugmentor

顺序安全验证过程会产生三个结果之一:

  • HTTP 请求经过身份验证和授权,并被授予对 Quarkus 应用的访问权限。
  • HTTP 请求身份验证失败,请求者会收到特定于身份验证机制的质询,例如: 401 错误、重新验证 URL 重定向或某些其他自定义身份验证质询响应。有关质询响应的实际示例,请参阅 Quarkus 安全提示和 Tricks 指南。
  • HTTP 请求授权失败,请求者被拒绝访问 Quarkus 应用。

下图显示了 Quarkus 安全架构的详细流程的步骤:

图 1.1. Quarkus 安全架构和进程流

1.2. Quarkus 安全架构的核心组件

1.2.1. HttpAuthenticationMechanism

quarkus Security 使用 HttpAuthenticationMechanism 从 HTTP 请求中提取身份验证凭据,并将它们委派给 IdentityProvider,以将凭据转换为 SecurityIdentity。例如,凭据可以来自 Authorization 标头、客户端 HTTPS 证书或 Cookie。

当 Quarkus 安全拒绝身份验证请求时,HttpAuthenticationMechanism 会向客户端返回身份验证质询。质询类型取决于身份验证机制。例如,使用 OIDC OpenID Connect (OIDC)授权代码流机制,会生成一个重定向 URL,客户端将返回到 OpenID Connect 供应商进行验证。

1.2.2. IdentityProvider

IdentityProvider 验证身份验证凭据,并将它们映射到 SecurityIdentity,后者具有用户名、角色、原始身份验证凭据和其他属性。

您可以为每个经过身份验证的用户注入 SecurityIdentity 实例,以获取经过身份验证的身份信息。

在其他上下文中,可以具有与其他相同信息或部分的并行表示,例如,用于 Jakarta REST 的 SecurityContext 或 JSON Web 令牌(JWT)的 JsonWebToken

如需更多信息,请参阅 Quarkus 身份提供程序 指南。

1.2.3. SecurityIdentityAugmentor

由于 Quarkus Security 是自定义的,例如,您可以将授权角色添加到 SecurityIdentity 中,然后注册并排列一个或多个 SecurityAugmentor 实现。

注册的 SecurityIdentityAugmentor 实例会在安全身份验证过程的最终阶段调用。如需更多信息,请参阅 "Security Tips and Tricks" 指南中的 Security Identity Customization 部分。

1.3. 支持的验证机制

Quarkus 安全框架支持多种身份验证机制,也可以组合使用。一些支持的身份验证机制内置在 Quarkus 中,另一些则要求您添加扩展名。

要了解 Quarkus 中的安全身份验证以及支持的机制和协议,请参阅 Quarkus 指南中的 Quarkus 身份验证机制

1.4. 主动验证

主动验证在 Quarkus 中默认启用。如果传入请求具有凭证,即使目标页面不需要身份验证,请求也会被验证。如需更多信息,请参阅 Quarkus 主动身份验证 指南。

1.5. Quarkus 安全自定义

Quarkus Security 可自定义。您可以自定义 Quarkus 的以下核心安全组件:

  • HttpAuthenticationMechanism
  • IdentityProvider
  • SecurityidentityAugmentor

有关自定义 Quarkus 安全性的更多信息,包括被动安全性以及如何注册安全供应商,请参阅 Quarkus 安全提示和技巧 指南。

1.6. 参考

第 2 章 Quarkus 中的验证机制

Quarkus 安全框架支持多种身份验证机制,您可以使用它们来保护应用程序。您还可以组合身份验证机制。

提示

在选择用于保护 Quarkus 应用程序的身份验证机制前,请查看提供的信息。

2.1. 支持的身份验证机制概述

一些支持的身份验证机制内置在 Quarkus 中,另一些则要求您添加扩展名。所有这些机制都包括在以下部分中:

下表将特定的身份验证要求映射到您可以在 Quarkus 中使用的支持机制:

Expand
表 2.1. 身份验证要求和机制
身份验证要求身份验证机制

用户名和密码

基本的 ,基于表单的身份验证

bearer 访问令牌

OIDC Bearer 令牌身份验证,JWT

单点登录(SSO)

OIDC 代码流 ,基于表单的身份验证

客户端证书

双向 TLS 身份验证

如需更多信息,请参阅以下 令牌身份验证机制比较 表。

2.2. 内置身份验证机制

Quarkus Security 提供以下内置身份验证支持:

2.2.1. 基本身份验证(Basic authentication)

您可以使用内置 HTTP 基本身份验证机制保护 Quarkus 应用程序端点。如需更多信息,请参阅以下文档:

2.2.2. 基于表单的身份验证

Quarkus 提供基于表单的身份验证,它们的工作方式类似于传统的 Servlet 形式的身份验证。与传统的形式身份验证不同,经过身份验证的用户不会存储在 HTTP 会话中,因为 Quarkus 不支持集群的 HTTP 会话。相反,身份验证信息存储在加密的 Cookie 中,可由共享同一加密密钥的所有群集成员读取。

要应用加密,请添加 quarkus.http.auth.session.encryption-key 属性,并确保您设置的值至少为 16 个字符。加密密钥使用 SHA-256 进行哈希处理。生成的摘要用作 Cookie 值的 AES-256 加密的密钥。Cookie 包含作为加密值的一部分的到期时间,因此集群中的所有节点都必须同步其时钟。在一分钟的间隔内,如果会话正在使用,则会使用更新的到期时间生成新的 Cookie。

要开始使用表单身份验证,您应该有类似的设置,如 启用基本身份验证和属性 quarkus.http.auth.form.enabled 所述,必须设置为 true

带有表单验证的简单 application.properties 类似如下:

quarkus.http.auth.form.enabled=true

quarkus.http.auth.form.login-page=login.html
quarkus.http.auth.form.landing-page=hello
quarkus.http.auth.form.error-page=

# Define testing user
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.alice=alice
quarkus.security.users.embedded.roles.alice=user
Copy to Clipboard Toggle word wrap
重要

在 application.properties 文件中配置用户名、secret 和角色只适用于测试场景。对于保护生产应用,使用数据库或 LDAP 存储此信息至关重要。如需更多信息,您可以查看 带有 Jakarta Persistence 的 Quarkus 安全性,或其他 启用基本身份验证 中所述。

应用程序登录页面将包含类似如下的 HTML 表单:

<form action="/j_security_check" method="post">
    <label>Username</label>
    <input type="text" placeholder="Username" name="j_username" required>
    <label>Password</label>
    <input type="password" placeholder="Password" name="j_password" required>
    <button type="submit">Login</button>
</form>
Copy to Clipboard Toggle word wrap

使用单页应用程序(SPA),您通常希望通过删除默认页面路径来避免重定向,如下例所示:

# do not redirect, respond with HTTP 200 OK
quarkus.http.auth.form.landing-page=

# do not redirect, respond with HTTP 401 Unauthorized
quarkus.http.auth.form.login-page=
quarkus.http.auth.form.error-page=

# HttpOnly must be false if you want to log out on the client; it can be true if logging out from the server
quarkus.http.auth.form.http-only-cookie=false
Copy to Clipboard Toggle word wrap

现在,您已为 SPA 禁用了重定向,您必须以编程方式从客户端登录并注销。以下是登录 j_security_check 端点的 JavaScript 方法示例,并通过销毁 Cookie 来注销应用。

const login = () => {
    // Create an object to represent the form data
    const formData = new URLSearchParams();
    formData.append("j_username", username);
    formData.append("j_password", password);

    // Make an HTTP POST request using fetch against j_security_check endpoint
    fetch("j_security_check", {
        method: "POST",
        body: formData,
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
        },
    })
    .then((response) => {
        if (response.status === 200) {
            // Authentication was successful
            console.log("Authentication successful");
        } else {
            // Authentication failed
            console.error("Invalid credentials");
        }
    })
    .catch((error) => {
        console.error(error);
    });
};
Copy to Clipboard Toggle word wrap

要从客户端注销 SPA,cookie 必须设为 quarkus.http.auth.form.http-only-cookie=false,以便您可以销毁 Cookie,并可能重定向到您的主页。

const logout= () => {
    // delete the credential cookie, essentially killing the session
    const removeCookie = `quarkus-credential=; Max-Age=0;path=/`;
    document.cookie = removeCookie;

    // perform post-logout actions here, such as redirecting back to your login page
};
Copy to Clipboard Toggle word wrap

要从服务器注销 SPA,cookie 可以设置为 quarkus.http.auth.form.http-only-cookie=true,并使用此示例代码销毁 Cookie。

import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.POST;

@Inject
SecurityIdentity identity;

@POST
public Response logout() {
    if (identity.isAnonymous()) {
        throw new UnauthorizedException("Not authenticated");
    }
    FormAuthenticationMechanism.logout(identity); 
1

    return Response.noContent().build();
}
Copy to Clipboard Toggle word wrap
1
通过删除会话 Cookie 来执行注销。
2.2.2.1. 基于表单的身份验证配置参考

以下属性可用于配置基于表单的身份验证:

build 时修复的 :在构建时修复的配置属性 - 所有其他配置属性在运行时可覆盖

Expand

配置属性

Type

default

quarkus.http.auth.permission."permissions".enabled

确定是否启用整个权限集。

默认情况下,如果定义了权限集,则会启用它。

环境变量: QUARKUS_HTTP_AUTH_PERMISSION__PERMISSIONS__ENABLED

布尔值

 

quarkus.http.auth.permission."permissions".policy

此权限集链接到的 HTTP 策略。

有三个内置策略:allow、deny 和 authenticated。可以定义基于角色的策略,扩展可以添加自己的策略。

环境变量: QUARKUS_HTTP_AUTH_PERMISSION__PERMISSIONS__POLICY

string

所需的 HEKETI Required

quarkus.http.auth.permission."permissions".methods

此权限集的方法应用到。如果没有设置,则适用于所有方法。

请注意,如果请求与任何权限集中的任何路径匹配,但由于未列出方法,则请求将被拒绝。

方法特定权限优先于未设置任何方法的匹配项。

这意味着,如果 Quarkus 配置为允许 GET 和 POST 请求到 /admin,且没有将其他权限配置为 /admin 将被拒绝。

环境变量: QUARKUS_HTTP_AUTH_PERMISSION__PERMISSIONS__METHODS

字符串列表

 

quarkus.http.auth.permission."permissions".paths

此权限检查应用到的路径。如果路径以 86] 结尾,则这被视为路径前缀,否则它将被视为完全匹配。

匹配会以长度为基础完成,因此最具体的路径匹配具有优先权。

如果多个权限集与同一路径匹配,那么显式方法匹配优先于设置方法,否则应用最严格的权限。

环境变量: QUARKUS_HTTP_AUTH_PERMISSION__PERMISSIONS__PATHS

字符串列表

 

quarkus.http.auth.permission."permissions".auth-mechanism

必须用来验证用户身份的路径特定身份验证机制。它需要匹配 io.quarkus.vertx.http.runtime.security.HttpCredentialTransport 身份验证方案,如 'basic'、'bearer'、'form' 等。

环境变量: QUARKUS_HTTP_AUTH_PERMISSION__PERMISSIONS__AUTH_MECHANISM

string

 

quarkus.http.auth.permission."permissions".shared

表示除了具有获奖路径的策略外,此策略还始终适用于匹配的路径。避免创建多个共享策略,以最大程度降低性能影响。

环境变量: QUARKUS_HTTP_AUTH_PERMISSION__PERMISSIONS__SHARED

布尔值

false

quarkus.http.auth.permission."permissions".applies-to

权限检查是否应该应用到所有匹配路径,还是专用于 Jakarta REST 资源的路径。

环境变量: QUARKUS_HTTP_AUTH_PERMISSION__PERMISSIONS__APPLIES_TO

All :适用于所有匹配路径。

jaxrs: Declares,权限检查必须仅应用于 Jakarta REST 请求路径。使用这个选项,延迟权限检查是否使用匹配的 Jakarta REST 端点上的注解选择验证机制。如果使用以下 REST 端点注解,则必须设置这个选项:- io.quarkus.oidc.Tenant 注解,它选择带有租户标识符的 OIDC 身份验证机制 - io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication,它选择 Basic 身份验证机制 - io.quarkus.vertx.http.runtime.security.annotation.FormAuthentication 选择基于 Form 的身份验证机制 - io.quarkus.vertx.http.runtime.security.annotation.MTLS Authentication,它选择 mTLS 身份验证机制 - io.quarkus.security.webauthn.WebAuth n 选择 WebAuth 身份验证机制 - io.quarkus.oidc.BearerToken Authentication,它选择 OpenID Connect Bearer 令牌身份验证机制 - - io.quarkus.oidc.Authorization CodeFlow,它选择 OpenID Connect Code 身份验证机制

all

quarkus.http.auth.policy."role-policy".roles-allowed

允许访问此策略保护的资源的角色。默认情况下,任何经过身份验证的用户都允许访问。

环境变量: QUARKUS_HTTP_AUTH_POLICY_ROLE_POLICY__ROLES_ALLOWED

字符串列表

**

quarkus.http.auth.policy."role-policy".roles."role-name"

根据 SecurityIdentity 已存在的角色,为 SecurityIdentity 添加授予的角色。例如,Quarkus OIDC 扩展可以映射来自验证的 JWT 访问令牌的角色,您可能希望将它们重新映射到部署特定的角色。

环境变量: QUARKUS_HTTP_AUTH_POLICY__ROLE_POLICY__ROLES__ROLE_NAME_

Map<String,List<String>>

 

quarkus.http.auth.policy."role-policy".permissions."role-name"

如果成功应用此策略,则授予 SecurityIdentity 的权限(策略允许请求继续),并且经过身份验证的请求具有必要的角色。例如,您可以通过设置 quarkus.http.auth.policy.role-policy1.permissions. admin =perm1:action1,perm1: action2 配置属性 将每个m1 的权限映射为 role admin授予的权限用于通过 @PermissionsAllowed 注释进行授权。

环境变量: QUARKUS_HTTP_AUTH_POLICY__ROLE_POLICY__PERMISSIONS__ROLE_NAME_

Map<String,List<String>>

 

quarkus.http.auth.policy."role-policy".permission-class

此策略授予的权限将通过此配置属性指定的 java.security.Permission 实施来创建。权限类必须声明一个接受权限名称(String )或权限名称和操作(字符串、字符串[])的构造器。如果您以原生模式运行应用程序,则必须注册权限类,以反映其是否运行您的应用程序。

环境变量: QUARKUS_HTTP_AUTH_POLICY__ROLE_POLICY__PERMISSION_CLASS

string

io.quarkus.security.StringPermission

quarkus.http.auth.roles-mapping."role-name"

SecurityIdentity 角色映射到部署特定的角色,并将匹配的角色添加到 SecurityIdentity

例如,如果 SecurityIdentity 具有用户角色,并且端点使用 'UserRole' 角色进行保护,则使用此属性将用户角色映射到 UserRole 角色,并且具有 SecurityIdentity 角色,并且具有 user UserRole 角色。

环境变量: QUARKUS_HTTP_AUTH_ROLES_MAPPING__ROLE_NAME_

Map<String,List<String>>

 

quarkus.http.auth.certificate-role-attribute

客户端证书属性,其值将根据证书属性文件中指定的角色映射映射到"安全Identity"角色。属性必须是 Relative Distinguished Names (RDN)或 Subject Alternative Names (SAN)之一。默认情况下,通用名称(CN)属性值用于角色映射。支持的值有:

  • rdn type - Distinguished Name 字段。例如 'CN' 代表 Common Name 字段。目前不支持 Multivalued RNDs 和相同属性的多个实例。
  • 'SAN_RFC822' - Subject Alternative Name field RFC 822 Name.
  • 'SAN_URI' - Subject Alternative Name field Uniform Resource Identifier (URI)。
  • 'SAN_ANY' - Subject Alternative Name field Other Name.请注意,只支持 UTF8 标识符映射的简单情况。例如,您可以将 'other-identifier' 映射到 SecurityIdentity 角色。如果您使用 'openssl' 工具,则支持其他名称定义类似如下: subjectAltName=otherName:1.2.3.4;UTF8:other-identifier

环境变量: QUARKUS_HTTP_AUTH_CERTIFICATE_ROLE_ATTRIBUTE

string

CN

quarkus.http.auth.certificate-role-properties

包含客户端证书属性值到角色映射的属性文件。只有在使用 quarkus.http.ssl.client-auth=requiredquarkus.http.ssl.client-auth=request 启用 mTLS 身份验证机制时,才使用它。

属性文件预期具有 CN_VALUE=role1,role,…​,roleN 格式,并使用 UTF-8 进行编码。

环境变量: QUARKUS_HTTP_AUTH_CERTIFICATE_ROLE_PROPERTIES

path

 

quarkus.http.auth.realm

身份验证域

环境变量: QUARKUS_HTTP_AUTH_REALM

string

 

quarkus.http.auth.form.login-page

登录页面。可以通过设置 quarkus.http.auth.form.login-page= 来禁用重定向到登录页面。

环境变量: QUARKUS_HTTP_AUTH_FORM_LOGIN_PAGE

string

/login.html

quarkus.http.auth.form.username-parameter

用户名字段名称。

环境变量: QUARKUS_HTTP_AUTH_FORM_USERNAME_PARAMETER

string

j_username

quarkus.http.auth.form.password-parameter

密码字段名称。

环境变量: QUARKUS_HTTP_AUTH_FORM_PASSWORD_PARAMETER

string

j_password

quarkus.http.auth.form.error-page

错误页面。可以通过设置 quarkus.http.auth.form.error-page= 来禁用重定向到错误页面。

环境变量: QUARKUS_HTTP_AUTH_FORM_ERROR_PAGE

string

/error.html

quarkus.http.auth.form.landing-page

如果没有保存的页面要重定向到,则要重定向到的登录页面。可以通过设置 quarkus.http.auth.form.landing-page= 来禁用到登录页面。

环境变量: QUARKUS_HTTP_AUTH_FORM_LANDING_PAGE

string

/index.html

quarkus.http.auth.form.location-cookie

选项控制用于将用户重新定向到他们要访问的位置的 Cookie 名称。

环境变量: QUARKUS_HTTP_AUTH_FORM_LOCATION_COOKIE

string

quarkus-redirect-location

quarkus.http.auth.form.timeout

不活跃(空闲)超时

当达到不活跃超时时,cookie 不会更新,并会强制使用新的登录。

环境变量: QUARKUS_HTTP_AUTH_FORM_TIMEOUT

持续时间 5-4 Duration 格式

PT30M

quarkus.http.auth.form.new-cookie-interval

Cookie 在新的 Cookie 被更新超时替代之前如何获得,也称为"renewal-timeout"。

请注意,较小的值会导致服务器负载稍多(因为新加密的 Cookie 将更频繁生成);但是,较大的值会影响不活动超时,因为在生成 Cookie 时设置了超时。

例如,如果这设为 10 分钟,并且不活跃超时为 30m,如果用户的最后一个请求为 9m,则实际超时将在最后一次请求后发生 21m,因为仅在生成新 Cookie 时超时才会刷新。

也就是说,在服务器端不会跟踪超时;时间戳采用 Cookie 本身进行编码和加密,并通过每个请求进行解密和解析。

环境变量: QUARKUS_HTTP_AUTH_FORM_NEW_COOKIE_INTERVAL

持续时间 5-4 Duration 格式

PT1M

quarkus.http.auth.form.cookie-name

用于存储持久会话的 Cookie

环境变量: QUARKUS_HTTP_AUTH_FORM_COOKIE_NAME

string

quarkus-credential

quarkus.http.auth.form.cookie-path

会话和位置 Cookie 的 Cookie 路径。

环境变量: QUARKUS_HTTP_AUTH_FORM_COOKIE_PATH

string

/

quarkus.http.auth.form.cookie-domain

Cookie 域参数值(如果设置了)将用于会话和位置 Cookie。

环境变量: QUARKUS_HTTP_AUTH_FORM_COOKIE_DOMAIN

string

 

quarkus.http.auth.form.http-only-cookie

设置 HttpOnly 属性,以防止通过 JavaScript 访问 Cookie。

环境变量: QUARKUS_HTTP_AUTH_FORM_HTTP_ONLY_COOKIE

布尔值

false

quarkus.http.auth.form.cookie-same-site

会话和位置 Cookie 的 SameSite 属性。

环境变量: QUARKUS_HTTP_AUTH_FORM_COOKIE_SAME_SITE

strict,lax,none

strict

quarkus.http.auth.form.cookie-max-age

会话 Cookie 的 max-Age 属性。这是浏览器将保留 Cookie 的时间。

默认值为空,这意味着 Cookie 将保留,直到浏览器关闭为止。

环境变量: QUARKUS_HTTP_AUTH_FORM_COOKIE_MAX_AGE

持续时间 5-4 Duration 格式

 

quarkus.http.auth.form.post-location

后位置。

环境变量: QUARKUS_HTTP_AUTH_FORM_POST_LOCATION

string

/j_security_check

quarkus.http.auth.inclusive

要求所有注册的 HTTP 身份验证机制都必须尝试验证请求凭据。

默认情况下,当 inclusive-mode 严格时,每个注册的身份验证机制都必须生成 SecurityIdentity,否则,生成身份的多个机制可能小于注册机制的总数。

所有生成的安全身份都可以使用以下工具的方法检索:

`io.quarkus.vertx.http.runtime.security.HttpSecurityUtils++#++getSecurityIdentities(io.quarkus.security.identity.SecurityIdentity)`
Copy to Clipboard Toggle word wrap

注入的 SecurityIdentity 代表一个由第一个包含身份验证机制生成的身份。当需要 mTLS 身份验证时,mTLS 机制始终是第一个机制,因为它的优先级会在包含验证时提升。

此属性默认为 false,这意味着当创建第一个 SecurityIdentity 时,身份验证过程会马上完成。

如果启用了特定于路径的身份验证,则此属性将被忽略。

环境变量: QUARKUS_HTTP_AUTH_INCLUSIVE

布尔值

false

quarkus.http.auth.inclusive-mode

包含的身份验证模式。

环境变量: QUARKUS_HTTP_AUTH_INCLUSIVE_MODE

lax: 如果至少有一个注册的 HTTP 身份验证机制创建了身份,则身份验证会成功。

严格 :如果所有注册的 HTTP 身份验证机制都创建身份,则身份验证成功。通常,当凭证通过 mTLS 执行时,包含的身份验证应该处于严格的模式,当 mTLS 和另一个身份验证(如 OIDC bearer 令牌身份验证)时,必须成功。在这种情况下,由第一个机制 mTLS 创建的 SecurityIdentity 可以注入,其他机制创建的身份将在 SecurityIdentity 上提供。

strict

关于 Duration 格式

要写入持续时间值,请使用标准 java.time.Duration 格式。如需更多信息,请参阅 Duration#parse ()Java API 文档

您还可以使用简化的格式,从数字开始:

  • 如果值只是一个数字,它代表时间(以秒为单位)。
  • 如果值为数字,后跟 ms,代表时间(毫秒)。

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

  • 如果该值是一个数字,后跟 hms,则前缀为 PT
  • 如果值为数字,后跟 d,则会以 P 为前缀。
2.2.2.2. 以编程方式设置基于表单的身份验证

除了 基于 Form 的身份验证配置参考部分中列出的配置 属性外,Quarkus 在运行时支持程序设置,如下例所示:

package org.acme.http.security;

import io.quarkus.vertx.http.security.Form;
import io.quarkus.vertx.http.security.HttpSecurity;
import jakarta.enterprise.event.Observes;

public class FormConfiguration {

    void configure(@Observes HttpSecurity httpSecurity) {   
1

        httpSecurity.mechanism(Form.builder()
                .httpOnlyCookie()
                .loginPage("/my-login.html")
                .errorPage("/my-error.html")
                .build());
    }

}
Copy to Clipboard Toggle word wrap
1
观察 io.quarkus.vertx.http.security.HttpSecurity CDI 事件,并以编程方式配置 Form 身份验证机制。

2.2.3. 双向 TLS 身份验证

Quarkus 提供 mutual TLS (mTLS)身份验证,以便您可以根据其 X.509 证书验证用户。

要使用此验证方法,您必须首先为应用程序启用 SSL/TLS。如需更多信息,请参阅 Quarkus "HTTP 参考" 指南中的支持使用 SSL/TLS 的安全连接 部分。

在应用程序接受安全连接后,下一步是使用保存应用程序信任的所有证书的 quarkus.http.ssl.certificate.trust-store-file 属性配置 quarkus.http.ssl.certificate.trust-store-file 属性。此文件还包含有关当客户端(如浏览器或其他服务)如何请求证书的信息,请尝试访问受保护的资源之一。

因为 JKS 不再是 Quarkus 中的默认密钥存储和信任存储格式,因此框架会根据文件扩展名进行 educated guess:

  • .pem.crt.key 显示为 PEM 证书和密钥。
  • .jks.keystore、和 .truststore 显示为 JKS 密钥存储和信任存储。
  • .p12.pkcs12.pfx 显示为 PKCS12 密钥存储和信任存储。

如果您的文件没有使用其中一个扩展,则必须使用以下属性设置格式:

quarkus.http.ssl.certificate.key-store-file-type=JKS  # or P12 or PEM
quarkus.http.ssl.certificate.trust-store-file-type=JKS  # or P12 or PEM
Copy to Clipboard Toggle word wrap

JKS 变得不常使用。从 Java 9 开始,Java 的默认密钥存储格式是 PKCS12。JKS 和 PKCS12 之间的显著区别在于 JKS 是特定于 Java 的格式。相反,PKCS12 是一种标准化、不中立的方式,用于存储加密的私钥和证书。

以下是启用 mTLS 的示例配置:

quarkus.http.ssl.certificate.key-store-file=server-keystore.jks 
1

quarkus.http.ssl.certificate.key-store-password=the_key_store_secret
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks 
2

quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret
quarkus.http.ssl.client-auth=required 
3

quarkus.http.auth.permission.default.paths=/* 
4

quarkus.http.auth.permission.default.policy=authenticated
quarkus.http.insecure-requests=disabled 
5
Copy to Clipboard Toggle word wrap
1
服务器私钥所在的密钥存储。
2
从中加载可信证书的信任存储。
3
quarkus.http.ssl.client-auth 设置为 required 使服务器需要客户端证书。如果服务器应该接受没有证书的请求,您可以将它设置为 REQUEST。当支持多个身份验证方法时,此设置很有用。
4
定义一个策略,其中只有经过身份验证的用户可以访问应用程序中的资源。
5
禁用普通 HTTP 协议,要求所有请求使用 HTTPS。当您将 quarkus.http.ssl.client-auth 设置为 required 时,quarkus.http.insecure-requests 会自动禁用。

当传入请求与信任存储中的有效证书匹配时,您的应用程序可以通过注入 SecurityIdentity 获取主题,如下所示:

获取主题

@Inject
SecurityIdentity identity;

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
    return String.format("Hello, %s", identity.getPrincipal().getName());
}
Copy to Clipboard Toggle word wrap

您还可以使用以下示例中介绍的代码获取证书:

获取证书

import java.security.cert.X509Certificate;
import io.quarkus.security.credential.CertificateCredential;

CertificateCredential credential = identity.getCredential(CertificateCredential.class);
X509Certificate certificate = credential.getCertificate();
Copy to Clipboard Toggle word wrap

2.2.3.1. 将证书属性映射到角色

来自客户端证书的信息可用于将角色添加到 Quarkus 安全Identity

在检查客户端证书的通用名称(CN)属性后,您可以将新角色添加到 SecurityIdentity 中。添加新角色的最简单方法是将证书属性用于角色映射功能。

例如,您可以更新部分中显示的属性,它引入了 Mutual TLS 身份验证,如下所示:

quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=the_key_store_secret
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret
quarkus.http.ssl.client-auth=required
quarkus.http.insecure-requests=disabled

quarkus.http.auth.certificate-role-properties=cert-role-mappings.properties 
1


quarkus.http.auth.permission.certauthenticated.paths=/*   
2

quarkus.http.auth.permission.certauthenticated.policy=role-policy-cert 
3

quarkus.http.auth.policy.role-policy-cert.roles-allowed=user,admin     
4
Copy to Clipboard Toggle word wrap
1
cert-role-mappings.properties classpath 资源包含证书的 CN 值映射,格式为 CN=roleCN=role1,role2 等。让我们假设它包含三个条目: alice=user、admin、 bob=userjdoe=tester
2 3 4
使用 HTTP 安全策略要求 SecurityIdentity 必须具有 useradmin 角色,才能被授权请求。

根据前面的配置,如果客户端证书的 CN 属性等于 alicebob,如果请求等同于 jdoe,则请求被授权。

2.2.3.2. 使用证书属性来增强 SecurityIdentity

如果 角色选项的自动映射证书属性不适用,则始终可以注册 SecurityIdentityAugmentor。自定义 SecurityIdentityAugmentor 可以检查不同的客户端证书属性的值,并相应地增强 SecurityIdentity

有关自定义安全身份的更多信息,请参阅 Quarkus " 安全提示 和 tricks" 指南中的 安全身份自定义 部分。

Mutual TLS 身份验证 部分中,我们在 application.properties 文件中配置 mutual TLS 客户端身份验证,如下所示:

quarkus.tls.tls-config-1.key-store.p12.path=server-keystore.p12
quarkus.tls.tls-config-1.key-store.p12.password=the_key_store_secret
quarkus.tls.tls-config-1.trust-store.p12.path=server-truststore.p12
quarkus.tls.tls-config-1.trust-store.p12.password=the_trust_store_secret
quarkus.http.ssl.client-auth=required   
1

quarkus.http.tls-configuration-name=tls-config-1    
2
Copy to Clipboard Toggle word wrap
1
启用并需要 mutual TLS 客户端身份验证。
2
tls-config-1 TLS 配置用于 HTTP 服务器 TLS 通信。

io.quarkus.vertx.http.security.HttpSecurity CDI 事件允许您以编程方式配置 mutual TLS 身份验证,如下例所示:

TLS 配置示例

package org.acme.http.security;

import io.quarkus.tls.BaseTlsConfiguration;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.KeyStoreOptions;
import io.vertx.core.net.SSLOptions;
import io.vertx.core.net.TrustOptions;

import java.util.Set;
import java.util.concurrent.TimeUnit;

public class MyTlsConfiguration extends BaseTlsConfiguration {

    @Override
    public KeyCertOptions getKeyStoreOptions() {
        return new KeyStoreOptions()
                .setPath("/tmp/certs/server-keystore.p12")
                .setPassword("the_key_store_secret")
                .setType("PKCS12");
    }

    @Override
    public TrustOptions getTrustStoreOptions() {
        return new KeyStoreOptions()
                .setPath("/tmp/certs/server-truststore.jks")
                .setPassword("the_trust_store_secret")
                .setType("PKCS12");
    }

    @Override
    public SSLOptions getSSLOptions() {
        SSLOptions options = new SSLOptions();
        options.setKeyCertOptions(getKeyStoreOptions());
        options.setTrustOptions(getTrustStoreOptions());
        options.setSslHandshakeTimeoutUnit(TimeUnit.SECONDS);
        options.setSslHandshakeTimeout(10);
        options.setEnabledSecureTransportProtocols(Set.of("TLSv1.3", "TLSv1.2"));
        return options;
    }

}
Copy to Clipboard Toggle word wrap

双向 TLS 客户端身份验证配置示例

package org.acme.http.security;

import io.quarkus.vertx.http.security.HttpSecurity;
import jakarta.enterprise.event.Observes;

public class MutualTlsClientAuthConfig {

    void configure(@Observes HttpSecurity httpSecurity) {
        httpSecurity.mTLS("tls-config-1", new MyTlsConfiguration());    
1

    }

}
Copy to Clipboard Toggle word wrap

1
启用并需要 mutual TLS 客户端身份验证,并将 tls-config-1 TLS 配置用于 HTTP 服务器 TLS 通信。tls-config-1 TLS 配置在 TLS registry 中注册。

也可以将证书属性映射到角色。让我们考虑在 Mapping certificate 属性到 roles 部分所解释的示例:

quarkus.tls.tls-config-1.key-store.p12.path=server-keystore.p12
quarkus.tls.tls-config-1.key-store.p12.password=the_key_store_secret
quarkus.tls.tls-config-1.trust-store.p12.path=server-truststore.p12
quarkus.tls.tls-config-1.trust-store.p12.password=the_trust_store_secret
quarkus.http.ssl.client-auth=required
quarkus.http.tls-configuration-name=tls-config-1
quarkus.http.auth.certificate-role-properties=cert-role-mappings.properties
Copy to Clipboard Toggle word wrap

带有编程集的配置类似如下:

package org.acme.http.security;

import io.quarkus.vertx.http.security.MTLS;
import io.quarkus.vertx.http.security.HttpSecurity;
import jakarta.enterprise.event.Observes;

public class MutualTlsClientAuthConfig {

    void configure(@Observes HttpSecurity httpSecurity) {
        httpSecurity.mTLS(MTLS.builder()
                .tls("tls-config-1", new MyTlsConfiguration())
                .rolesMapping("CN-value", "user", "admin")
                .build());
    }

}
Copy to Clipboard Toggle word wrap

此 API 还允许您使用证书来增强 SecurityIdentity

quarkus.http.tls-configuration-name=tls-config-1
quarkus.tls.tls-config-1.key-store.p12.path=server-keystore.p12
quarkus.tls.tls-config-1.key-store.p12.password=the_key_store_secret
quarkus.tls.tls-config-1.trust-store.p12.path=server-truststore.jks
quarkus.tls.tls-config-1.trust-store.p12.password=the_trust_store_secret
Copy to Clipboard Toggle word wrap
package org.acme.http.security;

import io.quarkus.vertx.http.security.HttpSecurity;
import io.quarkus.vertx.http.security.MTLS;
import jakarta.enterprise.event.Observes;

import java.util.Set;

public class MutualTlsClientAuthConfig {

    void configure(@Observes HttpSecurity httpSecurity) {
        httpSecurity.mTLS(MTLS.builder()
                .certificateToRolesMapper(x509Certificate -> {   
1

                    final Set<String> securityIdentityRoles;

                    // replace this logic with your own certificate to roles mapping
                    if (x509Certificate.getIssuerX500Principal() != null
                            && "CN=quarkus.io".equals(x509Certificate.getIssuerX500Principal().getName())) {
                        securityIdentityRoles = Set.of("admin");
                    } else {
                        securityIdentityRoles = Set.of();
                    }
                    return securityIdentityRoles;
                })
                .build());
    }

}
Copy to Clipboard Toggle word wrap
1
Using certificate attributes to increased SecurityIdentity 部分中,我们提到了 SecurityIdentityAugmentor 可用于将客户端证书属性值映射到 SecurityIdentity 角色。MTLS API 允许您定义这样的角色映射。
重要

如果 Quarkus 在启动失败后以 DEV 模式启动 HTTP 服务器,Quarkus 可能需要回退到 application.properties 文件中提供的配置。这是一个已知的限制。

2.3. 其他支持的身份验证机制

Quarkus 安全还通过扩展支持以下身份验证机制:

2.3.1. OpenID Connect 身份验证

OpenID Connect (OIDC)是一个身份层,可在 OAuth 2.0 协议之上工作。OIDC 允许客户端应用程序根据 OIDC 供应商执行的身份验证验证用户身份,并检索有关该用户的基本信息。

Quarkus quarkus-oidc 扩展提供了一个被动、可互操作的、启用了多租户的 OIDC 适配器,它支持 Bearer 令牌和授权代码流身份验证机制。Bearer 令牌身份验证机制从 HTTP Authorization 标头中提取令牌。

授权代码流机制将用户重定向到 OIDC 供应商,以验证用户身份。用户重定向到 Quarkus 后,机制通过交换为 ID、访问和刷新令牌授予的提供代码,从而完成身份验证过程。

您可以使用可刷新的 JSON Web 密钥(JWK)设置或远程内省它们,以验证 ID 和访问 JSON Web Token (JWT)令牌。但是,不透明(也称为二进制令牌)只能远程内省。

注意

使用 Quarkus OIDC 扩展,Bearer 令牌和授权代码流身份验证机制都使用 SmallRye JWT 身份验证来将 JWT 令牌表示为 MicroProfile JWT org.eclipse.microprofile.jwt.JsonWebToken

2.3.1.1. 用于 OIDC 身份验证的其他 Quarkus 资源

有关可用于保护 Quarkus 应用程序的 OIDC 身份验证和授权方法的更多信息,请参阅以下资源:

Expand
OIDC 主题Quarkus 信息资源

bearer 令牌身份验证机制

OIDC Bearer 令牌身份验证

授权代码流身份验证机制

OpenID Connect (OIDC)授权代码流机制

OIDC 和 SAML 身份代理

OpenID Connect (OIDC)授权代码流和 SAML Identity 代理

多个租户,可以支持 Bearer 令牌身份验证或授权代码流机制

使用 OpenID Connect (OIDC)多租户

使用常用的 OpenID Connect 供应商保护 Quarkus

配置已知的 OpenID Connect 供应商

使用 Keycloak 集中管理授权

使用 OpenID Connect (OIDC)和 Keycloak 集中管理授权

注意

要在运行时启用 Quarkus OIDC 扩展,请在构建时设置 quarkus.oidc.tenant-enabled=false。然后,使用系统属性在运行时重新启用它。

有关在多租户 OIDC 部署中管理单个租户配置的更多信息,请参阅"使用 OpenID Connect (OIDC)多租户"指南中的 禁用 租户配置部分。

2.3.1.2. OpenID Connect 客户端和过滤器

quarkus-oidc-client 扩展为从支持以下令牌授予的 OpenID Connect 和 OAuth2 供应商中获取和刷新访问令牌提供 OidcClient

  • client-credentials
  • password
  • refresh_token

quarkus-resteasy-client-oidc-filter 扩展需要 quarkus-oidc-client 扩展。它提供 JAX-RS RESTful Web Services OidcClientRequestFilter,它将 OidcClient 获取的访问令牌设置为 HTTP Authorization 标头的 Bearer 方案值。此过滤器可以注册到注入当前 Quarkus 端点中的 MicroProfile REST 客户端实施,但它与此服务端点的身份验证要求无关。例如,它可以是公共端点,也可以使用 mTLS 进行保护。

重要

在这种情况下,您不需要使用 Quarkus OpenID Connect 适配器来保护 Quarkus 端点。

quarkus-resteasy-client-oidc-token-propagation 扩展需要 quarkus-oidc 扩展。它提供 Jakarta REST TokenCredentialRequestFilter,它将 OpenID Connect Bearer 令牌或 Authorization Code Flow 访问令牌设置为 HTTP Authorization 标头的 Bearer scheme 值。此过滤器可以注册到注入当前 Quarkus 端点中的 MicroProfile REST 客户端实现,该端点必须使用 Quarkus OIDC 适配器进行保护。此过滤器可以将访问令牌传播到下游服务。

如需更多信息,请参阅 OpenID Connect 客户端和令牌传播快速启动OpenID Connect (OIDC)和 OAuth2 客户端和过滤器参考指南

2.3.2. smallrye JWT 身份验证

quarkus-smallrye-jwt 扩展提供 MicroProfile JSON Web Token (JWT) 2.1 实施,以及多个选项来验证已签名和加密的 JWT 令牌。它将它们表示为 org.eclipse.microprofile.jwt.JsonWebToken

quarkus-smallrye-jwtquarkus-oidc Bearer 令牌身份验证机制的替代选择,并使用 Privacy Enhanced Mail (PEM)密钥或可刷新的 JWK 密钥集来验证仅 JWT 令牌。quarkus-smallrye-jwt 还提供 JWT 生成 API,您可以使用它来轻松创建 经过签名内部 签名的,以及 加密的 JWT 令牌。

如需更多信息,请参阅使用 JWT RBAC 指南。

使用以下信息来选择适当的令牌身份验证机制来保护 Quarkus 应用程序。

身份验证机制用例列表

  • quarkus-oidc 需要 OpenID Connect 供应商,如 Keycloak,它可以验证 bearer 令牌或使用授权代码流验证最终用户。在这两种情况下,quarkus-oidc 需要连接到指定的 OpenID Connect 供应商。
  • 如果用户身份验证需要授权代码流,或者您需要支持多个租户,请使用 quarkus-oidcquarkus-oidc 还可以使用授权代码流和 Bearer 访问令牌来请求用户信息。
  • 如果必须验证 bearer 令牌,请使用 quarkus-oidcquarkus-smallrye-jwt
  • 如果您的 bearer 令牌采用 JSON Web 令牌(JWT)格式,您可以使用上述列表中的任何扩展。quarkus-oidcquarkus-smallrye-jwt 支持刷新 OpenID Connect 提供程序轮转密钥时设置的 JsonWebKey (JWK)。因此,如果必须避免远程令牌内省,或者提供程序不支持的远程令牌,请使用 quarkus-oidcquarkus-smallrye-jwt 来验证 JWT 令牌。
  • 要远程内省 JWT 令牌,您可以使用 quarkus-oidc 使用远程内省来验证不透明或二进制令牌。quarkus-smallrye-jwt 不支持对不透明或 JWT 令牌的远程内省,而是依赖于通常从 OpenID Connect 提供程序检索的本地可用密钥。
  • quarkus-oidcquarkus-smallrye-jwt 支持 JWT 和不透明令牌注入端点代码中。注入的 JWT 令牌提供有关用户的更多信息。所有扩展都可以将令牌注入为 Principal
  • Quarkus-smallrye-jwt 支持比 quarkus-oidc 更多的密钥格式。quarkus-oidc 只使用属于 JWK 设置的 JWK 格式的密钥,而 quarkus-smallrye-jwt 支持 PEM 密钥。
  • quarkus-smallrye-jwt 处理本地签名、内部签名和加密令牌。相反,虽然 quarkus-oidc 也可以验证这些令牌,但它将其视为不透明令牌,并通过远程内省对其进行验证。
注意

架构注意事项有助于您决定使用不透明或 JSON Web 令牌(JWT)令牌格式。不透明令牌往往比 JWT 令牌要短,但需要在提供商数据库中维护大多数令牌关联状态。不透明令牌是有效的数据库指针。

JWT 令牌比不透明令牌要长。然而,提供商通过将大多数令牌关联状态存储为令牌声明,并对其进行签名或加密来有效地将大多数令牌关联状态委派给客户端。

Expand
表 2.2. 令牌身份验证机制比较
功能需要身份验证机制
 

quarkus-oidc

quarkus-smallrye-jwt

bearer JWT 验证

本地验证或内省

本地验证

bearer 不透明令牌验证

内省

刷新 JsonWebKey 设置为验证 JWT 令牌

将令牌表示为 Principal

将 JWT 注入 MP JWT

授权代码流

多租户

用户信息支持

PEM 密钥格式支持

secretKey 支持

JSON Web 密钥(JWK)格式

内部签名和加密或加密令牌

内省

本地验证

自定义令牌验证

使用注入的 JWT 解析器

JWT 作为 Cookie 支持

2.5. 组合身份验证机制

如果不同的源提供用户凭证,您可以组合身份验证机制。例如,您可以组合内置的 Basic 和 Quarkus quarkus-oidc Bearer 令牌身份验证机制。

当第一个 SecurityIdentity 由其中一个身份验证机制生成后,身份验证过程就会马上完成。

2.5.1. 包含的身份验证

在某些情况下,可能要求所有注册的身份验证机制都创建其 SecurityIdentity。当用户通过虚拟专用网络进行身份验证时,或者当前令牌必须绑定到令牌验证才能成功通过 Mutual TLS 身份验证时,可能需要使用令牌验证。

在 Quarkus 中,此类身份验证名为 inclusive,您可以启用它,如下所示:

quarkus.http.auth.inclusive=true
Copy to Clipboard Toggle word wrap

如果身份验证包含,则由第一个身份验证机制创建的 SecurityIdentity 可以注入到应用程序代码中。例如,如果需要 Mutual TLS 身份验证和 基本身份验证机制身份验证,则 Mutual TLS 身份验证机制 将首先创建 SecurityIdentity

注意

Mutual TLS 身份验证机制在启用包含身份验证时具有最高的优先级,以确保注入的 SecurityIdentity 始终代表 双向 TLS 身份验证,并可用于获取由其他身份验证机制提供的 SecurityIdentity 身份的访问。

可以在第一个 SecurityIdentity 上作为 quarkus.security.identities 属性访问额外的 SecurityIdentity 实例,但访问这些额外的身份可能并不需要访问这些额外身份,例如,当 Mutual TLS 身份验证和 OIDC Bearer 身份验证机制都已合并并完成其身份验证时,经过身份验证的 bearer 令牌可以注入令牌凭证,以及 Mutual TLS 身份验证 创建的 SecurityIdentity。下面是一个示例:

package org.acme.security;

import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class InclusiveAuthExampleBean {

    @Inject
    SecurityIdentity mtlsIdentity;  
1


    @Inject
    AccessTokenCredential accessTokenCredential;

    private AccessTokenCredential getAccessTokenCredential() {
        if (doItHardWay()) {
            var securityIdentities = HttpSecurityUtils.getSecurityIdentities(mtlsIdentity);    
2

            if (securityIdentities != null) {
                SecurityIdentity bearerIdentity = securityIdentities.get(OidcConstants.BEARER_SCHEME);
                if (bearerIdentity != null) {
                    return bearerIdentity.getCredential(AccessTokenCredential.class);
                }
            }
        }
        return accessTokenCredential;
    }

}
Copy to Clipboard Toggle word wrap
1
这是由具有最高优先级的适用身份验证机制创建的 SecurityIdentity
2
其他执行身份验证的身份验证机制,可在 SecurityIdentity 上可用。
重要

您不能组合 Quarkus quarkus-oidc Bearer 令牌和 smallrye-jwt 身份验证机制,因为这两种机制试图验证从 HTTP Bearer 令牌身份验证方案中提取的令牌。

2.5.2. 基于路径的身份验证

以下配置示例演示了如何为给定请求路径强制执行单个可选择的身份验证机制:

quarkus.http.auth.permission.basic-or-bearer.paths=/service
quarkus.http.auth.permission.basic-or-bearer.policy=authenticated

quarkus.http.auth.permission.basic.paths=/basic-only
quarkus.http.auth.permission.basic.policy=authenticated
quarkus.http.auth.permission.basic.auth-mechanism=basic

quarkus.http.auth.permission.bearer.paths=/bearer-only
quarkus.http.auth.permission.bearer.policy=authenticated
quarkus.http.auth.permission.bearer.auth-mechanism=bearer
Copy to Clipboard Toggle word wrap

确保 auth-mechanism 属性的值与 HttpAuthenticationMechanism 支持的身份验证方案匹配,例如 基本的bearer表单

可以使用注解来选择特定于每个 Jakarta REST 端点的身份验证机制。只有在禁用 主动 身份验证时,此功能才会启用。因此,注解只能在与 REST 端点匹配后选择验证机制。以下是如何为每个 REST 端点选择身份验证机制:

quarkus.http.auth.proactive=false
Copy to Clipboard Toggle word wrap
import io.quarkus.oidc.AuthorizationCodeFlow;
import io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("hello")
public class HelloResource {

    @GET
    @BasicAuthentication 
1
 
2

    @Path("basic")
    public String basicAuthMechanism() {
        return "basic";
    }

    @GET
    @RolesAllowed("admin") 
3

    @AuthorizationCodeFlow 
4

    @Path("code-flow")
    public String codeFlowAuthMechanism() {
        return "code-flow";
    }
}
Copy to Clipboard Toggle word wrap
1
REST 端点 /hello/basic 只能通过 基本身份验证 访问。
2
此端点需要身份验证,因为当没有标准安全注释时,@BasicAuthentication 注释会被默认添加 @Authenticated 注释。
3
@AuthorizationCodeFlow 注释可以与任何其他标准安全注释(如 @RolesAllowed@PermissionsAllowed 等)结合使用。
4
REST 端点 /hello/code-flow 只能通过 OIDC 授权代码流机制 访问。
Expand
表 2.3. 支持的身份验证机制注解
身份验证机制^注解

基本身份验证机制

io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication

基于表单的验证机制

io.quarkus.vertx.http.runtime.security.annotation.FormAuthentication

双向 TLS 身份验证机制

io.quarkus.vertx.http.runtime.security.annotation.MTLSAuthentication

bearer 令牌身份验证机制

io.quarkus.oidc.BearerTokenAuthentication

OIDC 授权代码流机制

io.quarkus.oidc.AuthorizationCodeFlow

smallrye JWT 身份验证机制

io.quarkus.smallrye.jwt.runtime.auth.BearerTokenAuthentication

提示

Quarkus 会自动保护使用身份验证机制注解注解的端点。当 REST 端点和资源中没有标准安全注解时,会为您添加 io.quarkus.security.Authenticated 注解。

也可以使用 io.quarkus.vertx.http.runtime.security.annotation.HttpAuthenticationMechanism 注释来选择基于其方案的任何身份验证机制。基于注释的 analogy 到 quarkus.http.auth.permission.basic.auth-mechanism=custom 配置属性是 @HttpAuthenticationMechanism ("custom") 注释。

注意

为了与各种 Jakarta EE 规格保持一致,建议始终重复注释,而不依赖于注解继承。

默认情况下,Quarkus 只支持 每个路径的一个验证机制的基于路径的 验证。如果需要将多个身份验证机制用于基于路径的身份验证,您可以编写一个自定义 HttpAuthenticationMechanism 部分,如 Security Tips and Tricks 指南中有多个 HttpAuthenticationMechanism 部分所述。另一种选择是在 lax 模式中启用 Inclusive 身份验证,并编写自定义 HttpSecurityPolicyPermissionChecker,验证所有注册的 HTTP 身份验证机制是否创建了其机制特定的 SecurityIdentity

在 lax 模式中启用包含的身份验证

quarkus.http.auth.inclusive-mode=lax 
1

quarkus.http.auth.inclusive=true
Copy to Clipboard Toggle word wrap

1
默认情况下,包含的身份验证要求所有注册的 HTTP 身份验证机制都必须创建 SecurityIdentity。但是,在 lax 模式中,如果至少一个注册的 HttpAuthenticationMechanism 创建了 SecurityIdentity,则身份验证会成功。

假设我们有 3 个注册机制 mTLS、Basic 和 OIDC,您只需要 Basic 和 mTLS 身份验证才能成功访问 hello 方法。在这种情况下,在 lax 模式中启用包含的身份验证,可以检查生成身份的机制,如下例所示:

HTTP 身份验证机制检查示例

import io.quarkus.security.PermissionChecker;
import io.quarkus.security.PermissionsAllowed;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import java.util.Map;

@Path("/hello")
public class HelloResource {

    @PermissionsAllowed("mtls-basic")
    @GET
    public String hello() {
        return "Hello world";
    }

    @PermissionChecker("mtls-basic")
    boolean isMtlsAndBasicAuthentication(SecurityIdentity identity) {
        Map<String, SecurityIdentity> identities = HttpSecurityUtils.getSecurityIdentities(identity);
        if (identities != null) {
            return identities.containsKey("basic") && identities.containsKey("x509"); 
1

        }
        return false;
    }
}
Copy to Clipboard Toggle word wrap

1
只有在确认 mTLS基本身份验证 机制都已验证当前请求时,才允许访问端点。
2.5.2.4. 如何将其与 HTTP 安全策略合并

定义允许访问单个资源的角色的最简单方法是 @RolesAllowed 注释。但是,也可以使用 HTTP 安全策略,如下例所示:

quarkus.http.auth.policy.roles1.roles-allowed=user
quarkus.http.auth.permission.roles1.paths=/hello/code-flow
quarkus.http.auth.permission.roles1.applies-to=JAXRS 
1

quarkus.http.auth.permission.roles1.policy=roles1
quarkus.http.auth.permission.roles1.methods=GET 
2
Copy to Clipboard Toggle word wrap
1
在选择了特定于端点的身份验证机制后,延迟此策略的权限检查。
2
使 roles1 权限仅与使用 @AuthorizationCodeFlow 注释标注的端点匹配。Unannotated 端点必须避免 apply -to=JAXRS 选项导致的延迟。

2.6. 主动验证

主动验证在 Quarkus 中默认启用。这意味着,如果传入请求具有凭证,则请求将始终通过身份验证,即使目标页面不需要身份验证。如需更多信息,请参阅 Quarkus 主动身份验证 指南。

2.7. 参考

第 3 章 用户身份供应商

在 Quarkus 安全框架中,身份提供程序通过验证用户身份在身份验证和授权中扮演重要角色。IdentityProvider 创建一个 SecurityIdentity 实例,它会在用户身份验证过程中用来验证和授权对 Quarkus 应用的访问请求。

IdentityProviderHttpAuthenticationMechanism 提供的身份验证凭据转换为 SecurityIdentity 实例。

有些扩展,如 OIDC 和 SmallRye JWT 的扩展,包括特定于支持的身份验证流的内联 IdentityProvider 实现。例如,quarkus-oidc 使用自己的 IdentityProvider 将令牌转换为 SecurityIdentity 实例。

如果使用基于 Basic 或 form 的身份验证,您必须添加一个 IdentityProvider 实例,将用户名和密码转换为 SecurityIdentity 实例。

要开始使用 Quarkus 中的安全性,请考虑将 Quarkus 内置基本 HTTP 身份验证与 Jakarta Persistence 身份提供程序相结合,以启用基于角色的访问控制(RBAC)。

有关基本身份验证、其机制和相关身份提供程序的更多信息,请参阅以下资源:

第 4 章 主动验证

了解如何在 Quarkus 中管理主动身份验证,包括自定义设置和处理异常。获取各种应用程序场景的实用见解和策略。

主动验证在 Quarkus 中默认启用。它确保所有传入的请求都通过身份验证,即使目标页面不需要身份验证。因此,带有无效凭证的请求将被拒绝,即使目标页面是公共的。没有凭证的请求不会被拒绝,因为允许匿名请求。

只有在目标页面需要时才需要身份验证时,可以关闭此默认行为。要关闭主动身份验证,以便仅在目标页面需要时才进行身份验证,请按如下所示修改 application.properties 配置文件:

quarkus.http.auth.proactive=false
Copy to Clipboard Toggle word wrap

如果您关闭主动身份验证,身份验证过程仅在请求身份时运行。可以请求一个身份,因为需要需要用户进行身份验证的安全规则,或者因为需要对当前身份进行编程访问。

如果没有使用主动身份验证,访问 SecurityIdentity 是一个阻止操作。这是因为身份验证可能已经发生,访问 SecurityIdentity 可能需要调用外部系统,如数据库,这可能会阻止操作。对于阻止应用程序,这并不是一个问题。但是,如果您在被动应用程序中禁用了身份验证,则会失败,因为您无法阻止 I/O 线程的操作。要临时解决这个问题,您需要 @Inject 一个 io.quarkus.security.identity.CurrentIdentityAssociation 实例,并调用 Uni<SecurityIdentity> getDeferredIdentity (); 方法。然后,您可以订阅生成的 Uni,以便在身份验证完成后获得通知,且身份可用。

注意

您仍然可以从使用 @RolesAllowed 、@Authenticated、@Authenticated ,或具有相应配置授权检查的端点同时访问 Quarkus REST 中的 SecurityIdentity getIdentity () (以前为 RESTEasy Reactive)。如果路由响应是同步,则也对 Reactive 路由 也有效。

禁用主动身份验证后,如果未同步返回值的安全方法,则 CDI Bean 上使用 的标准安全注解 在 I/O 线程上无法正常工作。由于需要这些方法访问 SecurityIdentity,就会产生这个限制。

以下示例定义了 HelloResourceHelloService。任何对 /hello 的 GET 请求都在 I/O 线程上运行,并抛出 BlockingOperationNotAllowedException 异常。

修复示例的方法不止一种:

  • 通过注解带有 @Blockinghello 端点来切换到 worker 线程。
  • 使用被动或异步数据类型更改 sayHello 方法返回类型。
  • @RolesAllowed 注释移到端点。这可能是安全的方法之一,因为从端点方法访问 SecurityIdentity 并不是阻塞操作。
import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.smallrye.mutiny.Uni;

@Path("/hello")
@PermitAll
public class HelloResource {

    @Inject
    HelloService helloService;

    @GET
    public Uni<String> hello() {
        return Uni.createFrom().item(helloService.sayHello());
    }

}
Copy to Clipboard Toggle word wrap
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class HelloService {

    @RolesAllowed("admin")
    public String sayHello() {
        return "Hello";
    }

}
Copy to Clipboard Toggle word wrap

4.1. 激活 CDI 请求上下文

您可能需要在身份验证和授权期间注入 @RequestScoped Bean。这是在 SecurityIdentity 增强过程中访问数据库的一个很好的例子,这在" 安全提示和 Tricks"指南的"安全身份自定义 "部分中描述。如果身份验证或授权失败并显示 jakarta.enterprise.context.ContextNotActiveException,则禁用主动身份验证通常是最佳解决方案。用户也可以使用 @ActivateRequestContext 注释激活 CDI 请求上下文。但是,一些 CDI Bean 可能未就绪。

此解决方案的一个例外是应用端点 使用配置 进行授权保护时的情况。有关更多信息,请参阅"Authorization of Web 端点"指南中的 Inject RequestScoped Bean into HttpSecurityPolicy 部分以了解更多信息。

4.2. 自定义身份验证异常响应

您可以使用 Jakarta REST ExceptionMapper 来捕获 Quarkus 安全身份验证异常,如 io.quarkus.security.AuthenticationFailedException。例如:

package io.quarkus.it.keycloak;

import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

import io.quarkus.security.AuthenticationFailedException;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFailedExceptionMapper implements ExceptionMapper<AuthenticationFailedException> {

    @Context
    UriInfo uriInfo;

    @Override
    public Response toResponse(AuthenticationFailedException exception) {
        return Response.status(401).header("WWW-Authenticate", "Basic realm=\"Quarkus\"").build();
    }
}
Copy to Clipboard Toggle word wrap
Important

有些 HTTP 身份验证机制必须处理身份验证异常本身,才能创建正确的身份验证问题。例如: io.quarkus.oidc.runtime.CodeAuthenticationMechanism,它管理 OpenID Connect (OIDC)授权代码流身份验证,必须构建正确的重定向 URL 并设置状态 Cookie。因此,避免使用自定义异常映射器自定义由此类机制引发的身份验证异常。相反,一种更安全的方法是确保启用主动身份验证并使用 Vert.x HTTP 路由失败处理程序。这是因为事件发送到带有正确响应状态和标头的处理程序。然后,您必须只自定义响应,例如:

package io.quarkus.it.keycloak;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

import io.quarkus.security.AuthenticationFailedException;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class AuthenticationFailedExceptionHandler {

    public void init(@Observes Router router) {
        router.route().failureHandler(new Handler<RoutingContext>() {
            @Override
            public void handle(RoutingContext event) {
                if (event.failure() instanceof AuthenticationFailedException) {
                    event.response().end("CUSTOMIZED_RESPONSE");
                } else {
                    event.next();
                }
            }
        });
    }
}
Copy to Clipboard Toggle word wrap

4.3. 参考

法律通告

Copyright © 2025 Red Hat, Inc.
The text of and illustrations in this document are licensed by Red Hat under a Creative Commons Attribution–Share Alike 3.0 Unported license ("CC-BY-SA"). An explanation of CC-BY-SA is available at http://creativecommons.org/licenses/by-sa/3.0/. In accordance with CC-BY-SA, if you distribute this document or an adaptation of it, you must provide the URL for the original version.
Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law.
Red Hat, Red Hat Enterprise Linux, the Shadowman logo, the Red Hat logo, JBoss, OpenShift, Fedora, the Infinity logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries.
Linux® is the registered trademark of Linus Torvalds in the United States and other countries.
Java® is a registered trademark of Oracle and/or its affiliates.
XFS® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries.
MySQL® is a registered trademark of MySQL AB in the United States, the European Union and other countries.
Node.js® is an official trademark of Joyent. Red Hat is not formally related to or endorsed by the official Joyent Node.js open source or commercial project.
The OpenStack® Word Mark and OpenStack logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community.
All other trademarks are the property of their respective owners.
返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat