Security Tips and Tricks
Quarkus Security依存関係
io.quarkus:quarkus-security
モジュールには、Quarkusの中核となるセキュリティクラスが含まれています。
In most cases, it does not have to be added directly to your project’s build file as it is already provided by all the security extensions. However, if you need to write your own custom security code (for example, register a Custom JAX-RS SecurityContext) or use BouncyCastle libraries, then please make sure it is included:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
implementation("io.quarkus:quarkus-security")
HttpAuthenticationMechanism のカスタマイズ
CDI実装Beanを登録することで、 HttpAuthenticationMechanism
をカスタマイズすることができます。以下の例では、カスタム認証機能は quarkus-smallrye-jwt
によって提供される JWTAuthMechanism
にデリゲートします。
@Alternative
@Priority(1)
@ApplicationScoped
public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism {
private static final Logger LOG = LoggerFactory.getLogger(CustomAwareJWTAuthMechanism.class);
@Inject
JWTAuthMechanism delegate;
@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
// do some custom action and delegate
return delegate.authenticate(context, identityProviderManager);
}
@Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return delegate.getChallenge(context);
}
@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return delegate.getCredentialTypes();
}
@Override
public Uni<HttpCredentialTransport> getCredentialTransport() {
return delegate.getCredentialTransport();
}
}
Dealing with more than one HttpAuthenticationMechanism
More than one HttpAuthenticationMechanism
can be combined, for example, the built-in Basic
or JWT
mechanism provided by quarkus-smallrye-jwt
has to be used to verify the service clients credentials passed as the HTTP Authorization
Basic
or Bearer
scheme values while the Authorization Code
mechanism provided by quarkus-oidc
has to be used to authenticate the users with Keycloak or other OpenID Connect providers.
In such cases the mechanisms are asked to verify the credentials in turn until a SecurityIdentity
is created. The mechanisms are sorted in the descending order using their priority. Basic
authentication mechanism has the highest priority of 2000
, followed by the Authorization Code
one with the priority of 1001
, with all other mechanisms provided by Quarkus having the priority of 1000
.
If no credentials are provided then the mechanism specific challenge is created, for example, 401
status is returned by either Basic
or JWT
mechanisms, URL redirecting the user to the OpenID Connect provider is returned by quarkus-oidc
, etc.
So if Basic
and Authorization Code
mechanisms are combined then 401
will be returned if no credentials are provided and if JWT
and Authorization Code
mechanisms are combined then a redirect URL will be returned.
In some cases such a default logic of selecting the challenge is exactly what is required by a given application, but sometimes it may not meet the requirements. In such cases (or indeed in other similar cases where you’d like to change the order in which the mechanisms are asked to handle the current authentication or challenge request), you can create a custom mechanism and choose which mechanism should create a challenge, for example:
@ApplicationScoped
public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism {
private static final Logger LOG = LoggerFactory.getLogger(CustomAwareJWTAuthMechanism.class);
@Inject
JWTAuthMechanism jwt;
@Inject
OidcAuthenticationMechanism oidc;
@Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
return selectBetweenJwtAndOidc(context).authenticate(context, identityProviderManager);
}
@Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return selectBetweenJwtAndOidcChallenge(context).getChallenge(context);
}
@Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return selectBetweenJwtAndOidc(context).getCredentialTypes();
}
@Override
public HttpCredentialTransport getCredentialTransport(RoutingContext context) {
return selectBetweenJwtAndOidc(context).getCredentialTransport();
}
private HttpAuthenticationMechanism selectBetweenJwtAndOidc(RoutingContext context) {
....
}
private HttpAuthenticationMechanism selectBetweenJwtAndOidcChallenge(RoutingContext context) {
// for example, if no `Authorization` header is available and no `code` parameter is provided - use `jwt` to create a challenge
}
}
セキュリティー・アイデンティティのカスタマイズ
内部的には、アイデンティティ・プロバイダは、プリンシパル、役割、クライアント(ユーザー)の認証に使用されたクレデンシャル、 およびその他のセキュリティー属性を保持する io.quarkus.security.identity.SecurityIdentity
クラスのインスタンスを作成して更新します。 SecurityIdentity
をカスタマイズする簡単なオプションは、カスタム SecurityIdentityAugmentor
を登録することです。例えば、以下のエクステンションは、追加のロールを追加します。
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 javax.enterprise.context.ApplicationScoped;
import java.util.function.Supplier;
@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
return Uni.createFrom().item(build(identity));
// Do 'return context.runBlocking(build(identity));'
// if a blocking call is required to customize the identity
}
private Supplier<SecurityIdentity> build(SecurityIdentity identity) {
if(identity.isAnonymous()) {
return () -> identity;
} else {
// create a new builder and copy principal, attributes, credentials and roles from the original identity
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
// add custom role source here
builder.addRole("dummy");
return builder::build;
}
}
}
Here is another example showing how to use the client certificate available in the current Mutual TLS request to add more roles:
import java.security.cert.X509Certificate;
import io.quarkus.security.credential.CertificateCredential;
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 javax.enterprise.context.ApplicationScoped;
import java.util.function.Supplier;
import java.util.Set;
@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
return Uni.createFrom().item(build(identity));
}
private Supplier<SecurityIdentity> build(SecurityIdentity identity) {
// create a new builder and copy principal, attributes, credentials and roles from the original identity
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
CertificateCredential certificate = identity.getCredential(CertificateCredential.class);
if (certificate != null) {
builder.addRoles(extractRoles(certificate.getCertificate()));
}
return builder::build;
}
private Set<String> extractRoles(X509Certificate certificate) {
String name = certificate.getSubjectX500Principal().getName();
switch (name) {
case "CN=client":
return Collections.singleton("user");
case "CN=guest-client":
return Collections.singleton("guest");
default:
return Collections.emptySet();
}
}
}
複数のカスタム |
By default, the request context is not activated when augmenting the security identity, this means that if you want to use for example Hibernate that mandates a request context, you will have a javax.enterprise.context.ContextNotActiveException
.
The solution is to activate the request context, the following example shows how to get the roles from an Hibernate with Panache UserRoleEntity
.
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 javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.control.ActivateRequestContext;
import java.util.function.Supplier;
@ApplicationScoped
public class RolesAugmentor implements SecurityIdentityAugmentor {
@Override
public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
if(identity.isAnonymous()) {
return Uni.createFrom().item(identity);
}
// Hibernate ORM is blocking
return context.runBlocking(build(identity));
}
@ActivateRequestContext // Will activate the request context
Supplier<SecurityIdentity> build(SecurityIdentity identity) {
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);
String user = identity.getPrincipal().getName();
UserRoleEntity.<userRoleEntity>streamAll()
.filter(role -> user.equals(role.user))
.forEach(role -> builder.addRole(role.role));
return builder::build;
}
}
カスタム JAX-RS SecurityContext
JAX-RS ContainerRequestFilter
を使用してカスタム JAX-RS SecurityContext
を設定する場合は、 ContainerRequestFilter
が JAX-RS のプレマッチフェーズで実行されていることを確認し、このカスタムセキュリティーコンテキストが Quarkus SecurityIdentity
とリンクされるように @PreMatching
アノテーションを追加します。例:
import java.security.Principal;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
@Provider
@PreMatching
public class SecurityOverrideFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String user = requestContext.getHeaders().getFirst("User");
String role = requestContext.getHeaders().getFirst("Role");
if (user != null && role != null) {
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return user;
}
};
}
@Override
public boolean isUserInRole(String r) {
return role.equals(r);
}
@Override
public boolean isSecure() {
return false;
}
@Override
public String getAuthenticationScheme() {
return "basic";
}
});
}
}
}
認可の無効化
If you have a good reason to disable the authorization then you can register a custom AuthorizationController
:
@Alternative
@Priority(Interceptor.Priority.LIBRARY_AFTER)
@ApplicationScoped
public class DisabledAuthController extends AuthorizationController {
@ConfigProperty(name = "disable.authorization", defaultValue = "false")
boolean disableAuthorization;
@Override
public boolean isAuthorizationEnabled() {
return !disableAuthorization;
}
}
For manual testing Quarkus provides a convenient config property to disable authorization in dev mode. This property has the exact same effect as the custom AuthorizationController
shown above, but is only available in dev mode:
quarkus.security.auth.enabled-in-dev-mode=false
Please also see TestingSecurity Annotation section on how to disable the security checks using TestSecurity
annotation.
セキュリティー プロバイダの登録
デフォルトのプロバイダ
ネイティブモードで実行している場合、GraalVMネイティブ実行可能ファイル生成のデフォルトの動作は、SSLを有効にしていない限り、メインの「SUN」プロバイダのみを含めるようになっています。SSLを有効にしている場合は、すべてのセキュリティープロバイダが登録されます。SSLを使用していない場合は、 quarkus.security.security-providers
プロパティーを使用して、セキュリティープロバイダを名前で選択的に登録することができます。以下の例では、「SunRsaSign」と「SunJCE」のセキュリティープロバイダを登録するための設定を説明します。
quarkus.security.security-providers=SunRsaSign,SunJCE
BouncyCastle
org.bouncycastle.jce.provider.BouncyCastleProvider
JCE プロバイダを登録する必要がある場合は、 BC
プロバイダ名を設定してください。
quarkus.security.security-providers=BC
そして、BouncyCastleプロバイダ依存関係を追加します。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
implementation("org.bouncycastle:bcprov-jdk15on")
BouncyCastle JSSE
org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
JSSE プロバイダを登録して、デフォルトの SunJSSE プロバイダの代わりに使用する必要がある場合は、 BCJSSE
プロバイダ名を設定してください。
quarkus.security.security-providers=BCJSSE
quarkus.http.ssl.client-auth=REQUIRED
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=password
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=password
そして、BouncyCastleのTLS依存関係を追加します。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk15on</artifactId>
</dependency>
implementation("org.bouncycastle:bctls-jdk15on")
BouncyCastle FIPS
org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
JCE プロバイダを登録する必要がある場合は、 BCFIPS
プロバイダ名を設定してください。
quarkus.security.security-providers=BCFIPS
そして、BouncyCastle FIPSプロバイダの依存関係を追加します。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
</dependency>
implementation("org.bouncycastle:bc-fips")
|
BouncyCastle JSSE FIPS
デフォルトのSunJSSEプロバイダではなく、 org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
JSSEプロバイダを登録して、 org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
と組み合わせて使用する必要がある場合は、 BCFIPSJSSE
プロバイダ名を設定してください。
quarkus.security.security-providers=BCFIPSJSSE
quarkus.http.ssl.client-auth=REQUIRED
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=password
quarkus.http.ssl.certificate.key-store-file-type=BCFKS
quarkus.http.ssl.certificate.key-store-provider=BCFIPS
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=password
quarkus.http.ssl.certificate.trust-store-file-type=BCFKS
quarkus.http.ssl.certificate.trust-store-provider=BCFIPS
そして、BouncyCastle FIPSプロバイダを使用するために最適化されたBouncyCastle TLS依存関係:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-fips</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
</dependency>
implementation("org.bouncycastle:bctls-fips")
implementation("org.bouncycastle:bc-fips")
keystore と truststore のタイプとプロバイダが BCFKS
と BCFIPS
に設定されていることに注意してください。このタイプとプロバイダでは、以下のようにキーストアを生成することができます。
keytool -genkey -alias server -keyalg RSA -keystore server-keystore.jks -keysize 2048 -keypass password -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath $PATH_TO_BC_FIPS_JAR -storetype BCFKS
|
SunPKCS11
SunPKCS11
provider provides a bridge to specific PKCS#11
implementations such as cryptographic smartcards and other Hardware Security Modules, Network Security Services in FIPS mode, etc.
Typically, in order to work with SunPKCS11
, one needs to install a PKCS#11
implementation, generate a configuration which usually refers to a shared library, token slot, etc and write the following Java code:
import java.security.Provider;
import java.security.Security;
String configuration = "pkcs11.cfg"
Provider sunPkcs11 = Security.getProvider("SunPKCS11");
Provider pkcsImplementation = sunPkcs11.configure(configuration);
// or prepare configuration in the code or read it from the file such as "pkcs11.cfg" and do
// sunPkcs11.configure("--" + configuration);
Security.addProvider(pkcsImplementation);
In Quarkus you can achieve the same at the configuration level only without having to modify the code, for example:
quarkus.security.security-providers=SunPKCS11
quarkus.security.security-provider-config.SunPKCS11=pkcs11.cfg
Note that while accessing the |
Reactive Security
リアクティブな環境でセキュリティーを使用する場合は、SmallRye Context Propagationが必要になるでしょう。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-context-propagation</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-context-propagation")
This will allow you to propagate the identity throughout the reactive callbacks. You also need to make sure you are using an executor that is capable of propagating the identity (e.g. no CompletableFuture.supplyAsync
), to make sure that Quarkus can propagate it. For more information see the Context Propagation Guide.