diff --git a/docs/src/main/asciidoc/security-authorization.adoc b/docs/src/main/asciidoc/security-authorization.adoc new file mode 100644 index 00000000000000..9c4d34cffeb452 --- /dev/null +++ b/docs/src/main/asciidoc/security-authorization.adoc @@ -0,0 +1,210 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Authorization of Web Endpoints + +include::./attributes.adoc[] + +Quarkus has an integrated pluggable web security layer. If security is enabled all HTTP requests will have a permission +check performed to make sure they are allowed to continue. + +NOTE: Configuration authorization checks are executed before any annotation-based authorization check is done, so both +checks have to pass for a request to be allowed. + +== Authorization using Configuration + +The default implementation allows you to define permissions using config in `application.properties`. An example +config is shown below: + +[source,properties] +---- +quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin <1> + +quarkus.http.auth.permission.roles1.paths=/roles-secured/*,/other/*,/api/* <2> +quarkus.http.auth.permission.roles1.policy=role-policy1 + +quarkus.http.auth.permission.permit1.paths=/public/* <3> +quarkus.http.auth.permission.permit1.policy=permit +quarkus.http.auth.permission.permit1.methods=GET + +quarkus.http.auth.permission.deny1.paths=/forbidden <4> +quarkus.http.auth.permission.deny1.policy=deny +---- +<1> This defines a role based policy that allows users with the `user` and `admin` roles. This is referenced by later rules +<2> This is a permission set that references the previously defined policy. `roles1` is an arbitrary name, you can call the permission sets whatever you want. +<3> This permission references the default `permit` built in policy to allow `GET` methods to `/public`. This is actually a no-op in this example, as this request would have been allowed anyway. +<4> This permission references the built in `deny` build in policy `/forbidden`. This is an exact path match as it does not end with *. + +Permissions are defined in config using permission sets. These are arbitrarily named permission grouping. Each permission +set must specify a policy that is used to control access. There are three built in policies: `deny`, `permit` and `authenticated`, +which permit all, deny all and only allow authenticated users respectively. + +It is also possible to define role based policies, as shown in the example. These policies will only allow users with the +specified roles to access the resources. + +=== Matching on paths, methods + +Permission sets can also specify paths and methods as a comma separated list. If a path ends with '*' then it is considered +to be a wildcard match and will match all sub paths, otherwise it is an exact match and will only match that specific path: + +[source,properties] +---- +quarkus.http.auth.permission.permit1.paths=/public/*,/css/*,/js/*,/robots.txt +quarkus.http.auth.permission.permit1.policy=permit +quarkus.http.auth.permission.permit1.methods=GET,HEAD +---- + +=== Matching path but not method + +If a request would match one or more permission sets based on the path, but does not match any due to method requirements +then the request is rejected. + +TIP: Given the above permission set, `GET /public/foo` would match both the path and method and thus be allowed, +whereas `POST /public/foo` would match the path but not the method and would thus be rejected. + +=== Matching multiple paths: longest path wins + +Matching is always done on a longest path basis, less specific permission sets are not considered if a more specific one +has been matched: + +[source,properties] +---- +quarkus.http.auth.permission.permit1.paths=/public/* +quarkus.http.auth.permission.permit1.policy=permit +quarkus.http.auth.permission.permit1.methods=GET,HEAD + +quarkus.http.auth.permission.deny1.paths=/public/forbidden-folder/* +quarkus.http.auth.permission.deny1.policy=deny +---- + +TIP: Given the above permission set, `GET /public/forbidden-folder/foo` would match both permission sets' paths, +but because it matches the `deny1` permission set's path on a longer match, `deny1` will be chosen and the request will +be rejected. + +[NOTE] Subpath permission always win against the root path permission as explained above in the `deny1` versus `permit1` permission example. +Here is another example showing a subpath permission allowing a public resource access with the root path permission requiring the authorization: + +[source,properties] +---- +quarkus.http.auth.policy.user-policy.roles-allowed=user +quarkus.http.auth.permission.roles.paths=/api/* +quarkus.http.auth.permission.roles.policy=user-policy + +quarkus.http.auth.permission.public.paths=/api/noauth/* +quarkus.http.auth.permission.public.policy=permit +---- + +=== Matching multiple paths: most specific method wins + +If a path is registered with multiple permission sets then any permission sets that specify a HTTP method will take +precedence and permissions sets without a method will not be considered (assuming of course the method matches). In this +instance, the permission sets without methods will only come into effect if the request method does not match any of the +sets with method permissions. + +[source,properties] +---- +quarkus.http.auth.permission.permit1.paths=/public/* +quarkus.http.auth.permission.permit1.policy=permit +quarkus.http.auth.permission.permit1.methods=GET,HEAD + +quarkus.http.auth.permission.deny1.paths=/public/* +quarkus.http.auth.permission.deny1.policy=deny +---- + +TIP: Given the above permission set, `GET /public/foo` would match both permission sets' paths, +but because it matches the `permit1` permission set's explicit method, `permit1` will be chosen and the request will +be accepted. `PUT /public/foo` on the other hand, will not match the method permissions of `permit1` and so +`deny1` will be activated and reject the request. + +=== Matching multiple paths and methods: both win + +If multiple permission sets specify the same path and method (or multiple have no method) then both permissions have to +allow access for the request to proceed. Note that for this to happen both have to either have specified the method, or +have no method, method specific matches take precedence as stated above: + +[source,properties] +---- +quarkus.http.auth.policy.user-policy1.roles-allowed=user +quarkus.http.auth.policy.admin-policy1.roles-allowed=admin + +quarkus.http.auth.permission.roles1.paths=/api/*,/restricted/* +quarkus.http.auth.permission.roles1.policy=user-policy1 + +quarkus.http.auth.permission.roles2.paths=/api/*,/admin/* +quarkus.http.auth.permission.roles2.policy=admin-policy1 +---- + +TIP: Given the above permission set, `GET /api/foo` would match both permission sets' paths, +so would require both the `user` and `admin` roles. + +=== Configuration Properties to Deny access + +There are two configuration settings that alter the RBAC Deny behavior: + +- `quarkus.security.jaxrs.deny-unannotated-endpoints=true|false` - if set to true, the access will be denied for all JAX-RS endpoints by default. +That is if the security annotations do not define the access control. Defaults to `false` +- `quarkus.security.deny-unannotated-members=true|false` - if set to true, the access will be denied to all CDI methods +and JAX-RS endpoints that do not have security annotations but are defined in classes that contain methods with +security annotations. Defaults to `false`. + +[#standard-security-annotations] +== Authorization using Annotations + +Quarkus comes with built-in security to allow for Role-Based Access Control (link:https://en.wikipedia.org/wiki/Role-based_access_control[RBAC]) +based on the common security annotations `@RolesAllowed`, `@DenyAll`, `@PermitAll` on REST endpoints and CDI beans. +An example of an endpoint that makes use of both JAX-RS and Common Security annotations to describe and secure its endpoints is given in <>. Quarkus also provides +the `io.quarkus.security.Authenticated` annotation that will permit any authenticated user to access the resource +(equivalent to `@RolesAllowed("**")`). + +[#subject-example] +.SubjectExposingResource Example +[source,java] +---- +import java.security.Principal; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.SecurityContext; + +@Path("subject") +public class SubjectExposingResource { + + @GET + @Path("secured") + @RolesAllowed("Tester") <1> + public String getSubjectSecured(@Context SecurityContext sec) { + Principal user = sec.getUserPrincipal(); <2> + String name = user != null ? user.getName() : "anonymous"; + return name; + } + + @GET + @Path("unsecured") + @PermitAll<3> + public String getSubjectUnsecured(@Context SecurityContext sec) { + Principal user = sec.getUserPrincipal(); <4> + String name = user != null ? user.getName() : "anonymous"; + return name; + } + + @GET + @Path("denied") + @DenyAll<5> + public String getSubjectDenied(@Context SecurityContext sec) { + Principal user = sec.getUserPrincipal(); + String name = user != null ? user.getName() : "anonymous"; + return name; + } +} +---- +<1> This `/subject/secured` endpoint requires an authenticated user that has been granted the role "Tester" through the use of the `@RolesAllowed("Tester")` annotation. +<2> The endpoint obtains the user principal from the JAX-RS SecurityContext. This will be non-null for a secured endpoint. +<3> The `/subject/unsecured` endpoint allows for unauthenticated access by specifying the `@PermitAll` annotation. +<4> This call to obtain the user principal will return null if the caller is unauthenticated, non-null if the caller is authenticated. +<5> The `/subject/denied` endpoint disallows any access regardless of whether the call is authenticated by specifying the `@DenyAll` annotation. diff --git a/docs/src/main/asciidoc/security-built-in-authentication.adoc b/docs/src/main/asciidoc/security-built-in-authentication.adoc new file mode 100644 index 00000000000000..798651e38563ee --- /dev/null +++ b/docs/src/main/asciidoc/security-built-in-authentication.adoc @@ -0,0 +1,105 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - BuiltIn Authentication Support + +include::./attributes.adoc[] + +This document describes the Quarkus built in authentication mechanisms for HTTP based FORM, BASIC and Mutual TLS authentication as well as the proactive authentication. + +[[basic-auth]] +== Basic Authentication + +To enable basic authentication set `quarkus.http.auth.basic=true`. You must also have at least one extension installed +that provides a username/password based `IdentityProvider`, such as link:security-jdbc[Elytron JDBC]. + +Please see link:security#identity-providers[Security Identity Providers] for more information. + +[[form-auth]] +== Form Based Authentication + +Quarkus provides form based authentication that works in a similar manner to traditional Servlet form based auth. Unlike +traditional form authentication the authenticated user is not stored in an HTTP session, as Quarkus does not provide +clustered HTTP session support. Instead the authentication information is stored in an encrypted cookie, which can +be read by all members of the cluster (provided they all share the same encryption key). + +The encryption key can be set using the `quarkus.http.auth.session.encryption-key` property, and it must be at least 16 characters +long. This key is hashed using SHA-256 and the resulting digest is used as a key for AES-256 encryption of the cookie +value. This cookie contains a expiry time as part of the encrypted value, so all nodes in the cluster must have their +clocks synchronised. At one minute intervals a new cookie will be generated with an updated expiry time if the session +is in use. + +The following properties can be used to configure form based auth: + +include::{generated-dir}/config/quarkus-vertx-http-config-group-form-auth-config.adoc[opts=optional, leveloffset=+1] + +[[mutual-tls]] +== Mutual TLS Authentication + +Quarkus provides mTLS authentication so that you can authenticate users based on their X.509 certificates. + +To use this authentication method, you should first enable SSL to your application. For more details, check the link:http-reference[Supporting secure connections with SSL] guide. + +Once your application is accepting secure connections, the next step is to configure a `quarkus.http.ssl.certificate.trust-store-file` +holding all the certificates that your application should trust as well as how your application should ask for certificates when +a client (e.g.: browser or another service) tries to access one of its protected resources. + +[source,properties] +---- +quarkus.http.ssl.certificate.key-store-file=server-keystore.jks <1> +quarkus.http.ssl.certificate.key-store-password=the_key_store_secret +quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks <2> +quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret +quarkus.http.ssl.client-auth=required <3> + +quarkus.http.auth.permission.default.paths=/* <4> +quarkus.http.auth.permission.default.policy=authenticated +---- +<1> Configures a key store where the server's private key is located. + +<2> Configures a trust store from where the trusted certificates are going to be loaded from. + +<3> Defines that the server should *always* ask certificates from clients. You can relax this behavior by using `REQUEST` so +that the server should still accept requests without a certificate. Useful when you are also supporting authentication methods other than +mTLS. + +<4> Defines a policy where only authenticated users should have access to resources from your application. + +Once the incoming request matches a valid certificate in the truststore, your application should be able to obtain the subject by +just injecting a `SecurityIdentity` as follows: + +[#x509-subject-example] +.Obtaining the subject +[source,java] +---- +@Inject +SecurityIdentity identity; + +@GET +@Produces(MediaType.TEXT_PLAIN) +public String hello() { + return String.format("Hello, %s", identity.getPrincipal().getName()); +} +---- + +You should also be able to get the certificate as follows: + +[#x509-credential-example] +.Obtaining the certificate +[source,java] +---- +CertificateCredential credential = identity.getCredential(CertificateCredential.class); +X509Certificate certificate = credential.getCertificate(); +---- + +[[proactive-authentication]] +== Proactive Authentication + +By default Quarkus does what we call proactive authentication. This means that if an incoming request has a +credential then that request will always be authenticated (even if the target page does not require authentication). + +This means that requests with an invalid credential will always be rejected, even for public pages. You can change +this behaviour and only authenticate when required by setting `quarkus.http.auth.proactive=false`. + diff --git a/docs/src/main/asciidoc/security-customization.adoc b/docs/src/main/asciidoc/security-customization.adoc new file mode 100644 index 00000000000000..a83d69ce91b92a --- /dev/null +++ b/docs/src/main/asciidoc/security-customization.adoc @@ -0,0 +1,202 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Security Tips and Tricks + +include::./attributes.adoc[] + +== HttpAuthenticationMechanism Customization + +One can customize `HttpAuthenticationMechanism` by registering a CDI implementation bean. +In the example below the custom authenticator delegates to `JWTAuthMechanism` provided by `quarkus-smallrye-jwt`: + +[source,java] +---- +@Alternative +@Priority(1) +@ApplicationScoped +public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism { + + private static final Logger LOG = LoggerFactory.getLogger(CustomAwareJWTAuthMechanism.class); + + @Inject + JWTAuthMechanism delegate; + + @Override + public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) { + // do some custom action and delegate + return delegate.authenticate(context, identityProviderManager); + } + + @Override + public Uni getChallenge(RoutingContext context) { + return delegate.getChallenge(context); + } + + @Override + public Set> getCredentialTypes() { + return delegate.getCredentialTypes(); + } + + @Override + public HttpCredentialTransport getCredentialTransport() { + return delegate.getCredentialTransport(); + } + +} +---- + +== Security Identity Customization + +Internally, the identity providers create and update an istance of the `io.quarkus.security.identity.SecurityIdentity` class which holds the principal, roles, credentials which were used to authenticate the client (user) and other security attributes. An easy option to customize `SecurityIdentity` is to register a custom `SecurityIdentityAugmentor`, for example, the augmentor below adds an addition role: + +[source,java] +---- +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 int priority() { + return 0; + } + + @Override + public Uni augment(SecurityIdentity identity, AuthenticationRequestContext context) { + return context.runBlocking(build(identity)); + } + + private Supplier build(SecurityIdentity identity) { + if(identity.isAnonymous()) { + return () -> identity; + } else { + // create a new builder and copy principal, attributes, credentials and roles from the original + QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder() + .setPrincipal(identity.getPrincipal()) + .addAttributes(identity.getAttributes()) + .addCredentials(identity.getCredentials()) + .addRoles(identity.getRoles()); + + // add custom role source here + builder.addRole("dummy"); + return builder::build; + } + } +} +---- + +== Custom JAX-RS SecurityContext + +If you use JAX-RS `ContainerRequestFilter` to set a custom JAX-RS `SecurityContext` then make sure `ContainerRequestFilter` runs in the JAX-RS pre-match phase by adding a `@PreMatching` annotation to it for this custom security context be linked with Quarkus `SecurityIdentity`, for example: + +[source,java] +--- +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"; + } + }); + } + + } +} +--- + +== Disabling Authorization + +If you have a good reason to disable the authorization (for example, when testing) then you can register a custom `AuthorizationController`: + +[source,java] +--- +@Alternative +@Priority(Interceptor.Priority.LIBRARY_AFTER) +@ApplicationScoped +public class DisabledAuthController extends AuthorizationController { + @ConfigProperty(name = "disable.authorization") + boolean disableAuthorization; + + @Override + public boolean isAuthorizationEnabled() { + return disableAuthorization; + } +} +--- + +== Registering Security Providers + +When running in native mode the default behavior for Graal native image generation is to only include the main "SUN" provider +unless you have enabled SSL, in which case all security providers are registered. If you are not using SSL, then you can selectively +register security providers by name using the `quarkus.security.security-providers` property. The following example illustrates +configuration to register the "SunRsaSign" and "SunJCE" security providers: + +.Example Security Providers Configuration +[source,properties] +---- +quarkus.security.security-providers=SunRsaSign,SunJCE +... +---- + +== Reactive Security + +If you are going to use security in a reactive environment, you will likely need SmallRye Context Propagation: + +[source,xml] +---- + + 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 +link:context-propagation[Context Propagation Guide]. + diff --git a/docs/src/main/asciidoc/security-oauth2.adoc b/docs/src/main/asciidoc/security-oauth2.adoc index a82aec8df56ab1..fdc66290231edc 100644 --- a/docs/src/main/asciidoc/security-oauth2.adoc +++ b/docs/src/main/asciidoc/security-oauth2.adoc @@ -346,6 +346,7 @@ $ ./target/security-oauth2-quickstart-runner 2019-03-28 14:31:37,316 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, security, security-oauth2] ---- +[[integration-testing]] == Integration testing If you don't want to use a real OAuth2 authorization server for your integration tests, you can use the diff --git a/docs/src/main/asciidoc/security-testing.adoc b/docs/src/main/asciidoc/security-testing.adoc new file mode 100644 index 00000000000000..091cce2e150d46 --- /dev/null +++ b/docs/src/main/asciidoc/security-testing.adoc @@ -0,0 +1,99 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Security Testing + +include::./attributes.adoc[] + +This document describes how to test Quarkus Security. + +== Configuring User Information + +You can use link:security-properties[quarkus-elytron-security-properties-file] for testing security. This supports both embedding user info in `application.properties` and standalone properties files. + +For example, the following configuration will allow for configuring the users in both the production where OAuth2 is required and test (development) modes using link:https://quarkus.io/guides/config#configuration-profiles[Configuration Profiles]. + +[source,properties] +--- +# Configure embedded authentication +%dev.quarkus.security.users.embedded.enabled=true +%dev.quarkus.security.users.embedded.plain-text=true +%dev.quarkus.security.users.embedded.users.scott=reader +%dev.quarkus.security.users.embedded.users.stuart=writer +%dev.quarkus.security.users.embedded.roles.scott=READER +%dev.quarkus.security.users.embedded.roles.stuart=READER,WRITER + +# Configure OAuth2 +quarkus.oauth2.enabled=true +%dev.quarkus.oauth2.enabled=false +quarkus.oauth2.client-id=client-id +quarkus.oauth2.client-secret=client-secret +quarkus.oauth2.introspection-url=http://host:port/introspect +--- + +[#testing-security] +== Test Security Extension + +Quarkus provides explicit support for testing with different users, and with the security subsystem disabled. To use +this you must include the `quarkus-test-security` artifact: + +[source,xml] +---- + + io.quarkus + quarkus-test-security + test + +---- + +This artifact provides the `io.quarkus.test.security.TestSecurity` annotation, that can be applied to test methods and +test classes to control the security context that the test is run with. This allows you to do two things, you can disable +authorization so tests can access secured endpoints without needing to be authenticated, and you can specify the identity +that you want the tests to run under. + +A test that runs with authorization disabled can just set the enabled property to false: + +[source,java] +---- +@Test +@TestSecurity(authorizationEnabled = false) +void someTestMethod() { +... +} +---- + +This will disable all access checks, which allows the test to access secured endpoints without needing to authenticate. + +You can also use this to configure the current user that the test will run as: + +[source,java] +---- +@Test +@TestSecurity(user = "testUser", roles = {"admin", "user"}) +void someTestMethod() { +... +} +---- + +This will run the test with an identity with the given username and roles. Note that these can be combined, so you can +disable authorization while also providing an identity to run the test under, which can be useful if the endpoint expects an +identity to be present. + +[WARNING] +==== +The feature is only available for `@QuarkusTest` and will **not** work on a `@NativeImageTest`. +==== + +=== Mixing security tests + +If it becomes necessary to test security features using both `@TestSecurity` and Basic Auth (which is the fallback auth +mechanism when none is defined), then Basic Auth needs to be enabled explicitly, +for example by setting `quarkus.http.auth.basic=true` or `%test.quarkus.http.auth.basic=true`. + +== Use Wiremock for Integration Testing + +You can also use Wiremock to mock the authorization OAuth2 and OIDC services: +See link:security-oauth#integration-testing[OAuth2 Integration testing] for more details. + diff --git a/docs/src/main/asciidoc/security.adoc b/docs/src/main/asciidoc/security.adoc index 598a34c75f9435..8af7f671fd0035 100644 --- a/docs/src/main/asciidoc/security.adoc +++ b/docs/src/main/asciidoc/security.adoc @@ -3,470 +3,124 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc //// -= Quarkus - Security Guide += Quarkus - Security Architecture and Guides include::./attributes.adoc[] -Quarkus security allows you to define security authorization requirements for your code using annotations and/or configuration, and provides -several ways to load security authentication information using Quarkus extensions. +Quarkus Security provides the architecture, multiple authentication and authorization mechanisms, and other tools for the developers to build a production-quality security for their Quarkus applications. -== Authentication sources +This document provides a brief overview of Quarkus Security and links to the individual guides. -Quarkus supports several sources to load authentication information from. You need to import at least one of the following extensions -in order for Quarkus to know how to find the authentication information to check for authorizations: +== Architecture -.Security Extensions -|=== -|Extension |Description +`HttpAuthenticationMechanism` is the main entry into Quarkus HTTP Security. + +Quarkus Security Manager uses `HttpAuthenticationMechanism` to extract the authentication credentials from the HTTP request and delegates to `IdentityProvider` to +complete the conversion of these credentials to `SecurityIdentity`. -|link:security-properties[quarkus-elytron-security-properties-file] -|Provides support for simple properties files that can be used for testing security. This supports both embedding user info in `application.properties` and standalone properties files. +For example, the credentials may be coming with the HTTP `Authorization` header, client HTTPS certificates or cookies. -|link:security-jpa[quarkus-security-jpa] -|Provides support for authenticating via JPA. +`IdentityProvider` verifies the autentication credentials and maps them to `SecurityIdentity` which contains the user name, roles, the original authentication credentials, and other attributes. -|link:security-jdbc[quarkus-elytron-security-jdbc] -|Provides support for authenticating via JDBC. +For every authenticated resource, you can inject a `SecurityIdentity` instance to get the authenticated identity information. -|link:security-oauth2[quarkus-elytron-security-oauth2] -|Provides support for OAuth2 flows using Elytron. This extension will likely be deprecated soon and replaced by a reactive Vert.x version. +In some other contexts you may have other parallel representations of the same information (or parts of it) such as `SecurityContext` +for JAX-RS or `JsonWebToken` for JWT. -|link:security-jwt[quarkus-smallrye-jwt] -|A MicroProfile JWT implementation that provides support for authenticating using Json Web Tokens. This also allows you to inject the token and claims into the application as per the MP JWT spec. +== Authentication mechanisms -|link:security-openid-connect[quarkus-oidc] -|Provides support for authenticating via an OpenID Connect provider such as Keycloak. +Quarkus supports several sources to load authentication information from. -|link:security-keycloak-authorization[quarkus-keycloak-authorization] -|Provides support for a policy enforcer using Keycloak Authorization Services. +=== Basic and Form Authentication Mechanisms -|=== +Basic and Form HTTP-based authentication mechanisms are the core authentication mechanisms supported in Quarkus. +Please see link:security-built-in-authentication#basic-auth[Basic HTTP Authentication] and link:security-built-in-authentication#form-auth[Form HTTP Authentication] for more information. -Please see the linked documents above for details on how to setup the various extensions. +=== Mutual TLS Authentication -== Authenticating via HTTP +Quarkus provides Mutual TLS authentication so that you can authenticate users based on their X.509 certificates. -Quarkus has two built in authentication mechanisms for HTTP based FORM and BASIC auth. This mechanism is pluggable -however so extensions can add additional mechanisms (most notably OpenID Connect for Keycloak based auth). +Please see link:security-built-in-authentication#mutual-tls[Mutual TLS Authentication] for more information. -=== Basic Authentication +== OpenId Connect -To enable basic authentication set `quarkus.http.auth.basic=true`. You must also have at least one extension installed -that provides a username/password based `IdentityProvider`, such as link:security-jdbc[Elytron JDBC]. +`quarkus-oidc` extension provides a reactive, interoperable, multi-tenant enabled OpenId Connect adapter which supports `Bearer Token` and `Authorization Code Flow` authentication mechanisms. -=== Form Based Authentication +`Bearer Token` mechanism extract the token from HTTP `Authorization` header. +`Authorization Code Flow` mechanism uses OpenId Connect Authorization Code flow. It redirects the user to IDP to authenticate and completes the autentication process after the user has been redirected back to Quarkus by exchanging the provided code grant for ID, access and refresh tokens. -Quarkus provides form based authentication that works in a similar manner to traditional Servlet form based auth. Unlike -traditional form authentication the authenticated user is not stored in a HTTP session, as Quarkus does not provide -clustered HTTP session support. Instead the authentication information is stored in an encrypted cookie, which can -be read by all members of the cluster (provided they all share the same encryption key). +ID and access `JWT` tokens are verified with the refreshable `JWK` key set but both JWT and opaque (binary) tokens can be introspected remotely. -The encryption key can be set using the `quarkus.http.auth.session.encryption-key` property, and it must be at least 16 characters -long. This key is hashed using SHA-256 and the resulting digest is used as a key for AES-256 encryption of the cookie -value. This cookie contains a expiry time as part of the encrypted value, so all nodes in the cluster must have their -clocks synchronised. At one minute intervals a new cookie will be generated with an updated expiry time if the session -is in use. +See link:security-openid-connect[Using OpenID Connect to Protect Service Applications] guide for more information about `Bearer Token` authentication mechanism. -The following properties can be used to configure form based auth: +See link:security-openid-connect-web-authentication[Using OpenID Connect to Protect Web Application] guide for more information about `Authorization Code Flow` authentication mechanism. -include::{generated-dir}/config/quarkus-vertx-http-config-group-form-auth-config.adoc[opts=optional, leveloffset=+1] +[NOTE] Both `quarkus-oidc` `Bearer` and `Authorization Code Flow` Authentication mechanisms use <> to represent JWT tokens as Microprofile JWT `org.eclipse.microprofile.jwt.JsonWebToken`. -=== Mutual TLS Authentication +See link:security-openid-connect-multitenancy[Using OpenID Connect Multi-Tenancy] for more information about multiple tenants which can support `Bearer` or `Authorization Code Flow` authentication mechanism and configured statically or dynamically. -Quarkus provides mTLS authentication so that you can authenticate users based on their X.509 certificates. +If you use Keycloak and Bearer tokens then also see link:security-keycloak-authorization[Using Keycloak to Centralize Authorization] guide. -To use this authentication method, you should first enable SSL to your application. For more details, check the link:http-reference[Supporting secure connections with SSL] guide. +[[smallrye-jwt]] +== Smallrye JWT -Once your application is accepting secure connections, the next step is to configure a `quarkus.http.ssl.certificate.trust-store-file` -holding all the certificates that your application should trust as well as how your application should ask for certificates when -a client (e.g.: browser or another service) tries to access one of its protected resources. +`quarkus-smallrye-jwt` provides Microprofile JWT 1.1.1 impelementation and many more options to verify signed and encrypted `JWT` tokens and represent them as `org.eclipse.microprofile.jwt.JsonWebToken`. -[source,properties] ----- -quarkus.http.ssl.certificate.key-store-file=server-keystore.jks <1> -quarkus.http.ssl.certificate.key-store-password=the_key_store_secret -quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks <2> -quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret -quarkus.http.ssl.client-auth=required <3> +It provides an alternative to `quarkus-oidc` Bearer Token Authentication Mechanism. It can currently verify only `JWT` tokens using the PEM keys or refreshable `JWK` key set. -quarkus.http.auth.permission.default.paths=/* <4> -quarkus.http.auth.permission.default.policy=authenticated ----- -<1> Configures a key store where the server's private key is located. +Additionally it provides `JWT Generation API` for creating `signed`, `inner-signed` and/or `encrypted` `JWT` tokens with ease. -<2> Configures a trust store from where the trusted certificates are going to be loaded from. +See link:security-jwt[Using Smallrye JWT] guide for more information. -<3> Defines that the server should *always* ask certificates from clients. You can relax this behavior by using `REQUEST` so -that the server should still accept requests without a certificate. Useful when you are also supporting authentication methods other than -mTLS. +== OAuth2 -<4> Defines a policy where only authenticated users should have access to resources from your application. +`quarkus-elytron-security-oauth2` provides an alternative to `quarkus-oidc` Bearer Token Authentication Mechanism. It is based on `Elytron` and is primarily meant for introspecting the opaque tokens remotely. -Once the incoming request matches a valid certificate in the truststore, your application should be able to obtain the subject by -just injecting a `SecurityIdentity` as follows: +See link:security-oauth2[Using OAuth2] guide for more information. -[#x509-subject-example] -.Obtaining the subject -[source,java] ----- -@Inject -SecurityIdentity identity; +== LDAP -@GET -@Produces(MediaType.TEXT_PLAIN) -public String hello() { - return String.format("Hello, %s", identity.getPrincipal().getName()); -} ----- +Please see link:security-ldap[Authenticate with LDAP] guide for more information about LDAP authentication mechanism. -You should also be able to get the certificate as follows: +[[identity-providers]] +== Identity Providers -[#x509-credential-example] -.Obtaining the certificate -[source,java] ----- -CertificateCredential credential = identity.getCredential(CertificateCredential.class); -X509Certificate certificate = credential.getCertificate(); ----- +`IdentityProvider` converts the authentication credentials provided by `HttpAuthenticationMechanism` to `SecurityIdentity`. -=== Proactive Authentication +Some extensions such as `OIDC`, `OAuth2`, `Smallrye JWT`, `LDAP` have the inlined `IdentityProvider` implementations which are specific to the supported authentication flow. +For example, `quarkus-oidc` uses its own `IdentityProvider` to convert a token to `SecurityIdentity`. -By default Quarkus does what we call proactive authentication. This means that if an incoming request has a -credential then that request will always be authenticated (even if the target page does not require authentication). +If you use `Basic` or `Form` HTTP-based authentication then you have to add an `IdentityProvider` which can convert a user name and password to `SecurityIdentity`. -This means that requests with an invalid credential will always be rejected, even for public pages. You can change -this behaviour and only authenticate when required by setting `quarkus.http.auth.proactive=false`. - -[#standard-security-annotations] -== Authorization in REST endpoints and CDI beans using annotations - -Quarkus comes with built-in security to allow for Role-Based Access Control (link:https://en.wikipedia.org/wiki/Role-based_access_control[RBAC]) -based on the common security annotations `@RolesAllowed`, `@DenyAll`, `@PermitAll` on REST endpoints and CDI beans. -An example of an endpoint that makes use of both JAX-RS and Common Security annotations to describe and secure its endpoints is given in <>. Quarkus also provides -the `io.quarkus.security.Authenticated` annotation that will permit any authenticated user to access the resource -(equivalent to `@RolesAllowed("**")`). - -[#subject-example] -.SubjectExposingResource Example -[source,java] ----- -import java.security.Principal; - -import javax.annotation.security.DenyAll; -import javax.annotation.security.PermitAll; -import javax.annotation.security.RolesAllowed; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.SecurityContext; - -@Path("subject") -public class SubjectExposingResource { - - @GET - @Path("secured") - @RolesAllowed("Tester") <1> - public String getSubjectSecured(@Context SecurityContext sec) { - Principal user = sec.getUserPrincipal(); <2> - String name = user != null ? user.getName() : "anonymous"; - return name; - } - - @GET - @Path("unsecured") - @PermitAll<3> - public String getSubjectUnsecured(@Context SecurityContext sec) { - Principal user = sec.getUserPrincipal(); <4> - String name = user != null ? user.getName() : "anonymous"; - return name; - } - - @GET - @Path("denied") - @DenyAll<5> - public String getSubjectDenied(@Context SecurityContext sec) { - Principal user = sec.getUserPrincipal(); - String name = user != null ? user.getName() : "anonymous"; - return name; - } -} ----- -<1> This `/subject/secured` endpoint requires an authenticated user that has been granted the role "Tester" through the use of the `@RolesAllowed("Tester")` annotation. -<2> The endpoint obtains the user principal from the JAX-RS SecurityContext. This will be non-null for a secured endpoint. -<3> The `/subject/unsecured` endpoint allows for unauthenticated access by specifying the `@PermitAll` annotation. -<4> This call to obtain the user principal will return null if the caller is unauthenticated, non-null if the caller is authenticated. -<5> The `/subject/denied` endpoint disallows any access regardless of whether the call is authenticated by specifying the `@DenyAll` annotation. - - -== Authorization of Web Endpoints using configuration - -Quarkus has an integrated plugable web security layer. If security is enabled all HTTP requests will have a permission -check performed to make sure they are permitted to continue. - -NOTE: Configuration authorization checks are executed before any annotation-based authorization check is done, so both -checks have to pass for a request to be allowed. - -The default implementation allows you to define permissions using config in `application.properties`. An example -config is shown below: - -[source,properties] ----- -quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin <1> - -quarkus.http.auth.permission.roles1.paths=/roles-secured/*,/other/*,/api/* <2> -quarkus.http.auth.permission.roles1.policy=role-policy1 - -quarkus.http.auth.permission.permit1.paths=/public/* <3> -quarkus.http.auth.permission.permit1.policy=permit -quarkus.http.auth.permission.permit1.methods=GET - -quarkus.http.auth.permission.deny1.paths=/forbidden <4> -quarkus.http.auth.permission.deny1.policy=deny ----- -<1> This defines a role based policy that allows users with the `user` and `admin` roles. This is referenced by later rules -<2> This is a permission set that references the previously defined policy. `roles1` is an arbitrary name, you can call the permission sets whatever you want. -<3> This permission references the default `permit` built in policy to allow `GET` methods to `/public`. This is actually a no-op in this example, as this request would have been allowed anyway. -<4> This permission references the built in `deny` build in policy `/forbidden`. This is an exact path match as it does not end with *. - -Permissions are defined in config using permission sets. These are arbitrarily named permission grouping. Each permission -set must specify a policy that is used to control access. There are three built in policies: `deny`, `permit` and `authenticated`, -which permit all, deny all and only allow authenticated users respectively. - -It is also possible to define role based policies, as shown in the example. These policies will only allow users with the -specified roles to access the resources. - -=== Matching on paths, methods - -Permission sets can also specify paths and methods as a comma separated list. If a path ends with '*' then it is considered -to be a wildcard match and will match all sub paths, otherwise it is an exact match and will only match that specific path: - -[source,properties] ----- -quarkus.http.auth.permission.permit1.paths=/public/*,/css/*,/js/*,/robots.txt -quarkus.http.auth.permission.permit1.policy=permit -quarkus.http.auth.permission.permit1.methods=GET,HEAD ----- - -=== Matching path but not method - -If a request would match one or more permission sets based on the path, but does not match any due to method requirements -then the request is rejected. - -TIP: Given the above permission set, `GET /public/foo` would match both the path and method and thus be allowed, -whereas `POST /public/foo` would match the path but not the method and would thus be rejected. - -=== Matching multiple paths: longest wins - -Matching is always done on a longest path basis, less specific permission sets are not considered if a more specific one -has been matched: - -[source,properties] ----- -quarkus.http.auth.permission.permit1.paths=/public/* -quarkus.http.auth.permission.permit1.policy=permit -quarkus.http.auth.permission.permit1.methods=GET,HEAD - -quarkus.http.auth.permission.deny1.paths=/public/forbidden-folder/* -quarkus.http.auth.permission.deny1.policy=deny ----- +See link:security-jpa[JPA IdentityProvider] and link:security-jdbc[JDBC IdentityProvider] for more information. -TIP: Given the above permission set, `GET /public/forbidden-folder/foo` would match both permission sets' paths, -but because it matches the `deny1` permission set's path on a longer match, `deny1` will be chosen and the request will -be rejected. +== Combining Authentication Mechanisms -=== Matching multiple paths: most specific method wins +One can combine multiple authentication mechanisms if they get the authentication credentials from the different sources. +For example, combining built-in `Basic` and `quarkus-oidc` `Bearer` authentication mechanisms is allowed, but `quarkus-oidc` `Bearer` and `smallrye-jwt` authentication mechanisms is not allowed because both will attempt to verify the token extract from the HTTP `Authorization Bearer` scheme. -If a path is registered with multiple permission sets then any permission sets that specify a HTTP method will take -precedence and permissions sets without a method will not be considered (assuming of course the method matches). In this -instance, the permission sets without methods will only come into effect if the request method does not match any of the -sets with method permissions. +== Proactive Authentication -[source,properties] ----- -quarkus.http.auth.permission.permit1.paths=/public/* -quarkus.http.auth.permission.permit1.policy=permit -quarkus.http.auth.permission.permit1.methods=GET,HEAD - -quarkus.http.auth.permission.deny1.paths=/public/* -quarkus.http.auth.permission.deny1.policy=deny ----- +By default Quarkus does what we call proactive authentication. This means that if an incoming request has a +credential then that request will always be authenticated (even if the target page does not require authentication). -TIP: Given the above permission set, `GET /public/foo` would match both permission sets' paths, -but because it matches the `permit1` permission set's explicit method, `permit1` will be chosen and the request will -be accepted. `PUT /public/foo` on the other hand, will not match the method permissions of `permit1` and so -`deny1` will be activated and reject the request. +See link:security-built-in-authentication#proactive-authentication[Proactive Authentication] for more information. -=== Matching multiple paths and methods: both win +== Authorization -If multiple permission sets specify the same path and method (or multiple have no method) then both permissions have to -allow access for the request to proceed. Note that for this to happen both have to either have specified the method, or -have no method, method specific matches take precedence as stated above: +See link:security-authorization[Security Authorization] for more information about Role Based Access Control and other authorization options. -[source,properties] ----- -quarkus.http.auth.policy.user-policy1.roles-allowed=user -quarkus.http.auth.policy.admin-policy1.roles-allowed=admin +== Customization and other useful tips -quarkus.http.auth.permission.roles1.paths=/api/*,/restricted/* -quarkus.http.auth.permission.roles1.policy=user-policy1 +Quarkus Security is highly customizable. One can registers custom `HttpAuthenticationMechanism`s, `IdentityProvider`s and `SecurityidentityAugmentors`. -quarkus.http.auth.permission.roles2.paths=/api/*,/admin/* -quarkus.http.auth.permission.roles2.policy=admin-policy1 ----- +See link:security-customization[Security Customization] for more information about customizing Quarkus Security and other useful tips about the reactive security, registering the security providers, etc. -TIP: Given the above permission set, `GET /api/foo` would match both permission sets' paths, -so would require both the `user` and `admin` roles. +== Testing -== Authenticated representation +See link:security-testing[Security Testing] for more information about testing Quarkus Security. -For every authenticated resource, you can inject a `SecurityIdentity` instance to get the authenticated identity information. +== Secret Engines -In some other contexts you may have other parallel representations of the same information (or parts of it) such as `SecurityContext` -for JAX-RS or `JsonWebToken` for JWT. +Quarkus provided a very comprehensive `HashiCorp Vault` support, please see link:vault[HashiCorp Vault] documentation for more information. -== Configuration - -There are two configuration settings that alter the RBAC behavior: - -- `quarkus.security.jaxrs.deny-unannotated-endpoints=true|false` - if set to true, the access will be denied for all JAX-RS endpoints by default. -That is if the security annotations do not define the access control. Defaults to `false` -- `quarkus.security.deny-unannotated-members=true|false` - if set to true, the access will be denied to all CDI methods -and JAX-RS endpoints that do not have security annotations but are defined in classes that contain methods with -security annotations. Defaults to `false`. - -=== Registering Security Providers - -When running in native mode the default behavior for Graal native image generation is to only include the main "SUN" provider -unless you have enabled SSL, in which case all security providers are registered. If you are not using SSL, then you can selectively -register security providers by name using the `quarkus.security.security-providers` property. The following example illustrates -configuration to register the "SunRsaSign" and "SunJCE" security providers: - -.Example Security Providers Configuration -[source,properties] ----- -quarkus.security.security-providers=SunRsaSign,SunJCE -... ----- - -== Security Identity Customization - -Internally, the identity providers create and update an istance of the `io.quarkus.security.identity.SecurityIdentity` class which holds the principal, roles, credentials which were used to authenticate the client (user) and other security attributes. An easy option to customize `SecurityIdentity` is to register a custom `SecurityIdentityAugmentor`, for example, the augmentor below adds an addition role: - -[source,java] ----- -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 int priority() { - return 0; - } - - @Override - public Uni augment(SecurityIdentity identity, AuthenticationRequestContext context) { - return context.runBlocking(build(identity)); - } - - private Supplier build(SecurityIdentity identity) { - if(identity.isAnonymous()) { - return () -> identity; - } else { - // create a new builder and copy principal, attributes, credentials and roles from the original - QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder() - .setPrincipal(identity.getPrincipal()) - .addAttributes(identity.getAttributes()) - .addCredentials(identity.getCredentials()) - .addRoles(identity.getRoles()); - - // add custom role source here - builder.addRole("dummy"); - return builder::build; - } - } -} ----- - -== Reactive Security - -If you are going to use security in a reactive environment, you will likely need SmallRye Context Propagation: - -[source,xml] ----- - - 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 -link:context-propagation[Context Propagation Guide]. - -[#testing-security] -== Testing Security - -Quarkus provides explicit support for testing with different users, and with the security subsystem disabled. To use -this you must include the `quarkus-test-security` artifact: - -[source,xml] ----- - - io.quarkus - quarkus-test-security - test - ----- - -This artifact provides the `io.quarkus.test.security.TestSecurity` annotation, that can be applied to test methods and -test classes to control the security context that the test is run with. This allows you to do two things, you can disable -authorization so tests can access secured endpoints without needing to be authenticated, and you can specify the identity -that you want the tests to run under. - -A test that runs with authorization disabled can just set the enabled property to false: - -[source,java] ----- -@Test -@TestSecurity(authorizationEnabled = false) -void someTestMethod() { -... -} ----- - -This will disable all access checks, which allows the test to access secured endpoints without needing to authenticate. - -You can also use this to configure the current user that the test will run as: - -[source,java] ----- -@Test -@TestSecurity(user = "testUser", roles = {"admin", "user"}) -void someTestMethod() { -... -} ----- - -This will run the test with an identity with the given username and roles. Note that these can be combined, so you can -disable authorization while also providing an identity to run the test under, which can be useful if the endpoint expects an -identity to be present. - -[WARNING] -==== -The feature is only available for `@QuarkusTest` and will **not** work on a `@NativeImageTest`. -==== - -=== Mixing security tests - -If it becomes necessary to test security features using both `@TestSecurity` and Basic Auth (which is the fallback auth -mechanism when none is defined), then Basic Auth needs to be enabled explicitly, -for example by setting `quarkus.http.auth.basic=true` or `%test.quarkus.http.auth.basic=true`.