OpenID Connect authorization code flow mechanism for protecting web applications
为了保护你的 Web 应用程序,你可以使用 Quarkus OIDC 扩展提供的行业标准 OpenID Connect (OIDC) 授权码流程机制。
To protect your web applications, you can use the industry-standard OpenID Connect (OIDC) Authorization Code Flow mechanism provided by the Quarkus OIDC extension.
Overview of the OIDC authorization code flow mechanism
Quarkus OpenID Connect (OIDC) 可以使用 OIDC 授权服务器支持的 OIDC 授权码流程机制来保护应用程序 HTTP 终端,例如 Keycloak。
The Quarkus OpenID Connect (OIDC) extension can protect application HTTP endpoints by using the OIDC Authorization Code Flow mechanism supported by OIDC-compliant authorization servers, such as Keycloak.
授权码流程机制通过将用户重定向到 OIDC 提供程序(比如 Keycloak)来验证你的 Web 应用程序的用户身份。在验证之后,OIDC 提供程序会通过授权码将用户重定向回应用程序,以确认已经成功验证身份。然后,应用程序会将此代码与 OIDC 提供程序交换身份令牌(代表已验证的用户)、访问令牌和刷新令牌以授权用户访问应用程序。
The Authorization Code Flow mechanism authenticates users of your web application by redirecting them to an OIDC provider, such as Keycloak, to log in. After authentication, the OIDC provider redirects the user back to the application with an authorization code that confirms that authentication was successful. Then, the application exchanges this code with the OIDC provider for an ID token (which represents the authenticated user), an access token, and a refresh token to authorize the user’s access to the application.
下图概述了 Quarkus 中的授权码流程机制。
The following diagram outlines the Authorization Code Flow mechanism in Quarkus.
-
The Quarkus user requests access to a Quarkus
web-app
application. -
The Quarkus web-app redirects the user to the authorization endpoint, that is, the OIDC provider for authentication.
-
The OIDC provider redirects the user to a login and authentication prompt.
-
At the prompt, the user enters their user credentials.
-
The OIDC provider authenticates the user credentials entered and, if successful, issues an authorization code and redirects the user back to the Quarkus web-app with the code included as a query parameter.
-
The Quarkus web-app exchanges this authorization code with the OIDC provider for ID, access, and refresh tokens.
授权码流程完成,Quarkus Web 应用程序会使用发出的令牌访问有关用户的信息,并将相关的基于角色的授权授予该用户。以下令牌会发出:
The authorization code flow is completed and the Quarkus web-app uses the tokens issued to access information about the user and grants the relevant role-based authorization to that user. The following tokens are issued:
-
ID token: The Quarkus
web-app
application uses the user information in the ID token to enable the authenticated user to log in securely and to provide role-based access to the web application. -
Access token: The Quarkus web-app might use the access token to access the UserInfo API to get additional information about the authenticated user or to propagate it to another endpoint.
-
Refresh token: (Optional) If the ID and access tokens expire, the Quarkus web-app can use the refresh token to get new ID and access tokens.
请参阅 OIDC configuration properties参考指南。
See also the OIDC configuration properties reference guide.
如需了解如何使用 OIDC 授权代码流机制来保护网络应用程序,请参阅 Protect a web application by using OIDC authorization code flow。
To learn about how you can protect web applications by using the OIDC Authorization Code Flow mechanism, see Protect a web application by using OIDC authorization code flow.
如果您想要使用 OIDC Bearer 令牌身份验证来保护服务应用程序,请参阅 OIDC Bearer token authentication。
If you want to protect service applications by using OIDC Bearer token authentication, see OIDC Bearer token authentication.
如需了解有关如何支持多租户的信息,请参阅 Using OpenID Connect Multi-Tenancy。
For information about how to support multiple tenants, see Using OpenID Connect Multi-Tenancy.
Using the authorization code flow mechanism
Configuring access to the OIDC provider endpoint
OIDC web-app`应用程序需要 OIDC 提供商的授权、令牌、`JsonWebKey
(JWK) 设置和可能有的 UserInfo
、内省和终止会话 (RP 发起的注销) 端点的 URL。
The OIDC web-app
application requires URLs of the OIDC provider’s authorization, token, JsonWebKey
(JWK) set, and possibly the UserInfo
, introspection and end-session (RP-initiated logout) endpoints.
惯例上,通过向已配置的 `quarkus.oidc.auth-server-url`添加 `/.well-known/openid-configuration`路径来发现它们。
By convention, they are discovered by adding a /.well-known/openid-configuration
path to the configured quarkus.oidc.auth-server-url
.
或者,如果发现端点不可用,或者您更希望减少发现端点往返次数,您可以禁用端点发现并配置相对路径值。例如:
Alternatively, if the discovery endpoint is not available, or you prefer to reduce the discovery endpoint round-trip, you can disable endpoint discovery and configure relative path values. For example:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.discovery-enabled=false
# Authorization endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/auth
quarkus.oidc.authorization-path=/protocol/openid-connect/auth
# Token endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/token
quarkus.oidc.token-path=/protocol/openid-connect/token
# JWK set endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/certs
quarkus.oidc.jwks-path=/protocol/openid-connect/certs
# UserInfo endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/userinfo
quarkus.oidc.user-info-path=/protocol/openid-connect/userinfo
# Token Introspection endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/token/introspect
quarkus.oidc.introspection-path=/protocol/openid-connect/token/introspect
# End-session endpoint: http://localhost:8180/realms/quarkus/protocol/openid-connect/logout
quarkus.oidc.end-session-path=/protocol/openid-connect/logout
一些 OIDC 提供程序支持元数据发现,但不会返回完成授权代码流或支持应用程序功能(例如用户注销)所需的所有端点 URL 值。为解决此限制,您可以按照以下示例中所述的方式在本地配置缺失的端点 URL 值:
Some OIDC providers support metadata discovery but do not return all the endpoint URL values required for the authorization code flow to complete or to support application functions, for example, user logout. To work around this limitation, you can configure the missing endpoint URL values locally, as outlined in the following example:
# Metadata is auto-discovered but it does not return an end-session endpoint URL
quarkus.oidc.auth-server-url=http://localhost:8180/oidcprovider/account
# Configure the end-session URL locally.
# It can be an absolute or relative (to 'quarkus.oidc.auth-server-url') address
quarkus.oidc.end-session-path=logout
您可以使用相同的配置来覆盖已发现的端点 URL,如果该 URL 不适用于本地 Quarkus 端点且需要更特定的值时。例如,支持全局和特定于应用程序的终止会话端点的提供程序返回全局终止会话的 URL,例如 http://localhost:8180/oidcprovider/account/global-logout
。此 URL 将注销用户当前已登录的所有应用程序。但是,如果要求当前应用程序仅注销用户特定的应用程序,您可以通过设置 `quarkus.oidc.end-session-path=logout`参数来覆盖全局终止会话 URL。
You can use this same configuration to override a discovered endpoint URL if that URL does not work for the local Quarkus endpoint and a more specific value is required.
For example, a provider that supports both global and application-specific end-session endpoints returns a global end-session URL such as http://localhost:8180/oidcprovider/account/global-logout
.
This URL will log the user out of all the applications into which the user is currently logged in.
However, if the requirement is for the current application to log the user out of a specific application only, you can override the global end-session URL, by setting the quarkus.oidc.end-session-path=logout
parameter.
OIDC provider client authentication
OIDC 提供程序通常要求应用程序在与 OIDC 端点交互时进行身份识别和身份验证。Quarkus OIDC(尤其是 `quarkus.oidc.runtime.OidcProviderClient`类)在必须将授权代码交换为 ID、访问和刷新令牌时或者必须刷新或内省 ID 和访问令牌时对 OIDC 提供程序进行身份验证。
OIDC providers typically require applications to be identified and authenticated when they interact with the OIDC endpoints.
Quarkus OIDC, specifically the quarkus.oidc.runtime.OidcProviderClient
class, authenticates to the OIDC provider when the authorization code must be exchanged for the ID, access, and refresh tokens, or when the ID and access tokens must be refreshed or introspected.
通常,在给定应用程序向 OIDC 提供商注册时,会为此定义客户端 ID 和客户端机密。支持所有 OIDC client authentication选项。例如:
Typically, client id and client secrets are defined for a given application when it enlists to the OIDC provider. All OIDC client authentication options are supported. For example:
client_secret_basic
:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=mysecret
或者:
Or:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
以下示例演示从 credentials provider检索机密:
The following example shows the secret retrieved from a credentials provider:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
# This is a key which will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc.credentials.client-secret.provider.key=mysecret-key
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc.credentials.client-secret.provider.name=oidc-credentials-provider
client_secret_post
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
quarkus.oidc.credentials.client-secret.method=post
client_secret_jwt
, where the signature algorithm is HS256:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
client_secret_jwt
, where the secret is retrieved from a credentials provider:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
# This is a key which will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc.credentials.jwt.secret-provider.key=mysecret-key
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc.credentials.jwt.secret-provider.name=oidc-credentials-provider
使用应用程序中内联的 PEM 密钥的 private_key_jwt`示例,其中签名算法是 `RS256
:
Example of private_key_jwt
with the PEM key inlined in application.properties, and where the signature algorithm is RS256
:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key=Base64-encoded private key representation
使用 PEM 密钥文件的 `private_key_jwt`示例,其中签名算法是 RS256:
Example of private_key_jwt
with the PEM key file, and where the signature algorithm is RS256:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-file=privateKey.pem
private_key_jwt
with the keystore file, where the signature algorithm is RS256:quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-store-file=keystore.jks
quarkus.oidc.credentials.jwt.key-store-password=mypassword
quarkus.oidc.credentials.jwt.key-password=mykeypassword
# Private key alias inside the keystore
quarkus.oidc.credentials.jwt.key-id=mykeyAlias
使用 `client_secret_jwt`或 `private_key_jwt`身份验证方法可以确保不会将客户端机密发送到 OIDC 提供商,因而避免了机密被“中间人”攻击拦截的风险。
Using client_secret_jwt
or private_key_jwt
authentication methods ensures that a client secret does not get sent to the OIDC provider, therefore avoiding the risk of a secret being intercepted by a 'man-in-the-middle' attack.
Additional JWT authentication options
如果使用 client_secret_jwt
、`private_key_jwt`或 Apple `post_jwt`认证方法,那么您可以自定义 JWT 签名算法、密钥标识符、受众、主体和发布者。例如:
If client_secret_jwt
, private_key_jwt
, or an Apple post_jwt
authentication methods are used, then you can customize the JWT signature algorithm, key identifier, audience, subject and issuer.
For example:
# private_key_jwt client authentication
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus/
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.jwt.key-file=privateKey.pem
# This is a token key identifier 'kid' header - set it if your OIDC provider requires it:
# Note if the key is represented in a JSON Web Key (JWK) format with a `kid` property, then
# using 'quarkus.oidc.credentials.jwt.token-key-id' is not necessary.
quarkus.oidc.credentials.jwt.token-key-id=mykey
# Use RS512 signature algorithm instead of the default RS256
quarkus.oidc.credentials.jwt.signature-algorithm=RS512
# The token endpoint URL is the default audience value, use the base address URL instead:
quarkus.oidc.credentials.jwt.audience=${quarkus.oidc-client.auth-server-url}
# custom subject instead of the client id:
quarkus.oidc.credentials.jwt.subject=custom-subject
# custom issuer instead of the client id:
quarkus.oidc.credentials.jwt.issuer=custom-issuer
Apple POST JWT
Apple OIDC 提供商使用 `client_secret_post`方法,其中秘密是一个使用 `private_key_jwt`认证方法生成的 JWT,但包含 Apple 账户特定的发布者和主体声明。
The Apple OIDC provider uses a client_secret_post
method whereby a secret is a JWT produced with a private_key_jwt
authentication method, but with the Apple account-specific issuer and subject claims.
在 Quarkus Security 中,`quarkus-oidc`支持非标准 `client_secret_post_jwt`认证方法,您可以如下配置:
In Quarkus Security, quarkus-oidc
supports a non-standard client_secret_post_jwt
authentication method, which you can configure as follows:
# Apple provider configuration sets a 'client_secret_post_jwt' authentication method
quarkus.oidc.provider=apple
quarkus.oidc.client-id=${apple.client-id}
quarkus.oidc.credentials.jwt.key-file=ecPrivateKey.pem
quarkus.oidc.credentials.jwt.token-key-id=${apple.key-id}
# Apple provider configuration sets ES256 signature algorithm
quarkus.oidc.credentials.jwt.subject=${apple.subject}
quarkus.oidc.credentials.jwt.issuer=${apple.issuer}
mutual TLS (mTLS)
一些 OIDC 提供商可能要求客户端通过双向 TLS 认证流程认证。
Some OIDC providers might require that a client is authenticated as part of the mutual TLS authentication process.
以下示例演示了如何配置 quarkus-oidc
,支持 mTLS
:
The following example shows how you can configure quarkus-oidc
to support mTLS
:
quarkus.oidc.tls.verification=certificate-validation
# Keystore configuration
quarkus.oidc.tls.key-store-file=client-keystore.jks
quarkus.oidc.tls.key-store-password=${key-store-password}
# Add more keystore properties if needed:
#quarkus.oidc.tls.key-store-alias=keyAlias
#quarkus.oidc.tls.key-store-alias-password=keyAliasPassword
# Truststore configuration
quarkus.oidc.tls.trust-store-file=client-truststore.jks
quarkus.oidc.tls.trust-store-password=${trust-store-password}
# Add more truststore properties if needed:
#quarkus.oidc.tls.trust-store-alias=certAlias
POST query
一些提供商(例如 Strava OAuth2 provider)要求将客户端凭据作为 HTTP POST 查询参数发布:
Some providers, such as the Strava OAuth2 provider, require client credentials be posted as HTTP POST query parameters:
quarkus.oidc.provider=strava
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.client-secret.value=mysecret
quarkus.oidc.credentials.client-secret.method=query
Introspection endpoint authentication
一些 OIDC 提供商要求使用基本认证且凭据不同于 `client_id`和 `client_secret`的机制对认证终结点进行认证。如果您之前已配置安全认证以支持 `client_secret_basic`或 `client_secret_post`客户端认证方法(如 OIDC provider client authentication部分中所述),则可能需要如下应用额外配置。
Some OIDC providers require authentication to its introspection endpoint by using Basic authentication and with credentials that are different from the client_id
and client_secret
.
If you have previously configured security authentication to support either the client_secret_basic
or client_secret_post
client authentication methods as described in the oidc-provider-client-authentication section, you might need to apply the additional configuration as follows.
如果必须检查令牌并且需要检查终结点特定的认证机制,则可以如下配置 quarkus-oidc
:
If the tokens have to be introspected and the introspection endpoint-specific authentication mechanism is required, you can configure quarkus-oidc
as follows:
quarkus.oidc.introspection-credentials.name=introspection-user-name
quarkus.oidc.introspection-credentials.secret=introspection-user-secret
OIDC request filters
您可以通过注册一个或多个 `OidcRequestFilter`实现来过滤 Quarkus 向 OIDC 提供商发出的 OIDC 请求,这些实现可以更新或添加新的请求头,还可以记录请求。
You can filter OIDC requests made by Quarkus to the OIDC provider by registering one or more OidcRequestFilter
implementations, which can update or add new request headers and can also log requests.
例如:
For example:
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcRequestContextProperties;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;
@ApplicationScoped
@Unremovable
public class OidcTokenRequestCustomizer implements OidcRequestFilter {
@Override
public void filter(HttpRequest<Buffer> request, Buffer buffer, OidcRequestContextProperties contextProps) {
OidcConfigurationMetadata metadata = contextProps.get(OidcConfigurationMetadata.class.getName()); 1
// Metadata URI is absolute, request URI value is relative
if (metadata.getTokenUri().endsWith(request.uri())) { 2
request.putHeader("TokenGrantDigest", calculateDigest(buffer.toString()));
}
}
private String calculateDigest(String bodyString) {
// Apply the required digest algorithm to the body string
}
}
1 | Get OidcConfigurationMetadata , which contains all supported OIDC endpoint addresses. |
2 | Use OidcConfigurationMetadata to filter requests to the OIDC token endpoint only. |
或者,您可以使用 `OidcRequestFilter.Endpoint`枚举仅将此过滤器应用于令牌终结点请求:
Alternatively, you can use OidcRequestFilter.Endpoint
enum to apply this filter to the token endpoint requests only:
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.common.OidcEndpoint;
import io.quarkus.oidc.common.OidcEndpoint.Type;
import io.quarkus.oidc.common.OidcRequestContextProperties;
import io.quarkus.oidc.common.OidcRequestFilter;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;
@ApplicationScoped
@Unremovable
@OidcEndpoint(value = Type.DISCOVERY) 1
public class OidcDiscoveryRequestCustomizer implements OidcRequestFilter {
@Override
public void filter(HttpRequest<Buffer> request, Buffer buffer, OidcRequestContextProperties contextProps) {
request.putHeader("Discovery", "OK");
}
}
1 | Restrict this filter to requests targeting the OIDC discovery endpoint only. |
Redirecting to and from the OIDC provider
当将用户重定向到 OIDC 提供商进行认证时,重定向 URL 包含 `redirect_uri`查询参数,该参数向提供商指示在认证完成后用户应重定向到何处。在我们这里,它是 Quarkus 应用程序。
When a user is redirected to the OIDC provider to authenticate, the redirect URL includes a redirect_uri
query parameter, which indicates to the provider where the user has to be redirected to when the authentication is complete.
In our case, this is the Quarkus application.
Quarkus 默认将该参数设置为当前应用程序请求 URL。例如,如果用户尝试访问 http://localhost:8080/service/1`处的 Quarkus 服务终结点,那么 `redirect_uri`参数将设置为 `http://localhost:8080/service/1
。类似地,如果请求 URL 为 http://localhost:8080/service/2
,那么 redirect_uri`参数将设置为 `http://localhost:8080/service/2
。
Quarkus sets this parameter to the current application request URL by default.
For example, if a user is trying to access a Quarkus service endpoint at http://localhost:8080/service/1
, then the redirect_uri
parameter is set to http://localhost:8080/service/1
.
Similarly, if the request URL is http://localhost:8080/service/2
, then the redirect_uri
parameter is set to http://localhost:8080/service/2
.
一些 OIDC 提供商要求 redirect_uri`对于给定的应用程序具有相同的值(例如,对于所有重定向 URL,`http://localhost:8080/service/callback
)。在这种情况下,必须设置 quarkus.oidc.authentication.redirect-path`属性。例如,`quarkus.oidc.authentication.redirect-path=/service/callback
,Quarkus 将 redirect_uri`参数设置为绝对 URL(例如 `http://localhost:8080/service/callback
),无论当前请求 URL 如何,该绝对 URL 都相同。
Some OIDC providers require the redirect_uri
to have the same value for a given application, for example, http://localhost:8080/service/callback
, for all the redirect URLs.
In such cases, a quarkus.oidc.authentication.redirect-path
property has to be set.
For example, quarkus.oidc.authentication.redirect-path=/service/callback
, and Quarkus will set the redirect_uri
parameter to an absolute URL such as http://localhost:8080/service/callback
, which will be the same regardless of the current request URL.
如果设置了 quarkus.oidc.authentication.redirect-path
,但您需要在将用户重定向回唯一的回调 URL(例如 http://localhost:8080/service/callback
)后恢复原始请求 URL,请将 quarkus.oidc.authentication.restore-path-after-redirect`属性设置为 `true
。此操作将还原请求 URL,如 http://localhost:8080/service/1
。
If quarkus.oidc.authentication.redirect-path
is set, but you need the original request URL to be restored after the user is redirected back to a unique callback URL, for example, http://localhost:8080/service/callback
, set quarkus.oidc.authentication.restore-path-after-redirect
property to true
.
This will restore the request URL such as http://localhost:8080/service/1
.
Customizing authentication requests
默认情况下,仅将 response_type
(设置为 code
)、scope
(设置为 openid
)、client_id
、`redirect_uri`和 `state`属性作为 HTTP 查询参数传递给 OIDC 提供商的授权终结点,即当用户被重定向到该终结点进行认证时。
By default, only the response_type
(set to code
), scope
(set to openid
), client_id
, redirect_uri
, and state
properties are passed as HTTP query parameters to the OIDC provider’s authorization endpoint when the user is redirected to it to authenticate.
您可以通过 `quarkus.oidc.authentication.extra-params`为其添加更多属性。例如,一些 OIDC 提供商可能选择将授权码作为重定向 URI 片段的一部分返回,这将中断认证流程。以下示例演示如何解决此问题:
You can add more properties to it with quarkus.oidc.authentication.extra-params
.
For example, some OIDC providers might choose to return the authorization code as part of the redirect URI’s fragment, which would break the authentication process.
The following example shows how you can work around this issue:
quarkus.oidc.authentication.extra-params.response_mode=query
请参阅OIDC redirect filters一节,该节解释了如何使用自定义`OidcRedirectFilter`来自定义OIDC重定向,包括对OIDC授权端点的重定向。
See also the OIDC redirect filters section explaining how a custom OidcRedirectFilter
can be used to customize OIDC redirects, including those to the OIDC authorization endpoint.
Customizing the authentication error response
当用户被重定向到OIDC授权端点以进行身份验证,并在必要时授权Quarkus应用程序时,此重定向请求可能失败,例如,当重定向URI中包含无效范围时。在这种情况下,提供方会使用`error`和`error_description`参数而不是预期的`code`参数将用户重定向回Quarkus。
When the user is redirected to the OIDC authorization endpoint to authenticate and, if necessary, authorize the Quarkus application, this redirect request might fail, for example, when an invalid scope is included in the redirect URI.
In such cases, the provider redirects the user back to Quarkus with error
and error_description
parameters instead of the expected code
parameter.
例如,当无效范围或其他无效参数包含在重定向到提供方时,可能会发生这种情况。
For example, this can happen when an invalid scope or other invalid parameters are included in the redirect to the provider.
在这种情况下,HTTP`401`错误默认返回。但是,您可以请求调用自定义公共错误端点以返回更友好的HTML错误页面。要执行此操作,请设置`quarkus.oidc.authentication.error-path`属性,如下例所示:
In such cases, an HTTP 401
error is returned by default.
However, you can request that a custom public error endpoint be called to return a more user-friendly HTML error page.
To do this, set the quarkus.oidc.authentication.error-path
property, as shown in the following example:
quarkus.oidc.authentication.error-path=/error
确保该属性以正斜杠(/)字符开头,并且路径相对于当前端点的基本URI。例如,如果将其设置为'/error',并且当前请求URI是`https://localhost:8080/callback?error=invalid_scope`,则会执行最终重定向到`https://localhost:8080/error?error=invalid_scope`。
Ensure that the property starts with a forward slash (/) character and the path is relative to the base URI of the current endpoint.
For example, if it is set to '/error' and the current request URI is https://localhost:8080/callback?error=invalid_scope
, then a final redirect is made to https://localhost:8080/error?error=invalid_scope
.
为了防止用户被重定向到此页面进行重新身份验证,请确保此错误端点是一个公共资源。
To prevent the user from being redirected to this page to be re-authenticated, ensure that this error endpoint is a public resource.
OIDC redirect filters
您可以注册一个或多个`io.quarkus.oidc.OidcRedirectFilter`实现,以过滤对OIDC授权和注销端点的OIDC重定向,以及对自定义错误和会话过期页面的本地重定向。自定义`OidcRedirectFilter`可以添加其他查询参数、响应头和设置新Cookie。
You can register one or more io.quarkus.oidc.OidcRedirectFilter
implementations to filter OIDC redirects to OIDC authorization and logout endpoints but also local redirects to custom error and session expired pages. Custom OidcRedirectFilter
can add additional query parameters, response headers and set new cookies.
例如,以下简单的自定义`OidcRedirectFilter`为Quarkus OIDC可以执行的所有重定向请求添加了一个其他查询参数和一个自定义响应头:
For example, the following simple custom OidcRedirectFilter
adds an additional query parameter and a custom response header for all redirect requests that can be done by Quarkus OIDC:
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.OidcRedirectFilter;
@ApplicationScoped
@Unremovable
public class GlobalOidcRedirectFilter implements OidcRedirectFilter {
@Override
public void filter(OidcRedirectContext context) {
if (context.redirectUri().contains("/session-expired-page")) {
context.additionalQueryParams().add("redirect-filtered", "true,"); 1
context.routingContext().response().putHeader("Redirect-Filtered", "true"); 2
}
}
}
1 | Add an additional query parameter. Note the queury names and values are URL-encoded by Quarkus OIDC, a redirect-filtered=true%20C query parameter is added to the redirect URI in this case. |
2 | Add a custom HTTP response header. |
请参阅Customizing authentication requests一节,了解如何为OIDC授权点配置其他查询参数。
See also the Customizing authentication requests section how to configure additional query parameters for OIDC authorization point.
本地错误和会话过期页面的自定义`OidcRedirectFilter`还可以创建安全的Cookie来帮助生成此类页面。
Custom OidcRedirectFilter
for local error and session expired pages can also create secure cookies to help with generating such pages.
例如,假设您需要将会话已过期的当前用户重定向到一个位于`http://localhost:8080/session-expired-page`的自定义会话过期页面。以下自定义`OidcRedirectFilter`使用OIDC租户客户端密钥在自定义`session_expired`Cookie中加密用户名:
For example, let’s assume you need to redirect the current user whose session has expired to a custom session expired page available at http://localhost:8080/session-expired-page
. The following custom OidcRedirectFilter
encrypts the user name in a custom session_expired
cookie using an OIDC tenant client secret:
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.jwt.Claims;
import io.quarkus.arc.Unremovable;
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcRedirectFilter;
import io.quarkus.oidc.Redirect;
import io.quarkus.oidc.TenantFeature;
import io.quarkus.oidc.runtime.OidcUtils;
import io.smallrye.jwt.build.Jwt;
@ApplicationScoped
@Unremovable
@TenantFeature("tenant-refresh")
@Redirect(Location.SESSION_EXPIRED_PAGE) 1
public class SessionExpiredOidcRedirectFilter implements OidcRedirectFilter {
@Override
public void filter(OidcRedirectContext context) {
if (context.redirectUri().contains("/session-expired-page")) {
AuthorizationCodeTokens tokens = context.routingContext().get(AuthorizationCodeTokens.class.getName()); 2
String userName = OidcUtils.decodeJwtContent(tokens.getIdToken()).getString(Claims.preferred_username.name()); 3
String jwe = Jwt.preferredUserName(userName).jwe()
.encryptWithSecret(context.oidcTenantConfig().credentials.secret.get()); 4
OidcUtils.createCookie(context.routingContext(), context.oidcTenantConfig(), "session_expired",
jwe + "|" + context.oidcTenantConfig().tenantId.get(), 10); 5
}
}
}
1 | Make sure this redirect filter is only called during a redirect to the session expired page. |
2 | Access AuthorizationCodeTokens tokens associated with the now expired session as a RoutingContext attribute. |
3 | Decode ID token claims and get a user name. |
4 | Save the user name in a JWT token encrypted with the current OIDC tenant’s client secret. |
5 | Create a custom session_expired cookie valid for 5 seconds which joins the encrypted token and a tenant id using a "|" separator. Recording a tenant id in a custom cookie can help to generate correct session expired pages in a multi-tenant OIDC setup. |
接下来,生成会话过期页面的公共JAX-RS资源可以使用此Cookie来创建针对此用户和相应的OIDC租户定制的页面,例如:
Next, a public JAX-RS resource which generates session expired pages can use this cookie to create a page tailored for this user and the corresponding OIDC tenant, for example:
package io.quarkus.it.keycloak;
import jakarta.inject.Inject;
import jakarta.ws.rs.CookieParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.runtime.TenantConfigBean;
import io.smallrye.jwt.auth.principal.DefaultJWTParser;
import io.vertx.ext.web.RoutingContext;
@Path("/session-expired-page")
public class SessionExpiredResource {
@Inject
TenantConfigBean tenantConfig; 1
@GET
public String sessionExpired(@CookieParam("session_expired") String sessionExpired) throws Exception {
// Cookie format: jwt|<tenant id>
String[] pair = sessionExpired.split("\\|"); 2
OidcTenantConfig oidcConfig = tenantConfig.getStaticTenantsConfig().get(pair[1]).getOidcTenantConfig(); 3
JsonWebToken jwt = new DefaultJWTParser().decrypt(pair[0], oidcConfig.credentials.secret.get()); 4
OidcUtils.removeCookie(context, oidcConfig, "session_expired"); 5
return jwt.getClaim(Claims.preferred_username) + ", your session has expired. "
+ "Please login again at http://localhost:8081/" + oidcConfig.tenantId.get(); 6
}
}
1 | Inject TenantConfigBean which can be used to access all the current OIDC tenant configurations. |
2 | Split the custom cookie value into 2 parts, first part is the encrypted token, last part is the tenant id. |
3 | Get the OIDC tenant configuration. |
4 | Decrypt the cookie value using the OIDC tenant’s client secret. |
5 | Remove the custom cookie. |
6 | Use the username in the decrypted token and the tenant id to generate the service expired page response. |
Accessing authorization data
你可以通过不同的方式访问授权信息。
You can access information about authorization in different ways.
Accessing ID and access tokens
OIDC 代码验证机制在授权代码流期间获取三个令牌: ID token、访问令牌和刷新令牌。
The OIDC code authentication mechanism acquires three tokens during the authorization code flow: ID token, access token, and refresh token.
ID 令牌永远都是 JWT 令牌,并通过 JWT 声明表示用户验证。你可以使用它来获取颁发 OIDC 端点、用户名和其他称为 claims 的信息。你可以通过注入 JsonWebToken
和 IdToken
限定符来访问 ID 令牌声明:
The ID token is always a JWT token and represents a user authentication with the JWT claims.
You can use this to get the issuing OIDC endpoint, the username, and other information called claims.
You can access ID token claims by injecting JsonWebToken
with an IdToken
qualifier:
import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.IdToken;
import io.quarkus.security.Authenticated;
@Path("/web-app")
@Authenticated
public class ProtectedResource {
@Inject
@IdToken
JsonWebToken idToken;
@GET
public String getUserName() {
return idToken.getName();
}
}
OIDC web-app
应用程序通常使用访问令牌代表当前登录用户访问其他端点。你可以如下访问原始访问令牌:
The OIDC web-app
application usually uses the access token to access other endpoints on behalf of the currently logged-in user.
You can access the raw access token as follows:
import jakarta.inject.Inject;
import org.eclipse.microprofile.jwt.JsonWebToken;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.security.Authenticated;
@Path("/web-app")
@Authenticated
public class ProtectedResource {
@Inject
JsonWebToken accessToken;
// or
// @Inject
// AccessTokenCredential accessTokenCredential;
@GET
public String getReservationOnBehalfOfUser() {
String rawAccessToken = accessToken.getRawToken();
//or
//String rawAccessToken = accessTokenCredential.getToken();
// Use the raw access token to access a remote endpoint.
// For example, use RestClient to set this token as a `Bearer` scheme value of the HTTP `Authorization` header:
// `Authorization: Bearer rawAccessToken`.
return getReservationfromRemoteEndpoint(rawAccesstoken);
}
}
当授权代码流访问令牌被注入为 When an authorization code flow access token is injected as |
当颁发给 Quarkus
|
JsonWebToken
和 AccessTokenCredential
的注入 在 @RequestScoped
和 @ApplicationScoped
上下文中都受支持。
Injection of the JsonWebToken
and AccessTokenCredential
is supported in both @RequestScoped
and @ApplicationScoped
contexts.
Quarkus OIDC 使用刷新令牌作为其 session management 进程的一部分来刷新当前 ID 和访问令牌。
Quarkus OIDC uses the refresh token to refresh the current ID and access tokens as part of its session-management process.
User info
如果 ID 令牌未提供有关当前经过验证的用户的足够信息,你可以从 UserInfo
端点获取更多信息。设置 quarkus.oidc.authentication.user-info-required=true
属性以从 OIDC UserInfo
端点请求 UserInfo JSON 对象。
If the ID token does not provide enough information about the currently authenticated user, you can get more information from the UserInfo
endpoint.
Set the quarkus.oidc.authentication.user-info-required=true
property to request a UserInfo JSON object from the OIDC UserInfo
endpoint.
将使用授权代码授予响应返回的访问令牌向 OIDC 提供程序 UserInfo
端点发送请求,并将创建 io.quarkus.oidc.UserInfo
对象(一个简单的 jakarta.json.JsonObject
封装)。可以将 io.quarkus.oidc.UserInfo
注入或作为 SecurityIdentity userinfo
属性来访问。
A request is sent to the OIDC provider UserInfo
endpoint by using the access token returned with the authorization code grant response, and an io.quarkus.oidc.UserInfo
(a simple jakarta.json.JsonObject
wrapper) object is created.
io.quarkus.oidc.UserInfo
can be injected or accessed as a SecurityIdentity userinfo
attribute.
如果满足以下某个条件,则 `quarkus.oidc.authentication.user-info-required`将自动启用:
quarkus.oidc.authentication.user-info-required
is automatically enabled if one of these conditions is met:
-
if
quarkus.oidc.roles.source
is set touserinfo
orquarkus.oidc.token.verify-access-token-with-user-info
is set totrue
orquarkus.oidc.authentication.id-token-required
is set tofalse
, the current OIDC tenant must support a UserInfo endpoint in these cases. -
if
io.quarkus.oidc.UserInfo
injection point is detected but only if the current OIDC tenant supports a UserInfo endpoint.
Accessing the OIDC configuration information
当前租户的已发现 OpenID Connect configuration metadata 由 io.quarkus.oidc.OidcConfigurationMetadata
表示,并且可以注入或作为 SecurityIdentity
configuration-metadata
属性来访问。
The current tenant’s discovered OpenID Connect configuration metadata is represented by io.quarkus.oidc.OidcConfigurationMetadata
and can be injected or accessed as a SecurityIdentity
configuration-metadata
attribute.
如果端点为公共端点,则会注入默认租户的 OidcConfigurationMetadata
。
The default tenant’s OidcConfigurationMetadata
is injected if the endpoint is public.
Mapping token claims and SecurityIdentity
roles
将角色从已验证令牌映射到 SecurityIdentity 角色的方法与 Bearer tokens 中所述方法相同。唯一的区别是默认情况下使用 ID token 作为角色的来源。
The way the roles are mapped to the SecurityIdentity roles from the verified tokens is identical to how it is done for the Bearer tokens. The only difference is that ID token is used as a source of the roles by default.
如果你使用 Keycloak,请为 ID 令牌设置 If you use Keycloak, set a |
但是,根据你的 OIDC 提供程序,角色可能存储在访问令牌或用户信息中。
However, depending on your OIDC provider, roles might be stored in the access token or the user info.
如果访问令牌包含角色,并且此访问令牌并不意味着要传播到下游端点,则设置`quarkus.oidc.roles.source=accesstoken`。
If the access token contains the roles and this access token is not meant to be propagated to the downstream endpoints, then set quarkus.oidc.roles.source=accesstoken
.
如果UserInfo是角色的来源,则设置 quarkus.oidc.roles.source=userinfo
,如果需要,quarkus.oidc.roles.role-claim-path
。
If UserInfo is the source of the roles, then set quarkus.oidc.roles.source=userinfo
, and if needed, quarkus.oidc.roles.role-claim-path
.
此外,您还可以使用自定义`SecurityIdentityAugmentor`添加角色。有关更多信息,请参阅 SecurityIdentity customization。 您还可以使用 HTTP Security policy将令牌声明创建的 SecurityIdentity
角色映射到特定于部署的角色。
Additionally, you can also use a custom SecurityIdentityAugmentor
to add the roles.
For more information, see SecurityIdentity customization.
You can also map SecurityIdentity
roles created from token claims to deployment-specific roles with the HTTP Security policy.
Ensuring validity of tokens and authentication data
身份验证过程的核心部分是确保可信链和信息的有效性。这是通过确保可以信任令牌来完成的。
A core part of the authentication process is ensuring the chain of trust and validity of the information. This is done by ensuring tokens can be trusted.
Token verification and introspection
OIDC 授权码流令牌的验证过程遵循 Bearer 令牌身份验证令牌验证和内省逻辑。有关更多信息,请参阅“Quarkus OpenID Connect(OIDC)Bearer 令牌身份验证”指南的 Token verification and introspection 部分。
The verification process of OIDC authorization code flow tokens follows the Bearer token authentication token verification and introspection logic. For more information, see the Token verification and introspection section of the "Quarkus OpenID Connect (OIDC) Bearer token authentication" guide.
对于 Quarkus With Quarkus |
Token introspection and UserInfo cache
除非预计授权码访问令牌是角色的来源,否则不会对其进行内省。但是,它们将被用于获取 UserInfo
。 如果需要令牌内省、`UserInfo`或两者,则将存在一个或两个带有授权码访问令牌的远程调用。
Code flow access tokens are not introspected unless they are expected to be the source of roles.
However, they will be used to get UserInfo
.
There will be one or two remote calls with the code flow access token if the token introspection, UserInfo
, or both are required.
有关使用默认令牌缓存或注册自定义缓存实现的更多信息,请参阅 Token introspection and UserInfo cache。
For more information about using the default token cache or registering a custom cache implementation, see Token introspection and UserInfo cache.
JSON web token claim verification
有关声明验证的信息,包括 iss
(颁发者)声明,请参阅 JSON Web Token claim verification 部分。如果 web-app
应用程序请求访问令牌验证,则它适用于ID令牌,也适用于JWT格式的访问令牌。
For information about the claim verification, including the iss
(issuer) claim, see the JSON Web Token claim verification section.
It applies to ID tokens and also to access tokens in a JWT format, if the web-app
application has requested the access token verification.
Proof Key for Code Exchange (PKCE)
Proof Key for Code Exchange (PKCE)最大程度地降低了授权代码拦截风险。
Proof Key for Code Exchange (PKCE) minimizes the risk of authorization code interception.
虽然 PKCE 对公共 OIDC 客户端(比如在浏览器中运行的 SPA 脚本)至关重要,但它也可以为 Quarkus OIDC web-app
应用程序提供额外的保护。 使用 PKCE,Quarkus OIDC web-app
应用程序充当机密 OIDC 客户端,可以安全地存储客户端机密并用它来交换令牌的代码。
While PKCE is of primary importance to public OIDC clients, such as SPA scripts running in a browser, it can also provide extra protection to Quarkus OIDC web-app
applications.
With PKCE, Quarkus OIDC web-app
applications act as confidential OIDC clients that can securely store the client secret and use it to exchange the code for the tokens.
您可以使用 quarkus.oidc.authentication.pkce-required
属性和一个 32 字符的机密为 OIDC web-app 端点启用 PKCE,该机密用于在状态 Cookie 中加密 PKCE 代码验证器,如下例所示:
You can enable PKCE for your OIDC web-app endpoint with a quarkus.oidc.authentication.pkce-required
property and a 32-character secret that is required to encrypt the PKCE code verifier in the state cookie, as shown in the following example:
quarkus.oidc.authentication.pkce-required=true
quarkus.oidc.authentication.state-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU
如果您已经有一个 32 字符的客户端机密,则不需要设置 quarkus.oidc.authentication.pkce-secret
属性,除非您更喜欢使用不同的密钥。如果未配置此机密,并且在客户端机密少于 16 个字符的情况下无法回退到客户端机密,则会自动生成此机密。
If you already have a 32-character client secret, you do not need to set the quarkus.oidc.authentication.pkce-secret
property unless you prefer to use a different secret key.
This secret will be auto-generated if it is not configured and if the fallback to the client secret is not possible in cases where the client secret is less than 16 characters long.
密钥用于在用户使用 code_challenge
查询参数重定向到 OIDC 提供程序进行身份验证时对随机生成的 PKCE code_verifier
进行加密。当用户重定向回 Quarkus 并发送到令牌端点,以及 code
、客户端机密和其他参数以完成代码交换时,将对 code_verifier
进行解密。如果 code_verifier
的 SHA256`摘要与身份验证请求期间提供的 `code_challenge
不匹配,提供程序将失败代码交换。
The secret key is required to encrypt a randomly generated PKCE code_verifier
while the user is redirected with the code_challenge
query parameter to an OIDC provider to authenticate.
The code_verifier
is decrypted when the user is redirected back to Quarkus and sent to the token endpoint alongside the code
, client secret, and other parameters to complete the code exchange.
The provider will fail the code exchange if a SHA256
digest of the code_verifier
does not match the code_challenge
that was provided during the authentication request.
Handling and controlling the lifetime of authentication
身份验证的另一个重要要求是,确保会话所基于的数据是最新的,而无需要求用户对每个请求进行身份验证。 还有一些情况下明确要求注销事件。使用以下要点为保护您的 Quarkus 应用程序找到正确的平衡:
Another important requirement for authentication is to ensure that the data the session is based on is up-to-date without requiring the user to authenticate for every single request. There are also situations where a logout event is explicitly requested. Use the following key points to find the right balance for securing your Quarkus applications:
Cookies
OIDC 适配器使用 Cookie 来保持会话、代码流和注销后状态。此状态是控制身份验证数据生命周期的一个关键元素。
The OIDC adapter uses cookies to keep the session, code flow, and post-logout state. This state is a key element controlling the lifetime of authentication data.
使用 quarkus.oidc.authentication.cookie-path
属性来确保在使用重叠或不同的根访问受保护资源时可见同一 Cookie。例如:
Use the quarkus.oidc.authentication.cookie-path
property to ensure that the same cookie is visible when you access protected resources with overlapping or different roots.
For example:
-
/index.html
and/web-app/service
-
/web-app/service1
and/web-app/service2
-
/web-app1/service
and/web-app2/service
默认情况下,quarkus.oidc.authentication.cookie-path
设置为 /
,但您可以在需要时将其更改为更具体的路径,例如,/web-app
。
By default, quarkus.oidc.authentication.cookie-path
is set to /
but you can change this to a more specific path if required, for example, /web-app
.
如需动态设置 cookie 路径,需配置 quarkus.oidc.authentication.cookie-path-header
属性。设置 quarkus.oidc.authentication.cookie-path-header
属性。例如,如需使用 X-Forwarded-Prefix
HTTP 标头的值动态设置 cookie 路径,请将属性配置为 quarkus.oidc.authentication.cookie-path-header=X-Forwarded-Prefix
.
To set the cookie path dynamically, configure the quarkus.oidc.authentication.cookie-path-header
property.
Set the quarkus.oidc.authentication.cookie-path-header
property.
For example, to set the cookie path dynamically by using the value of the`X-Forwarded-Prefix` HTTP header, configure the property to quarkus.oidc.authentication.cookie-path-header=X-Forwarded-Prefix
.
如果设置了 quarkus.oidc.authentication.cookie-path-header
,但当前请求中没有可配置的 HTTP 标头,则会检查 quarkus.oidc.authentication.cookie-path
.
If quarkus.oidc.authentication.cookie-path-header
is set but no configured HTTP header is available in the current request, then the quarkus.oidc.authentication.cookie-path
will be checked.
如果您的应用程序部署在多个域上,请设置 quarkus.oidc.authentication.cookie-domain
属性,以便受保护的 Quarkus 服务均可见会话 cookie。例如,如果您在以下两个域名上部署了 Quarkus 服务,则必须将 quarkus.oidc.authentication.cookie-domain
属性设置为 company.net
:
If your application is deployed across multiple domains, set the quarkus.oidc.authentication.cookie-domain
property so that the session cookie is visible to all protected Quarkus services.
For example, if you have Quarkus services deployed on the following two domains, then you must set the quarkus.oidc.authentication.cookie-domain
property to company.net
:
State cookies
状态 cookie 用于支持授权码流程完成。当启动授权码流程时,Quarkus 创建一个状态 cookie 和一个匹配的 state
查询参数,然后再将用户重定向到 OIDC 提供商。当用户重定向回 Quarkus 以完成授权码流程时,Quarkus 期望请求 URI 必须包含 state
查询参数,并且它必须与当前状态 cookie 值匹配。
State cookies are used to support authorization code flow completion.
When an authorization code flow is started, Quarkus creates a state cookie and a matching state
query parameter, before redirecting the user to the OIDC provider.
When the user is redirected back to Quarkus to complete the authorization code flow, Quarkus expects that the request URI must contain the state
query parameter and it must match the current state cookie value.
状态 cookie 的默认生存期为 5 分钟,您可以通过 quarkus.oidc.authenticaion.state-cookie-age
Duration 属性来更改它。
The default state cookie age is 5 mins and you can change it with a quarkus.oidc.authenticaion.state-cookie-age
Duration property.
每次启动新的授权码流程时,Quarkus 都会创建一个唯一的状态 cookie 名称以支持多标签页身份验证。代表同一用户的许多并发身份验证请求可能导致创建大量状态 cookie。如果您不希望您的用户使用多个浏览器标签页进行身份验证,建议使用 quarkus.oidc.authenticaion.allow-multiple-code-flows=false
将其禁用。它还确保为每个新的用户身份验证创建相同的状态 cookie 名称。
Quarkus creates a unique state cookie name every time a new authorization code flow is started to support multi-tab authentication. Many concurrent authentication requests on behalf of the same user may cause a lot of state cookies be created.
If you do not want to allow your users use multiple browser tabs to authenticate then it is recommended to disable it with quarkus.oidc.authenticaion.allow-multiple-code-flows=false
. It also ensures that the same state cookie name is created for every new user authentication.
Session cookie and default TokenStateManager
OIDC CodeAuthenticationMechanism
使用默认 io.quarkus.oidc.TokenStateManager
接口实施来保留授权码或刷新授予响应中返回的 ID、访问和刷新令牌,保存在一个加密会话 cookie 中。
OIDC CodeAuthenticationMechanism
uses the default io.quarkus.oidc.TokenStateManager
interface implementation to keep the ID, access, and refresh tokens returned in the authorization code or refresh grant responses in an encrypted session cookie.
它使 Quarkus OIDC 端点完全无状态,建议遵循此策略以实现最佳的可伸缩性成果。
It makes Quarkus OIDC endpoints completely stateless and it is recommended to follow this strategy to achieve the best scalability results.
参阅本指南的 Database TokenStateManager 部分,了解有关在数据库或其他服务器端存储解决方案中存储令牌的信息。如果您愿意并且有令人信服的理由将令牌状态存储在服务器上,那么此方法是合适的。
Refer to the Database TokenStateManager section of this guide for information on storing tokens in the database or other server-side storage solutions. This approach is suitable if you prefer and have compelling reasons to store the token state on the server.
请参阅 Session cookie and custom TokenStateManager 部分,了解令牌存储的替代方法。这非常适合那些寻求令牌状态管理的定制解决方案,尤其是当标准服务器端存储无法满足您的特定要求时。
See the Session cookie and custom TokenStateManager section for alternative methods of token storage. This is ideal for those seeking customized solutions for token state management, especially when standard server-side storage does not meet your specific requirements.
您可以配置默认 TokenStateManager
以避免在会话 cookie 中保存访问令牌,并且仅保留 ID 和刷新令牌或仅保留一个 ID 令牌。
You can configure the default TokenStateManager
to avoid saving an access token in the session cookie and to only keep ID and refresh tokens or a single ID token only.
仅当端点需要执行以下操作时才需要访问令牌:
An access token is only required if the endpoint needs to do the following actions:
-
Retrieve
UserInfo
-
Access the downstream service with this access token
-
Use the roles associated with the access token, which are checked by default
在这种情况下,使用 quarkus.oidc.token-state-manager.strategy
属性将令牌状态策略配置如下:
In such cases, use the quarkus.oidc.token-state-manager.strategy
property to configure the token state strategy as follows:
To… | Set the property to … |
---|---|
Keep the ID and refresh tokens only |
|
Keep the ID token only |
|
如果所选会话 cookie 策略组合令牌并生成大于 4KB 的大会话 cookie 值,则一些浏览器可能无法处理此类 cookie 大小。当 ID、访问和刷新令牌是 JWT 令牌,并且所选策略为 keep-all-tokens
时,或者当策略为 id-refresh-token
时具有 ID 和刷新令牌时,可能会发生这种情况。要解决此问题,您可以设置 quarkus.oidc.token-state-manager.split-tokens=true
以为每个令牌创建一个唯一会话令牌。另一种解决方法是将令牌保存在数据库中。有关更多信息,请参阅 Database TokenStateManager.
If your chosen session cookie strategy combines tokens and generates a large session cookie value that is greater than 4KB, some browsers might not be able to handle such cookie sizes.
This can occur when the ID, access, and refresh tokens are JWT tokens and the selected strategy is keep-all-tokens
or with ID and refresh tokens when the strategy is id-refresh-token
.
To work around this issue, you can set quarkus.oidc.token-state-manager.split-tokens=true
to create a unique session token for each token.
An alternative solution is to have the tokens saved in the database.
For more information, see Database TokenStateManager.
默认的 TokenStateManager
在将标记存储在会话 Cookie 中之前会对其进行加密。以下示例展示了如何将其配置为分割并加密标记:
The default TokenStateManager
encrypts the tokens before storing them in the session cookie.
The following example shows how you configure it to split the tokens and encrypt them:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=quarkus-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.token-state-manager.split-tokens=true
quarkus.oidc.token-state-manager.encryption-secret=eUk1p7UB3nFiXZGUXi0uph1Y9p34YhBU
标记加密密钥至少必须有 32 个字符的长度。如果未配置此键,则将对 quarkus.oidc.credentials.secret
或 quarkus.oidc.credentials.jwt.secret
进行哈希处理以创建加密键。
The token encryption secret must be at least 32 characters long.
If this key is not configured, then either quarkus.oidc.credentials.secret
or quarkus.oidc.credentials.jwt.secret
will be hashed to create an encryption key.
如果 Quarkus 使用以下其中一个身份验证方法向 OIDC 提供程序进行身份验证,则配置 quarkus.oidc.token-state-manager.encryption-secret
属性:
Configure the quarkus.oidc.token-state-manager.encryption-secret
property if Quarkus authenticates to the OIDC provider by using one of the following authentication methods:
-
mTLS
-
private_key_jwt
, where a private RSA or EC key is used to sign a JWT token
否则,将生成一个随机密钥,如果 Quarkus 应用程序在云中运行,并且有多个 Pod 管理请求,则这可能会造成问题。
Otherwise, a random key is generated, which can be problematic if the Quarkus application is running in the cloud with multiple pods managing the requests.
您可以通过设置 quarkus.oidc.token-state-manager.encryption-required=false
来停用会话 Cookie 中的标记加密。
You can disable token encryption in the session cookie by setting quarkus.oidc.token-state-manager.encryption-required=false
.
Session cookie and custom TokenStateManager
如果您想自定义标记与会话 Cookie 关联的方式,请注册一个自定义 io.quarkus.oidc.TokenStateManager
的实现作为 @ApplicationScoped
的 CDI bean。
If you want to customize the way the tokens are associated with the session cookie, register a custom io.quarkus.oidc.TokenStateManager
implementation as an @ApplicationScoped
CDI bean.
例如,您可能希望将标记保存在缓存集群中,并且仅将密钥存储在会话 Cookie 中。请注意,如果您需要让多个微服务节点可以使用标记,则此方法可能会带来一些挑战。
For example, you might want to keep the tokens in a cache cluster and have only a key stored in a session cookie. Note that this approach might introduce some challenges if you need to make the tokens available across multiple microservices nodes.
这是一个简单的示例:
Here is a simple example:
package io.quarkus.oidc.test;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import jakarta.inject.Inject;
import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.TokenStateManager;
import io.quarkus.oidc.runtime.DefaultTokenStateManager;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
@Alternative
@Priority(1)
public class CustomTokenStateManager implements TokenStateManager {
@Inject
DefaultTokenStateManager tokenStateManager;
@Override
public Uni<String> createTokenState(RoutingContext routingContext, OidcTenantConfig oidcConfig,
AuthorizationCodeTokens sessionContent, TokenStateManager.CreateTokenStateRequestContext requestContext) {
return tokenStateManager.createTokenState(routingContext, oidcConfig, sessionContent, requestContext)
.map(t -> (t + "|custom"));
}
@Override
public Uni<AuthorizationCodeTokens> getTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig,
String tokenState, TokenStateManager.GetTokensRequestContext requestContext) {
if (!tokenState.endsWith("|custom")) {
throw new IllegalStateException();
}
String defaultState = tokenState.substring(0, tokenState.length() - 7);
return tokenStateManager.getTokens(routingContext, oidcConfig, defaultState, requestContext);
}
@Override
public Uni<Void> deleteTokens(RoutingContext routingContext, OidcTenantConfig oidcConfig, String tokenState,
TokenStateManager.DeleteTokensRequestContext requestContext) {
if (!tokenState.endsWith("|custom")) {
throw new IllegalStateException();
}
String defaultState = tokenState.substring(0, tokenState.length() - 7);
return tokenStateManager.deleteTokens(routingContext, oidcConfig, defaultState, requestContext);
}
}
有关默认 TokenStateManager
将标记存储在已加密会话 Cookie 中的信息,请参阅 Session cookie and default TokenStateManager。
For information about the default TokenStateManager
storing tokens in an encrypted session cookie, see Session cookie and default TokenStateManager.
有关将标记存储在数据库中的自定义 Quarkus TokenStateManager
实施的信息,请参阅 Database TokenStateManager。
For information about the custom Quarkus TokenStateManager
implementation storing tokens in a database, see Database TokenStateManager.
Database TokenStateManager
如果您更愿意遵循有状态标记存储策略,则可以使用 Quarkus 提供的自定义 TokenStateManager
来使您的应用程序将标记存储在数据库中,而不是将它们存储在已加密会话 Cookie 中,这在 Session cookie and default TokenStateManager 部分中以默认方式进行。
If you prefer to follow a stateful token storage strategy, you can use a custom TokenStateManager
provided by Quarkus to have your application store tokens in a database, instead of storing them in an encrypted session cookie, which is done by default as described in the Session cookie and default TokenStateManager section.
要使用此功能,请将以下扩展名添加到您的项目:
To use this feature, add the following extension to your project:
Unresolved directive in security-oidc-code-flow-authentication.adoc - include::{includes}/devtools/extension-add.adoc[]
此扩展名将使用基于数据库的扩展名替换默认的 io.quarkus.oidc.TokenStateManager
。
This extension will replace the default io.quarkus.oidc.TokenStateManager
with a database-based one.
OIDC 数据库标记状态管理器在底层使用响应式 SQL 客户端,以避免阻塞,因为身份验证可能发生在 IO 线程上。
OIDC Database Token State Manager uses a Reactive SQL client under the hood to avoid blocking because the authentication is likely to happen on an IO thread.
根据您的数据库,精确包含并配置一个 Reactive SQL client。支持以下响应式 SQL 客户端:
Depending on your database, include and configure exactly one Reactive SQL client. The following Reactive SQL clients are supported:
-
Reactive Microsoft SQL client
-
Reactive MySQL client
-
Reactive PostgreSQL client
-
Reactive Oracle client
-
Reactive DB2 client
如果您的应用程序已经将 Hibernate ORM 与其中一个 JDBC 驱动程序扩展名结合使用,则无需切换到使用响应式 SQL 客户端。
Your application is not required to switch to using the Reactive SQL client if it already uses Hibernate ORM with one of the JDBC driver extensions.
例如,您已经有一个将 Hibernate ORM 扩展名与 PostgreSQL JDBC Driver 结合使用的应用程序,并且您的数据源已配置如下:
For example, you already have an application that uses the Hibernate ORM extension together with a PostgreSQL JDBC Driver and your datasource is configured like this:
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test
现在,如果您决定使用 OIDC 数据库标记状态管理器,则必须添加以下依赖项并设置响应式驱动程序 URL:
Now, if you decided to use OIDC Database Token State Manager, you must add the following dependencies and set a reactive driver URL:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-oidc-db-token-state-manager</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
implementation("io.quarkus:quarkus-oidc-db-token-state-manager")
implementation("io.quarkus:quarkus-reactive-pg-client")
quarkus.datasource.reactive.url=postgresql://localhost:5432/quarkus_test
现在,标记可以存储在数据库中。
Now, the tokens are ready to be stored in the database.
默认情况下,会为您创建一个用于存储令牌的数据库表,但是,您可以使用 quarkus.oidc.db-token-state-manager.create-database-table-if-not-exists
配置属性禁用此选项。如果您希望 Hibernate ORM 扩展创建此表,那么您只需包含一个实体,例如以下内容:
By default, a database table used for storing tokens is created for you, however, you can disable this option with the quarkus.oidc.db-token-state-manager.create-database-table-if-not-exists
configuration property.
Should you want the Hibernate ORM extension to create this table instead, you must just include an Entity, such as the following:
package org.acme.manager;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Table(name = "oidc_db_token_state_manager") 1
@Entity
public class OidcDbTokenStateManagerEntity {
@Id
String id;
@Column(name = "id_token", length = 4000) 2
String idToken;
@Column(name = "refresh_token", length = 4000)
String refreshToken;
@Column(name = "access_token", length = 4000)
String accessToken;
@Column(name = "expires_in")
Long expiresIn;
}
1 | The Hibernate ORM extension will only create this table for you when the database schema is generated. For more information, refer to the Hibernate ORM guide. |
2 | You can choose a column length depending on the length of your tokens. |
Logout and expiration
认证信息失效的主要有两种方式:令牌过期且未续订或触发了明确的注销操作。
There are two main ways for the authentication information to expire: the tokens expired and were not renewed or an explicit logout operation was triggered.
我们从明确的注销操作开始。
Let’s start with explicit logout operations.
User-initiated logout
用户可以通过发送请求到使用 quarkus.oidc.logout.path
属性设置的 Quarkus 端点注销路径来请求注销。例如,如果端点地址为 https://application.com/webapp
,且 quarkus.oidc.logout.path
设置为 /logout
,则必须将注销请求发送到 https://application.com/webapp/logout
。
Users can request a logout by sending a request to the Quarkus endpoint logout path set with a quarkus.oidc.logout.path
property.
For example, if the endpoint address is https://application.com/webapp
and the quarkus.oidc.logout.path
is set to /logout
, then the logout request must be sent to https://application.com/webapp/logout
.
此注销请求会启动 RP-initiated logout。用户会重定向到 OIDC 提供者以注销,在这里他们可能会被要求确认注销确实是有意的。
This logout request starts an RP-initiated logout. The user will be redirected to the OIDC provider to log out, where they can be asked to confirm the logout is indeed intended.
注销完成后,用户会返回到端点注销后页面,前提是已设置 quarkus.oidc.logout.post-logout-path
属性。例如,如果端点地址为 https://application.com/webapp
,且 quarkus.oidc.logout.post-logout-path
设置为 /signin
,那么用户会返回到 https://application.com/webapp/signin
。请注意,此 URI 必须在 OIDC 提供者中注册为有效的 post_logout_redirect_uri
。
The user will be returned to the endpoint post-logout page once the logout has been completed and if the quarkus.oidc.logout.post-logout-path
property is set.
For example, if the endpoint address is https://application.com/webapp
and the quarkus.oidc.logout.post-logout-path
is set to /signin
, then the user will be returned to https://application.com/webapp/signin
.
Note, this URI must be registered as a valid post_logout_redirect_uri
in the OIDC provider.
如果设置了 quarkus.oidc.logout.post-logout-path
,那么会创建一个 q_post_logout
Cookie,并向注销重定向 URI 中添加一个匹配的 state
查询参数,且注销完成后,OIDC 提供者会返回此 state
。建议 Quarkus web-app
应用程序检查 state
查询参数是否与 q_post_logout
Cookie 的值匹配,例如,可以在 Jakarta REST 过滤器中执行此操作。
If the quarkus.oidc.logout.post-logout-path
is set, then a q_post_logout
cookie will be created and a matching state
query parameter will be added to the logout redirect URI and the OIDC provider will return this state
once the logout has been completed.
It is recommended for the Quarkus web-app
applications to check that a state
query parameter matches the value of the q_post_logout
cookie, which can be done, for example, in a Jakarta REST filter.
请注意,使用 OpenID Connect Multi-Tenancy 时 Cookie 名称会发生变化。例如,对于 ID 为 tenant_1
的租户,它将被命名为 q_post_logout_tenant_1
,依此类推。
Note that a cookie name varies when using OpenID Connect Multi-Tenancy.
For example, it will be named q_post_logout_tenant_1
for a tenant with a tenant_1
ID, and so on.
以下是如何将 Quarkus 应用程序配置为启动退出流程的示例:
Here is an example of how to configure a Quarkus application to initiate a logout flow:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.logout.path=/logout
# Logged-out users should be returned to the /welcome.html site which will offer an option to re-login:
quarkus.oidc.logout.post-logout-path=/welcome.html
# Only the authenticated users can initiate a logout:
quarkus.http.auth.permission.authenticated.paths=/logout
quarkus.http.auth.permission.authenticated.policy=authenticated
# All users can see the Welcome page:
quarkus.http.auth.permission.public.paths=/welcome.html
quarkus.http.auth.permission.public.policy=permit
您可能还需要将 quarkus.oidc.authentication.cookie-path
设置为您在所有应用程序资源中通用的路径值,例如本例中的 /
。有关更多信息,请参阅 Cookies 部分。
You might also want to set quarkus.oidc.authentication.cookie-path
to a path value common to all the application resources, which is /
in this example.
For more information, see the oidc-cookies section.
一些 OIDC 提供商不支持 RP-initiated logout 规范并且不会返回 OpenID Connect 广为人知的 Some OIDC providers do not support a RP-initiated logout specification and do not return an OpenID Connect well-known 根据 RP-initiated logout 规范, According to the RP-initiated logout specification, the 您可以使用 You can use
|
Back-channel logout
OIDC 提供商可以使用身份验证数据强制退出所有应用程序。这称为后通道退出。在这种情况下,OIDC 会从每个应用程序调用一个特定的 URL 来触发退出。
The OIDC provider can force the logout of all applications by using the authentication data. This is known as back-channel logout. In this case, the OIDC will call a specific URL from each application to trigger that logout.
OIDC 供应商使用 Back-channel logout 注销当前用户目前登录的所有应用程序,无需用户代理。
OIDC providers use Back-channel logout to log out the current user from all the applications into which this user is currently logged in, bypassing the user agent.
您可以将 Quarkus 配置为像下面一样支持后台注销:
You can configure Quarkus to support Back-channel logout as follows:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.logout.backchannel.path=/back-channel-logout
绝对`back-channel logout`URL是通过以下方式计算得出的:在当前端点URL后面加上`quarkus.oidc.back-channel-logout.path`,例如`http://localhost:8080/back-channel-logout`。您需要在OIDC提供商的管理控制台中配置此URL。
The absolute back-channel logout
URL is calculated by adding quarkus.oidc.back-channel-logout.path
to the current endpoint URL, for example, http://localhost:8080/back-channel-logout
.
You will need to configure this URL in the admin console of your OIDC provider.
OIDC提供商在当前登出令牌中未设置到期声明时,您还必须配置令牌年龄属性,以使登出令牌验证成功。例如,将`quarkus.oidc.token.age=10S`设置为确保从登出令牌的`iat`(签发时间)开始,不超过10秒。
You will also need to configure a token age property for the logout token verification to succeed if your OIDC provider does not set an expiry claim in the current logout token.
For example, set quarkus.oidc.token.age=10S
to ensure that no more than 10 seconds elapse since the logout token’s iat
(issued at) time.
Front-channel logout
您可以使用 Front-channel logout直接从用户代理(例如其浏览器)注销当前用户。它类似于Back-channel logout,但登出步骤是由用户代理(例如浏览器)执行的,而不是由OIDC提供商在后台执行的。此选项很少使用。
You can use Front-channel logout to log out the current user directly from the user agent, for example, its browser. It is similar to back-channel-logout but the logout steps are executed by the user agent, such as the browser, and not in the background by the OIDC provider. This option is rarely used.
您可以按照以下步骤配置Quarkus以支持前端通道登出:
You can configure Quarkus to support Front-channel logout as follows:
quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.oidc.client-id=frontend
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
quarkus.oidc.logout.frontchannel.path=/front-channel-logout
此路径将与此请求的路径进行比较,如果路径匹配,用户将登出。
This path will be compared to the current request’s path, and the user will be logged out if these paths match.
Local logout
User-initiated logout将使用户从OIDC提供商处登出。如果将其用作单点登录,那可能不是您所需的。例如,如果您的OIDC提供商是Google,您将从Google及其服务中登出。而用户可能只想从该特定应用程序中登出。另一种用例可能是OIDC提供商没有登出端点。
user-initiated-logout will log the user out of the OIDC provider. If it is used as single sign-on, it might not be what you require. If, for example, your OIDC provider is Google, you will be logged out from Google and its services. Instead, the user might just want to log out of that specific application. Another use case might be when the OIDC provider does not have a logout endpoint.
通过使用OidcSession,您可以支持本地登出,这意味着仅清除本地会话Cookie,如下面的示例所示:
By using oidc-session, you can support a local logout, which means that only the local session cookie is cleared, as shown in the following example:
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.quarkus.oidc.OidcSession;
@Path("/service")
public class ServiceResource {
@Inject
OidcSession oidcSession;
@GET
@Path("logout")
public String logout() {
oidcSession.logout().await().indefinitely();
return "You are logged out".
}
Using OidcSession
for local logout
`io.quarkus.oidc.OidcSession`是当前`IdToken`的包装器,它可以帮助执行Local logout、检索当前会话的租户标识符以及检查会话何时过期。随着时间的推移,将向其添加更多有用的方法。
io.quarkus.oidc.OidcSession
is a wrapper around the current IdToken
, which can help to perform a local-logout, retrieve the current session’s tenant identifier, and check when the session will expire.
More useful methods will be added to it over time.
Session management
默认情况下,登出会根据OIDC提供商颁发的ID令牌的到期时间进行。当ID令牌过期时,Quarkus端点的当前用户会话将失效,并且用户将被重新定向到OIDC提供商再次进行身份验证。如果OIDC提供商中的会话仍然有效,用户将自动重新身份验证,而无需再次提供其凭据。
By default, logout is based on the expiration time of the ID token issued by the OIDC provider. When the ID token expires, the current user session at the Quarkus endpoint is invalidated, and the user is redirected to the OIDC provider again to authenticate. If the session at the OIDC provider is still active, users are automatically re-authenticated without needing to provide their credentials again.
可以通过启用`quarkus.oidc.token.refresh-expired`属性来自动扩展当前用户会话。如果设置为`true`,当当前ID令牌过期时,刷令牌授权将用于更新ID令牌以及访问令牌和刷令牌。
The current user session can be automatically extended by enabling the quarkus.oidc.token.refresh-expired
property.
If set to true
, when the current ID token expires, a refresh token grant will be used to refresh the ID token as well as access and refresh tokens.
如果您有single page application for service applications且您的OIDC提供商脚本(例如`keycloak.js`)在管理授权代码流程,则该脚本还将控制SPA身份验证会话的持续时间。 If you have a single page application for service applications where your OIDC provider script such as |
如果您使用的是Quarkus OIDC`web-app`应用程序,则Quarkus OIDC代码验证机制将管理用户会话的持续时间。
If you work with a Quarkus OIDC web-app
application, then the Quarkus OIDC code authentication mechanism manages the user session lifespan.
要使用刷令牌,您应仔细配置会话Cookie年龄。会话年龄应大于ID令牌的持续时间,并且应接近或等于刷令牌的持续时间。
To use the refresh token, you should carefully configure the session cookie age. The session age should be longer than the ID token lifespan and close to or equal to the refresh token lifespan.
您可以通过添加当前ID令牌的持续时间值以及`quarkus.oidc.authentication.session-age-extension`和`quarkus.oidc.token.lifespan-grace`属性的值来计算会话年龄。
You calculate the session age by adding the lifespan value of the current ID token and the values of the quarkus.oidc.authentication.session-age-extension
and quarkus.oidc.token.lifespan-grace
properties.
如果需要,您可以仅使用`quarkus.oidc.authentication.session-age-extension`属性来显着延长会话持续时间。您仅使用`quarkus.oidc.token.lifespan-grace`属性即可考虑一些小的时钟偏差。 You use only the |
当当前经过身份验证的用户返回到受保护的Quarkus端点,并且与会话Cookie关联的ID令牌已过期后,默认情况下,用户将自动重定向到OIDC授权端点以重新进行身份验证。如果用户与该OIDC提供商之间的会话仍然有效,则OIDC提供商可能会再次向用户发起质询,这种情况可能发生在会话配置为持续时间长于ID令牌的情况下。
When the current authenticated user returns to the protected Quarkus endpoint and the ID token associated with the session cookie has expired, then, by default, the user is automatically redirected to the OIDC Authorization endpoint to re-authenticate. The OIDC provider might challenge the user again if the session between the user and this OIDC provider is still active, which might happen if the session is configured to last longer than the ID token.
如果将`quarkus.oidc.token.refresh-expired`设置为`true`,则系统将使用初始授权代码授权响应返回的刷令牌来刷新过期的ID令牌(和访问令牌)。作为此流程的一部分,此刷令牌本身也可能被回收(刷新)。因此,将创建新的会话Cookie并延长会话。
If the quarkus.oidc.token.refresh-expired
is set to true
, then the expired ID token (and the access token) is refreshed by using the refresh token returned with the initial authorization code grant response.
This refresh token might also be recycled (refreshed) itself as part of this process.
As a result, the new session cookie is created, and the session is extended.
在用户不十分活跃的情况下,您可以使用`quarkus.oidc.authentication.session-age-extension`属性来帮助处理过期的ID令牌。如果ID令牌过期,则在下一个用户请求期间,会话Cookie可能不会被返回到Quarkus端点,因为Cookie的持续时间已经过去。Quarkus假定此请求是第一个身份验证请求。针对几乎不活跃的用户,根据安全政策设置`quarkus.oidc.authentication.session-age-extension`为_reasonably_。 In instances where the user is not very active, you can use the |
您可以更进一步的主动作ID令牌或即将过期的访问令牌。将`quarkus.oidc.token.refresh-token-time-skew`设置为您希望预期的刷新值。如果在当前用户请求期间,计算出当前ID令牌将在此`quarkus.oidc.token.refresh-token-time-skew`范围内过期,则刷新ID令牌并创建新的会话Cookie。此属性应设置为小于ID令牌持续时间的值;它越接近此持续时间值,ID令牌的刷新频率就越高。
You can go one step further and proactively refresh ID tokens or access tokens that are about to expire.
Set quarkus.oidc.token.refresh-token-time-skew
to the value you want to anticipate the refresh.
If, during the current user request, it is calculated that the current ID token will expire within this quarkus.oidc.token.refresh-token-time-skew
, then it is refreshed, and the new session cookie is created.
This property should be set to a value that is less than the ID token lifespan; the closer it is to this lifespan value, the more often the ID token is refreshed.
您可以通过定期 ping 您 Quarkus 端点的简单 JavaScript 函数来模拟用户活动,从而进一步优化此过程,这会最大程度地减少用户可能必须重新进行身份验证的时间范围。
You can further optimize this process by having a simple JavaScript function ping your Quarkus endpoint periodically to emulate the user activity, which minimizes the time frame during which the user might have to be re-authenticated.
如果无法刷新会话,当前经过身份验证的用户将被重定向到 OIDC 提供商以重新进行身份验证。但是,在某些情况下,如果用户在早先成功进行身份验证后,在尝试访问应用程序页面时突然看到 OIDC 身份验证提示屏幕,则用户体验可能不理想。 When the session can not be refreshed, the currently authenticated user is redirected to the OIDC provider to re-authenticate. However, the user experience may not be ideal in such cases, if the user, after an earlier successful authentication, is suddently seeing an OIDC authentication challenge screen when trying to access an application page. 取而代之的是,您可以要求将用户重定向到公共的、特定于应用程序的会话过期页面。此页面会通知用户会话已过期,并建议通过关注到安全应用程序欢迎页面的链接来重新进行身份验证。用户单击链接,然后 Quarkus OIDC 强制重新定向到 OIDC 提供商以重新进行身份验证。如果您希望执行此操作,请使用 Instead, you can request that the user is redirected to a public, application specific session expired page first. This page informs the user that the session has now expired and advise to re-authenticate by following a link to a secured application welcome page. The user clicks on the link and Quarkus OIDC enforces a redirect to the OIDC provider to re-authenticate. Use 例如,设置 For example, setting 另请参见 OIDC redirect filters 部分,该部分解释了如何使用自定义 See also the OIDC redirect filters section explaining how a custom |
无法无限期地延长用户会话。返回且持有已过期 ID 令牌的用户将必须在刷新令牌过期后从 OIDC 提供程序端点重新进行认证。 You cannot extend the user session indefinitely. The returning user with the expired ID token will have to re-authenticate at the OIDC provider endpoint once the refresh token has expired. |
Integration with GitHub and non-OIDC OAuth2 providers
一些著名的提供程序,例如 GitHub 或 LinkedIn,不是 OpenID Connect 提供程序,而是 OAuth2 提供程序,它们支持 authorization code flow
。例如, GitHub OAuth2 和 LinkedIn OAuth2。请记住,OIDC 构建于 OAuth2 之上。
Some well-known providers such as GitHub or LinkedIn are not OpenID Connect providers, but OAuth2 providers that support the authorization code flow
.
For example, GitHub OAuth2 and LinkedIn OAuth2.
Remember, OIDC is built on top of OAuth2.
OIDC 与 OAuth2 提供程序之间的主要区别在于,OIDC 提供程序除了 access
和 refresh
提供程序返回的标准授权代码流程 OAuth2
和令牌外,还会返回代表用户身份验证的 ID Token
。
The main difference between OIDC and OAuth2 providers is that OIDC providers return an ID Token
that represents a user authentication, in addition to the standard authorization code flow access
and refresh
tokens returned by OAuth2
providers.
GitHub 等 OAuth2 提供程序不会返回 IdToken
,而用户身份验证是暗示的,并由 access
令牌间接表示。此 access
令牌代表经身份验证的用户,授权当前 Quarkus web-app
应用程序代表经过身份验证的用户访问某些数据。
OAuth2 providers such as GitHub do not return IdToken
, and the user authentication is implicit and indirectly represented by the access
token.
This access
token represents an authenticated user authorizing the current Quarkus web-app
application to access some data on behalf of the authenticated user.
对于 OIDC,您可以验证 ID 令牌以证明身份验证有效性,而在 OAuth2 情况下,您验证访问令牌。这是通过随后调用需要访问令牌的端点来完成的,并且通常返回用户信息。此方法类似于 OIDC UserInfo 方法,其中 UserInfo
由 Quarkus OIDC 代表您获取。
For OIDC, you validate the ID token as proof of authentication validity whereas in the case of OAuth2, you validate the access token.
This is done by subsequently calling an endpoint that requires the access token and that typically returns user information.
This approach is similar to the OIDC code-flow-user-info approach, with UserInfo
fetched by Quarkus OIDC on your behalf.
例如,使用 GitHub 时,Quarkus 端点可以获取 access
令牌,这允许 Quarkus 端点为当前用户请求 GitHub 概要文件。
For example, when working with GitHub, the Quarkus endpoint can acquire an access
token, which allows the Quarkus endpoint to request a GitHub profile for the current user.
为了支持与此类 OAuth2 服务器的集成,需要对 quarkus-oidc
进行 sedikit 不同的配置,以允许授权码流响应而无需 IdToken
:quarkus.oidc.authentication.id-token-required=false
。
To support the integration with such OAuth2 servers, quarkus-oidc
needs to be configured a bit differently to allow the authorization code flow responses without IdToken
: quarkus.oidc.authentication.id-token-required=false
.
即使您配置了该扩展名以支持授权代码流而无需 Even though you configure the extension to support the authorization code flows without 您可以根据 session management 部分中所述,进一步对其进行扩展。 , which you can extend further as described in the session-management section. 这简化了您处理支持多个 OIDC 提供商的应用程序的方式。 This simplifies how you handle an application that supports multiple OIDC providers. |
下一步是确保返回的访问令牌有效并可用于当前 Quarkus 端点。如果提供商提供此类端点,则第一种方法是通过配置 quarkus.oidc.introspection-path
调用 OAuth2 提供商反省端点。在这种情况下,您可以使用访问令牌,将其作为 quarkus.oidc.roles.source=accesstoken
的角色来源。如果不存在反省端点,您可以尝试向提供商请求 UserInfo 代替,因为它至少会验证访问令牌。为此,请指定 quarkus.oidc.token.verify-access-token-with-user-info=true
。您还需要将 quarkus.oidc.user-info-path
属性设置为获取用户信息(或由访问令牌保护的端点)的 URL 端点。对于 GitHub,因为它没有反省端点,所以需要请求用户信息。
The next step is to ensure that the returned access token can be useful and is valid to the current Quarkus endpoint.
The first way is to call the OAuth2 provider introspection endpoint by configuring quarkus.oidc.introspection-path
, if the provider offers such an endpoint.
In this case, you can use the access token as a source of roles using quarkus.oidc.roles.source=accesstoken
.
If no introspection endpoint is present, you can attempt instead to request code-flow-user-info from the provider as it will at least validate the access token.
To do so, specify quarkus.oidc.token.verify-access-token-with-user-info=true
.
You also need to set the quarkus.oidc.user-info-path
property to a URL endpoint that fetches the user info (or to an endpoint protected by the access token).
For GitHub, since it does not have an introspection endpoint, requesting the UserInfo is required.
要求 UserInfo 涉及在每次请求时进行远程调用。 Requiring code-flow-user-info involves making a remote call on every request. 因此, Therefore, 或者,你可能希望考虑使用默认或自定义 UserInfo 缓存提供程序来缓存 Alternatively, you might want to consider caching 大多数著名的社交 OAuth2 提供程序都会强制执行速率限制,因此,你很可能会更希望缓存 UserInfo。 Most well-known social OAuth2 providers enforce rate-limiting so there is a high chance you will prefer to have UserInfo cached. |
OAuth2 服务器可能不支持众所周知的配置端点。在这种情况下,你必须禁用发现并手动配置授权、令牌、内省和 `UserInfo`端点路径。
OAuth2 servers might not support a well-known configuration endpoint.
In this case, you must disable the discovery and configure the authorization, token, and introspection and UserInfo
endpoint paths manually.
对于众所周知的 OIDC 或 OAuth2 提供程序(如 Apple、Facebook、GitHub、Google、Microsoft、Spotify 和 X(以前称为 Twitter)),Quarkus 可以通过 `quarkus.oidc.provider`属性显著简化应用程序的配置。以下是如何在 created a GitHub OAuth application之后将 `quarkus-oidc`与 GitHub 集成的方法。像这样配置 Quarkus 端点:
For well-known OIDC or OAuth2 providers, such as Apple, Facebook, GitHub, Google, Microsoft, Spotify, and X (formerly Twitter), Quarkus can help significantly simplify your application’s configuration with the quarkus.oidc.provider
property.
Here is how you can integrate quarkus-oidc
with GitHub after you have created a GitHub OAuth application.
Configure your Quarkus endpoint like this:
quarkus.oidc.provider=github
quarkus.oidc.client-id=github_app_clientid
quarkus.oidc.credentials.secret=github_app_clientsecret
# user:email scope is requested by default, use 'quarkus.oidc.authentication.scopes' to request different scopes such as `read:user`.
# See https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps for more information.
# Consider enabling UserInfo Cache
# quarkus.oidc.token-cache.max-size=1000
# quarkus.oidc.token-cache.time-to-live=5M
#
# Or having UserInfo cached inside IdToken itself
# quarkus.oidc.cache-user-info-in-idtoken=true
有关配置其他众所周知提供程序的详细信息,请参阅 OpenID Connect providers。
For more information about configuring other well-known providers, see OpenID Connect providers.
对于像这样的端点,只需要所有这些内容即可返回当前经过身份验证用户的个人资料(带有 GET [role="bare"]http://localhost:8080/github/userinfo
),并将其作为各个 `UserInfo`属性进行访问:
This is all that is needed for an endpoint like this one to return the currently-authenticated user’s profile with GET [role="bare"]http://localhost:8080/github/userinfo
and access it as the individual UserInfo
properties:
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import io.quarkus.oidc.UserInfo;
import io.quarkus.security.Authenticated;
@Path("/github")
@Authenticated
public class TokenResource {
@Inject
UserInfo userInfo;
@GET
@Path("/userinfo")
@Produces("application/json")
public String getUserInfo() {
return userInfo.getUserInfoString();
}
}
如果你在 OpenID Connect Multi-Tenancy的帮助下支持多个社交提供程序(例如,Google(一个返回 `IdToken`的 OIDC 提供程序)和 GitHub(一个不返回 `IdToken`并仅允许访问 `UserInfo`的 OAuth2 提供程序),那么你可以让端点使用已注入的 `SecurityIdentity`配合 Google 和 GitHub 流程工作。将在内部生成的 `IdToken`创建主体时,需要对 `SecurityIdentity`进行简单的增强,将主体替换为基于 `UserInfo`的主体(当 GitHub 流程处于活动状态时):
If you support more than one social provider with the help of OpenID Connect Multi-Tenancy, for example, Google, which is an OIDC provider that returns IdToken
, and GitHub, which is an OAuth2 provider that does not return IdToken
and only allows access to UserInfo
, then you can have your endpoint working with only the injected SecurityIdentity
for both Google and GitHub flows.
A simple augmentation of SecurityIdentity
will be required where a principal created with the internally-generated IdToken
will be replaced with the UserInfo
-based principal when the GitHub flow is active:
package io.quarkus.it.keycloak;
import java.security.Principal;
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.UserInfo;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class CustomSecurityIdentityAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
RoutingContext routingContext = identity.getAttribute(RoutingContext.class.getName());
if (routingContext != null && routingContext.normalizedPath().endsWith("/github")) {
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
UserInfo userInfo = identity.getAttribute("userinfo");
builder.setPrincipal(new Principal() {
@Override
public String getName() {
return userInfo.getString("preferred_username");
}
});
identity = builder.build();
}
return Uni.createFrom().item(identity);
}
}
现在,当用户使用 Google 或 GitHub 登录你的应用程序时,以下代码将起作用:
Now, the following code will work when the user signs into your application by using Google or GitHub:
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
@Path("/service")
@Authenticated
public class TokenResource {
@Inject
SecurityIdentity identity;
@GET
@Path("/google")
@Produces("application/json")
public String getUserName() {
return identity.getPrincipal().getName();
}
@GET
@Path("/github")
@Produces("application/json")
public String getUserName() {
return identity.getPrincipal().getUserName();
}
}
一种更简单的替代方法可能是注入 @IdToken JsonWebToken`和 `UserInfo
,并在处理返回 IdToken`的提供程序时使用 `JsonWebToken
,在处理不返回 IdToken`的提供程序时使用 `UserInfo
。
Possibly a simpler alternative is to inject both @IdToken JsonWebToken
and UserInfo
and use JsonWebToken
when handling the providers that return IdToken
and use UserInfo
with the providers that do not return IdToken
.
你必须确保在 GitHub OAuth 应用程序配置中输入的回调路径与希望在成功完成 GitHub 身份验证和应用程序授权之后将用户重定向到的端点路径相匹配。在这种情况下,必须将其设置为 http:localhost:8080/github/userinfo
。
You must ensure that the callback path you enter in the GitHub OAuth application configuration matches the endpoint path where you want the user to be redirected after a successful GitHub authentication and application authorization.
In this case, it has to be set to http:localhost:8080/github/userinfo
.
Listening to important authentication events
你可以注册 `@ApplicationScoped`bean,它将观察重要的 OIDC 身份验证事件。当用户首次登录、重新进行身份验证或刷新会话时,都会更新侦听器。将来可能还会报告更多事件。例如:
You can register the @ApplicationScoped
bean which will observe important OIDC authentication events.
When a user logs in for the first time, re-authenticates, or refreshes the session, the listener is updated.
In the future, more events might be reported.
For example:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import io.quarkus.oidc.IdTokenCredential;
import io.quarkus.oidc.SecurityEvent;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class SecurityEventListener {
public void event(@Observes SecurityEvent event) {
String tenantId = event.getSecurityIdentity().getAttribute("tenant-id");
RoutingContext vertxContext = event.getSecurityIdentity().getAttribute(RoutingContext.class.getName());
vertxContext.put("listener-message", String.format("event:%s,tenantId:%s", event.getEventType().name(), tenantId));
}
}
你可以按照安全提示和技巧指南的 Observe security events部分所述监听其他安全事件。 |
You can listen to other security events as described in the Observe security events section of the Security Tips and Tricks guide. |
Propagating tokens to downstream services
有关将授权代码流访问令牌传播到下游服务的详细信息,请参阅 Token Propagation部分。
For information about Authorization Code Flow access token propagation to downstream services, see the Token Propagation section.
Integration considerations
受 OIDC 保护的应用程序集成到一个环境中,从单页面应用程序中可以调用它。它必须与众所周知的 OIDC 提供程序配合使用,在 HTTP 反向代理后面运行,需要外部和内部访问,等等。
Your application secured by OIDC integrates in an environment where it can be called from single-page applications. It must work with well-known OIDC providers, run behind HTTP Reverse Proxy, require external and internal access, and so on.
本部分讨论这些注意事项。
This section discusses these considerations.
Single-page applications
你可以检查按照“OpenID Connect (OIDC) 持有者令牌认证”指南的 Single-page applications部分中建议的方式实现单页面应用程序 (SPA),是否能满足你的要求。
You can check if implementing single-page applications (SPAs) the way it is suggested in the Single-page applications section of the "OpenID Connect (OIDC) Bearer token authentication" guide meets your requirements.
如果你更喜欢在 Quarkus Web 应用程序中使用 SPA 和 JavaScript API(如 Fetch`或 `XMLHttpRequest
(XHR),请注意 OIDC 提供程序可能不支持对授权端点的跨源资源共享 (CORS),用户在从 Quarkus 进行重定向后在此端点进行身份验证。如果 Quarkus 应用程序和 OIDC 提供程序托管在不同的 HTTP 域、端口或两者上,这将导致身份验证失败。
If you prefer to use SPAs and JavaScript APIs such as Fetch
or XMLHttpRequest
(XHR) with Quarkus web applications, be aware that OIDC providers might not support cross-origin resource sharing (CORS) for authorization endpoints where the users are authenticated after a redirect from Quarkus.
This will lead to authentication failures if the Quarkus application and the OIDC provider are hosted on different HTTP domains, ports, or both.
在这些情况下,将 quarkus.oidc.authentication.java-script-auto-redirect
属性设置为 false
,它将指示 Quarkus 返回 499
状态代码和一个带有 OIDC
值的 WWW-Authenticate
标头。
In such cases, set the quarkus.oidc.authentication.java-script-auto-redirect
property to false
, which will instruct Quarkus to return a 499
status code and a WWW-Authenticate
header with the OIDC
value.
浏览器脚本必须设置标头,以将当前请求标识为针对 499
状态代码的 JavaScript 请求,以在 quarkus.oidc.authentication.java-script-auto-redirect
属性设置为 false
时返回。
The browser script must set a header to identify the current request as a JavaScript request for a 499
status code to be returned when the quarkus.oidc.authentication.java-script-auto-redirect
property is set to false
.
如果脚本引擎自身设置特定于引擎的请求标头,则可以注册自定义 quarkus.oidc.JavaScriptRequestChecker
bean,它会通知 Quarkus 当前请求是否为 JavaScript 请求。例如,如果 JavaScript 引擎设置了 `HX-Request: true`之类的标头,则可以像这样进行检查:
If the script engine sets an engine-specific request header itself, then you can register a custom quarkus.oidc.JavaScriptRequestChecker
bean, which will inform Quarkus if the current request is a JavaScript request. For example, if the JavaScript engine sets a header such as HX-Request: true
, then you can have it checked like this:
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.oidc.JavaScriptRequestChecker;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class CustomJavaScriptRequestChecker implements JavaScriptRequestChecker {
@Override
public boolean isJavaScriptRequest(RoutingContext context) {
return "true".equals(context.request().getHeader("HX-Request"));
}
}
然后,在 499
状态代码时重新加载最后请求的页面。
and reload the last requested page in case of a 499
status code.
否则,还必须更新浏览器脚本,以设置带有 JavaScript
值的 X-Requested-With
标头,并在 499
状态代码时重新加载最后请求的页面。
Otherwise, you must also update the browser script to set the X-Requested-With
header with the JavaScript
value and reload the last requested page in case of a 499
status code.
例如:
For example:
Future<void> callQuarkusService() async {
Map<String, String> headers = Map.fromEntries([MapEntry("X-Requested-With", "JavaScript")]);
await http
.get("https://localhost:443/serviceCall")
.then((response) {
if (response.statusCode == 499) {
window.location.assign("https://localhost.com:443/serviceCall");
}
});
}
Cross-origin resource sharing
如果计划从运行在不同域上的单页面应用程序中使用此应用程序,则需要配置跨域资源共享 (CORS)。有关更多信息,请参阅“跨域资源共享”指南的 CORS filter 部分。
If you plan to consume this application from a single-page application running on a different domain, you need to configure cross-origin resource sharing (CORS). For more information, see the CORS filter section of the "Cross-origin resource sharing" guide.
Calling Cloud provider services
Google Cloud
你可以使用 Quarkus OIDC web-app
应用程序访问 Google Cloud services,例如 BigQuery,代表当前已验证的用户,这些用户已在其 Google 开发者控制台中为这些服务启用了 OIDC 授权码流程权限。
You can have Quarkus OIDC web-app
applications access Google Cloud services such as BigQuery on behalf of the currently authenticated users who have enabled OIDC authorization code flow permissions to such services in their Google developer consoles.
你可以使用 Quarkiverse Google Cloud Services 来执行此操作。你只需要添加 latest tag 服务依赖项,如以下示例所示:
You can do so by using Quarkiverse Google Cloud Services. You need only to add the latest tag service dependency as shown in the following example:
<dependency>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<artifactId>quarkus-google-cloud-bigquery</artifactId>
<version>${quarkiverse.googlecloudservices.version}</version>
</dependency>
implementation("io.quarkiverse.googlecloudservices:quarkus-google-cloud-bigquery:${quarkiverse.googlecloudservices.version}")
然后,配置 Google OIDC 属性:
Then, configure Google OIDC properties:
quarkus.oidc.provider=google
quarkus.oidc.client-id={GOOGLE_CLIENT_ID}
quarkus.oidc.credentials.secret={GOOGLE_CLIENT_SECRET}
quarkus.oidc.token.issuer=https://accounts.google.com
Running Quarkus application behind a reverse proxy
当 Quarkus 应用程序运行在反向代理、网关或防火墙后,OIDC 身份验证机制可能会受到影响,这时 HTTP Host
标头可能会重置为内部 IP 地址,HTTPS 连接可能会被终止,等等。例如,授权码流程 redirect_uri
参数可能会设置为内部主机,而不是预期的外部主机。
The OIDC authentication mechanism can be affected if your Quarkus application is running behind a reverse proxy, gateway, or firewall when HTTP Host
header might be reset to the internal IP address and HTTPS connection might be terminated, and so on.
For example, an authorization code flow redirect_uri
parameter might be set to the internal host instead of the expected external one.
在这种情况下,将 Quarkus 配置为识别由代理转发来的原始标头是必需的。有关更多信息,请参阅 Running behind a reverse proxy Vert.x 文档部分。
In such cases, configuring Quarkus to recognize the original headers forwarded by the proxy will be required. For more information, see the Running behind a reverse proxy Vert.x documentation section.
例如,如果你的 Quarkus 端点在 Kubernetes Ingress 后面的群集中运行,则从 OIDC 提供商重定向到此端点的操作可能无法正常执行,因为计算后的 redirect_uri
参数可能指向内部端点地址。可以使用以下配置解决此问题,其中 X-ORIGINAL-HOST
由 Kubernetes Ingress 设置以表示外部端点地址:
For example, if your Quarkus endpoint runs in a cluster behind Kubernetes Ingress, then a redirect from the OIDC provider back to this endpoint might not work because the calculated redirect_uri
parameter might point to the internal endpoint address.
You can resolve this problem by using the following configuration, where X-ORIGINAL-HOST
is set by Kubernetes Ingress to represent the external endpoint address.:
quarkus.http.proxy.proxy-address-forwarding=true
quarkus.http.proxy.allow-forwarded=false
quarkus.http.proxy.enable-forwarded-host=true
quarkus.http.proxy.forwarded-host-header=X-ORIGINAL-HOST
当 Quarkus 应用程序运行在终止 SSL 的反向代理之后,还可以使用 quarkus.oidc.authentication.force-redirect-https-scheme
属性。
quarkus.oidc.authentication.force-redirect-https-scheme
property can also be used when the Quarkus application is running behind an SSL terminating reverse proxy.
External and internal access to the OIDC provider
与相对于 quarkus.oidc.auth-server-url
内部 URL 自动发现或配置的 URL 相比,OIDC 提供商可从外部访问的授权、注销和其他端点可以具有不同的 HTTP(S) URL。在这些情况下,端点可能会报告发行人验证失败,并且重定向到可从外部访问的 OIDC 提供商端点可能会失败。
The OIDC provider externally-accessible authorization, logout, and other endpoints can have different HTTP(S) URLs compared to the URLs auto-discovered or configured relative to the quarkus.oidc.auth-server-url
internal URL.
In such cases, the endpoint might report an issuer verification failure and redirects to the externally-accessible OIDC provider endpoints might fail.
如果你使用的是 Keycloak,请使用一个 KEYCLOAK_FRONTEND_URL
系统属性(已设置为可从外部访问的基本 URL)来启动它。如果你使用的是其他 OIDC 提供商,请查阅你的提供商的文档。
If you work with Keycloak, then start it with a KEYCLOAK_FRONTEND_URL
system property set to the externally-accessible base URL.
If you work with other OIDC providers, check the documentation of your provider.
OIDC SAML identity broker
如果你的身份提供商不实现 OpenID Connect,而只实现旧版基于 XML 的 SAML2.0 SSO 协议,则 Quarkus 无法用作 SAML 2.0 适配器,这类似于 quarkus-oidc
用作 OIDC 适配器的方式。
If your identity provider does not implement OpenID Connect but only the legacy XML-based SAML2.0 SSO protocol, then Quarkus cannot be used as a SAML 2.0 adapter, similarly to how quarkus-oidc
is used as an OIDC adapter.
但是,很多 OIDC 提供商(如 Keycloak、Okta、Auth0 和 Microsoft ADFS)提供 OIDC 到 SAML 2.0 桥接器。可以在你的 OIDC 提供商中创建到 SAML 2.0 提供商的身份代理连接,并使用 quarkus-oidc
对你的用户向此 SAML 2.0 提供商进行身份验证,让 OIDC 提供商协调 OIDC 和 SAML 2.0 通信。就 Quarkus 端点而言,它们可以继续使用相同的 Quarkus Security、OIDC API、注解(如 @Authenticated
、`SecurityIdentity`等)。
However, many OIDC providers such as Keycloak, Okta, Auth0, and Microsoft ADFS offer OIDC to SAML 2.0 bridges.
You can create an identity broker connection to a SAML 2.0 provider in your OIDC provider and use quarkus-oidc
to authenticate your users to this SAML 2.0 provider, with the OIDC provider coordinating OIDC and SAML 2.0 communications.
As far as Quarkus endpoints are concerned, they can continue using the same Quarkus Security, OIDC API, annotations such as @Authenticated
, SecurityIdentity
, and so on.
例如,假设 Okta
是你的 SAML 2.0 提供商,而 Keycloak
是你的 OIDC 提供商。以下是一个典型的流程,用于说明如何配置 Keycloak
以代理到 Okta
SAML 2.0 提供商。
For example, assume Okta
is your SAML 2.0 provider and Keycloak
is your OIDC provider.
Here is a typical sequence explaining how to configure Keycloak
to broker with the Okta
SAML 2.0 provider.
首先,在你的 Okta
Dashboard/Applications
中创建一个新的 SAML2
集成:
First, create a new SAML2
integration in your Okta
Dashboard/Applications
:
例如,将其命名为 OktaSaml
:
For example, name it as OktaSaml
:
接下来,将其配置为指向 Keycloak SAML 适配器端点。此时,您需要知道 Keycloak 领域名称,例如 quarkus
,并假设 Keycloak SAML 适配器别名为 saml
,则输入端点地址为 http:localhost:8081/realms/quarkus/broker/saml/endpoint
。输入服务提供商 (SP) 实体 ID 为 http:localhost:8081/realms/quarkus
,其中 http://localhost:8081
是 Keycloak 基本地址,saml
是适配器别名:
Next, configure it to point to a Keycloak SAML broker endpoint.
At this point, you need to know the name of the Keycloak realm, for example, quarkus
, and, assuming that the Keycloak SAML broker alias is saml
, enter the endpoint address as http:localhost:8081/realms/quarkus/broker/saml/endpoint
.
Enter the service provider (SP) entity ID as http:localhost:8081/realms/quarkus
, where http://localhost:8081
is a Keycloak base address and saml
is a broker alias:
接下来,保存此 SAML 集成并记下其元数据 URL:
Next, save this SAML integration and note its Metadata URL:
接下来,向 Keycloak 添加一个 SAML 提供商:
Next, add a SAML provider to Keycloak:
首先,像往常一样,创建一个新的领域或将现有领域导入到 Keycloak
。在这种情况下,领域名称必须为 quarkus
。
First, as usual, create a new realm or import the existing realm to Keycloak
.
In this case, the realm name has to be quarkus
.
现在,在 quarkus
领域属性中,导航到 Identity Providers
并添加一个新的 SAML 提供商:
Now, in the quarkus
realm properties, navigate to Identity Providers
and add a new SAML provider:
请注意,别名设置为 saml
,Redirect URI
为 http:localhost:8081/realms/quarkus/broker/saml/endpoint
,Service provider entity ID
为 http:localhost:8081/realms/quarkus
- 这些值与您在前一步创建 Okta SAML 集成时输入的值相同。
Note the alias is set to saml
, Redirect URI
is http:localhost:8081/realms/quarkus/broker/saml/endpoint
and Service provider entity ID
is http:localhost:8081/realms/quarkus
- these are the same values you entered when creating the Okta SAML integration in the previous step.
最后,将 Service entity descriptor
设置为指向您在上一步末尾记下的 Okta SAML 集成元数据 URL。
Finally, set Service entity descriptor
to point to the Okta SAML Integration Metadata URL you noted at the end of the previous step.
接下来,如果您愿意,您可以通过导航至 Authentication/browser/Identity Provider Redirector config
并将 Alias
和 Default Identity Provider
属性都设置为 saml
将此 Keycloak SAML 提供商注册为默认提供商。如果您没有将其配置为默认提供商,则在身份验证时,Keycloak 提供 2 个选项:
Next, if you want, you can register this Keycloak SAML provider as a default provider by navigating to Authentication/browser/Identity Provider Redirector config
and setting both the Alias
and Default Identity Provider
properties to saml
.
If you do not configure it as a default provider then, at authentication time, Keycloak offers 2 options:
-
Authenticate with the SAML provider
-
Authenticate directly to Keycloak with the name and password
现在,将 Quarkus OIDC web-app
应用程序配置为指向 Keycloak quarkus
领域、quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
。然后,您就可以使用由 Keycloak OIDC 和 Okta SAML 2.0 提供程序提供的 OIDC 到 SAML 桥接来开始验证您的 Quarkus 用户到 Okta SAML 2.0 提供程序。
Now, configure the Quarkus OIDC web-app
application to point to the Keycloak quarkus
realm, quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
.
Then, you are ready to start authenticating your Quarkus users to the Okta SAML 2.0 provider by using an OIDC to SAML bridge that is provided by Keycloak OIDC and Okta SAML 2.0 providers.
您可以配置其他 OIDC 提供商来提供 SAML 桥接,方法与对 Keycloak 所做的一样。
You can configure other OIDC providers to provide a SAML bridge similarly to how it can be done for Keycloak.
Testing
当对一个单独的类似 OIDC 的服务器进行身份验证时,测试常常很棘手。Quarkus 提供从模拟到局部运行 OIDC 提供程序的若干选项。
Testing is often tricky when it comes to authentication to a separate OIDC-like server. Quarkus offers several options from mocking to a local run of an OIDC provider.
首先将以下依赖项添加到测试项目:
Start by adding the following dependencies to your test project:
<dependency>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
testImplementation("org.htmlunit:htmlunit")
testImplementation("io.quarkus:quarkus-junit5")
Wiremock
添加以下依赖项:
Add the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-oidc-server</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-test-oidc-server")
准备 REST 测试端点并设置 application.properties
。例如:
Prepare the REST test endpoints and set application.properties
.
For example:
# keycloak.url is set by OidcWiremockTestResource
quarkus.oidc.auth-server-url=${keycloak.url:replaced-by-test-resource}/realms/quarkus/
quarkus.oidc.client-id=quarkus-web-app
quarkus.oidc.credentials.secret=secret
quarkus.oidc.application-type=web-app
最后,编写测试代码,例如:
Finally, write the test code, for example:
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.htmlunit.SilentCssErrorHandler;
import org.htmlunit.WebClient;
import org.htmlunit.html.HtmlForm;
import org.htmlunit.html.HtmlPage;
import io.quarkus.test.common.WithTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.oidc.server.OidcWiremockTestResource;
@QuarkusTest
@WithTestResource(OidcWiremockTestResource.class)
public class CodeFlowAuthorizationTest {
@Test
public void testCodeFlow() throws Exception {
try (final WebClient webClient = createWebClient()) {
// the test REST endpoint listens on '/code-flow'
HtmlPage page = webClient.getPage("http://localhost:8081/code-flow");
HtmlForm form = page.getFormByName("form");
// user 'alice' has the 'user' role
form.getInputByName("username").type("alice");
form.getInputByName("password").type("alice");
page = form.getInputByValue("login").click();
assertEquals("alice", page.getBody().asText());
}
}
private WebClient createWebClient() {
WebClient webClient = new WebClient();
webClient.setCssErrorHandler(new SilentCssErrorHandler());
return webClient;
}
}
OidcWiremockTestResource
识别 alice
和 admin
用户。用户 alice
仅默认具有 user
角色 - 它可以通过 quarkus.test.oidc.token.user-roles
系统属性进行自定义。用户 admin
默认具有 user
和 admin
角色 - 它可以通过 quarkus.test.oidc.token.admin-roles
系统属性进行自定义。
OidcWiremockTestResource
recognizes alice
and admin
users.
The user alice
has the user
role only by default - it can be customized with a quarkus.test.oidc.token.user-roles
system property.
The user admin
has the user
and admin
roles by default - it can be customized with a quarkus.test.oidc.token.admin-roles
system property.
此外,OidcWiremockTestResource
将令牌颁发者和受众设置为 https://service.example.com
,可以通过 quarkus.test.oidc.token.issuer
和 quarkus.test.oidc.token.audience
系统属性进行自定义。
Additionally, OidcWiremockTestResource
sets the token issuer and audience to https://service.example.com
, which can be customized with quarkus.test.oidc.token.issuer
and quarkus.test.oidc.token.audience
system properties.
OidcWiremockTestResource
可用于模拟所有 OIDC 提供商。
OidcWiremockTestResource
can be used to emulate all OIDC providers.
Dev Services for Keycloak
推荐使用 Dev Services for Keycloak 对照 Keycloak 进行集成测试。Dev Services for Keycloak
将启动并初始化一个测试容器:它将创建一个 quarkus
域,一个 quarkus-app
客户端(secret
密钥),并添加 alice
(admin
和 user
角色)和 bob
(user
角色)用户,其中所有这些属性都可以自定义。
Using Dev Services for Keycloak is recommended for integration testing against Keycloak.
Dev Services for Keycloak
will start and initialize a test container: it will create a quarkus
realm, a quarkus-app
client (secret
secret), and add alice
(admin
and user
roles) and bob
(user
role) users, where all of these properties can be customized.
首先,准备 application.properties
。你可以从一个完全空的 application.properties
文件开始,因为 Dev Services for Keycloak
也会注册 quarkus.oidc.auth-server-url
,指向正在运行的测试容器以及 quarkus.oidc.client-id=quarkus-app
和 quarkus.oidc.credentials.secret=secret
。
First, prepare application.properties
.
You can start with a completely empty application.properties
file as Dev Services for Keycloak
will register quarkus.oidc.auth-server-url
pointing to the running test container as well as quarkus.oidc.client-id=quarkus-app
and quarkus.oidc.credentials.secret=secret
.
但是,如果你已经配置了所有必需的 quarkus-oidc
属性,那么你只需要将 quarkus.oidc.auth-server-url
与 prod
配置文件关联即可,以便 Dev Services for Keycloak
启动一个容器。例如:
However, if you already have all the required quarkus-oidc
properties configured, then you only need to associate quarkus.oidc.auth-server-url
with the prod
profile for Dev Services for Keycloak
to start a container.
For example:
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
如果在运行测试之前必须将自定义域文件导入到 Keycloak,则可以将 Dev Services for Keycloak
配置如下:
If a custom realm file has to be imported into Keycloak before running the tests, then you can configure Dev Services for Keycloak
as follows:
%prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus
quarkus.keycloak.devservices.realm-path=quarkus-realm.json
最后,按照 Wiremock 部分中描述的方式编写测试代码。唯一的区别是 @WithTestResource
不再需要了:
Finally, write a test code the same way as it is described in the code-flow-integration-testing-wiremock section.
The only difference is that @WithTestResource
is no longer needed:
@QuarkusTest
public class CodeFlowAuthorizationTest {
}
Using KeycloakTestResourceLifecycleManager
仅在有充分理由不使用 Dev Services for Keycloak
时才在测试中使用 KeycloakTestResourceLifecycleManager
。如果你需要对照 Keycloak 执行集成测试,那么建议你使用 Dev Services for Keycloak 执行此操作。
Use KeycloakTestResourceLifecycleManager
for your tests only if there is a good reason not to use Dev Services for Keycloak
.
If you need to do the integration testing against Keycloak then you are encouraged to do it with code-flow-integration-testing-keycloak-devservices.
首先,添加以下依赖关系:
First, add the following dependency:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-keycloak-server</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-test-keycloak-server")
这提供了 io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager
,即 io.quarkus.test.common.QuarkusTestResourceLifecycleManager
的一个实现,它会启动一个 Keycloak 容器。
This provides io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager
- an implementation of io.quarkus.test.common.QuarkusTestResourceLifecycleManager
which starts a Keycloak container.
然后,像在 Wiremock 部分中所述那样配置 Maven Surefire 插件(在以本机映像进行测试时类似地配置 Maven Failsafe 插件):
Then, configure the Maven Surefire plugin as follows (and similarly the Maven Failsafe plugin when testing in native image):
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<!-- or, alternatively, configure 'keycloak.version' -->
<keycloak.docker.image>${keycloak.docker.image}</keycloak.docker.image>
<!--
Disable HTTPS if required:
<keycloak.use.https>false</keycloak.use.https>
-->
</systemPropertyVariables>
</configuration>
</plugin>
现在,设置配置并以 Wiremock 部分中所述的方式编写测试代码。唯一的区别是 WithTestResource
的名称:
Now, set the configuration and write the test code the same way as it is described in the code-flow-integration-testing-wiremock section.
The only difference is the name of WithTestResource
:
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
@QuarkusTest
@WithTestResource(KeycloakTestResourceLifecycleManager.class)
public class CodeFlowAuthorizationTest {
}
KeycloakTestResourceLifecycleManager
注册了 alice
和 admin
用户。默认情况下,用户 alice
仅具有 user
角色,可以使用 keycloak.token.user-roles
系统属性进行自定义。用户 admin
默认具有 user
和 admin
角色,可以使用 keycloak.token.admin-roles
系统属性进行自定义。
KeycloakTestResourceLifecycleManager
registers alice
and admin
users.
The user alice
has the user
role only by default - it can be customized with a keycloak.token.user-roles
system property.
The user admin
has the user
and admin
roles by default - it can be customized with a keycloak.token.admin-roles
system property.
默认情况下,KeycloakTestResourceLifecycleManager
使用 HTTPS 来初始化一个 Keycloak 实例,可以通过指定 keycloak.use.https=false
来禁用 HTTPS。默认域名称为 quarkus
,客户端 ID 为 quarkus-web-app
,如果需要,请设置 keycloak.realm
和 keycloak.web-app.client
系统属性以自定义这些值。
By default, KeycloakTestResourceLifecycleManager
uses HTTPS to initialize a Keycloak instance that can be disabled by specifying keycloak.use.https=false
.
The default realm name is quarkus
and client id is quarkus-web-app
- set keycloak.realm
and keycloak.web-app.client
system properties to customize the values if needed.
TestSecurity annotation
你可以使用 @TestSecurity 和 @OidcSecurity 注解来测试 web-app
应用程序端点代码,该代码依赖于以下依赖项之一或全部四项:
You can use @TestSecurity and @OidcSecurity annotations to test the web-app
application endpoint code, which depends on either one of the following injections, or all four:
-
ID
JsonWebToken
-
Access
JsonWebToken
-
UserInfo
-
OidcConfigurationMetadata
For more information, see Use TestingSecurity with injected JsonWebToken.
Checking errors in the logs
要查看令牌验证错误的详细信息,你必须启用 io.quarkus.oidc.runtime.OidcProvider
TRACE
级别日志记录:
To see details about the token verification errors, you must enable io.quarkus.oidc.runtime.OidcProvider
TRACE
level logging:
quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".min-level=TRACE
要查看 OidcProvider 客户端初始化错误的详细信息,请启用 io.quarkus.oidc.runtime.OidcRecorder
TRACE
级别日志记录:
To see details about the OidcProvider client initialization errors, enable io.quarkus.oidc.runtime.OidcRecorder
TRACE
level logging:
quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".level=TRACE
quarkus.log.category."io.quarkus.oidc.runtime.OidcRecorder".min-level=TRACE
从 quarkus dev
控制台键入 j
以更改应用程序全局日志级别。
From the quarkus dev
console, type j
to change the application global log level.