Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions docs/src/main/asciidoc/security-authorize-web-endpoints-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ The context can be activated by users, for example with the `@ActivateRequestCon
We recommend to let Quarkus activate and prepare CDI request context for you.
For example, consider a situation where you want to inject a bean from the Jakarta REST context, such as the `jakarta.ws.rs.core.UriInfo` bean.
In this case, you must apply the `HttpSecurityPolicy` to Jakarta REST endpoints. This can be achieved in one of the following ways:

* Use the `@AuthorizationPolicy` security annotation.
* Set the `quarkus.http.auth.permission.custom1.applies-to=jaxrs` configuration property.

Expand Down Expand Up @@ -445,6 +446,23 @@ quarkus.http.auth.roles-mapping.admin=Admin1 <1> <2>
<1> Map the `admin` role to `Admin1` role. The `SecurityIdentity` will have both `admin` and `Admin1` roles.
<2> The `/*` path is not secured. You must secure your endpoints with standard security annotations or define HTTP permissions in addition to this configuration property.

If you prefer a programmatic configuration, the same mapping can be added with the `io.quarkus.vertx.http.security.HttpSecurity` CDI event:

[source,java]
----
package org.acme.http.security;

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

public class HttpSecurityConfiguration {

void configure(@Observes HttpSecurity httpSecurity) {
httpSecurity.rolesMapping("admin", "Admin1");
}
}
----

=== Shared permission checks

One important rule for unshared permission checks is that only one path match is applied, the most specific one.
Expand Down Expand Up @@ -488,6 +506,92 @@ quarkus.http.auth.permission.roles3.policy=role-policy3
<2> The `/secured/*` path can only be accessed by authenticated users. This way, you have secured the `/secured/all` path and so on.
<3> Shared permissions are always applied before unshared ones, therefore a `SecurityIdentity` with the `root` role will have the `user` role as well.

=== Set up path-specific authorization programmatically

You can also configure the authorization policies presented by this guide so far programmatically.
Consider the example mentioned earlier:

[source,properties]
----
quarkus.http.auth.permission.permit1.paths=/public/*
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET

quarkus.http.auth.permission.deny1.paths=/forbidden
quarkus.http.auth.permission.deny1.policy=deny

quarkus.http.auth.permission.roles1.paths=/roles-secured/*,/other/*,/api/*
quarkus.http.auth.permission.roles1.policy=role-policy1
quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin
----

The same authorization policies can be configured programmatically:

[source,java]
----
package org.acme.http.security;

import jakarta.enterprise.event.Observes;

import io.quarkus.vertx.http.security.HttpSecurity;

public class HttpSecurityConfiguration {

void configure(@Observes HttpSecurity httpSecurity) {
httpSecurity
.get("/public/*").permit()
.path("/roles-secured/*", "/other/*", "/api/*").roles("admin", "user")
.path("/forbidden").authorization().deny();
}

}
----

Additionally, the `io.quarkus.vertx.http.security.HttpSecurity` CDI event can be used to configure specific authentication mechanisms and policies:

[source,java]
----
package org.acme.http.security;

import io.quarkus.vertx.http.security.HttpSecurity;
import jakarta.enterprise.event.Observes;
import org.eclipse.microprofile.config.inject.ConfigProperty;

public class HttpSecurityConfiguration {

void configure(@Observes HttpSecurity httpSecurity, CustomHttpSecurityPolicy customHttpSecurityPolicy,
@ConfigProperty(name = "secured-path") String securedPath) {

httpSecurity.path("/api/*").authenticatedWith(new CustomAuthenticationMechanism()); <1>

httpSecurity.path("/other/*").basic().policy(customHttpSecurityPolicy); <2>

httpSecurity.path("/roles-secured/*").bearer().authorization()
.policy(identity -> identity.hasRole("user") || "root".equals(identity.getPrincipal().getName())); <3>

httpSecurity.path("/other/administration").authorizationCodeFlow()
.authorization().policy((identity, routingContext) -> {
if (!identity.isAnonymous()) {
String customAuthorization = routingContext.request().getHeader("Custom Authorization");
return yourCustomAuthorizationCheck(customAuthorization);
}
return false;
}); <4>

httpSecurity.path(securedPath).form(); <5>

httpSecurity.path("/user-info").bearer().authorization().permissions("openid", "email", "profile"); <6>
}
}
----
<1> Authenticate all the '/api/' sub-paths with your own `HttpAuthenticationMechanism` instance.
<2> Use the Basic authentication and authorize the requests with a custom `io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy`.
<3> Use the Bearer token authentication and authorize the `SecurityIdentity` with your own policy.
<4> Use Authorization Code Flow mechanism and write your own policy based on incoming request headers.
<5> When Quarkus fires the `HttpSecurity` CDI event, the runtime configuration is ready.
<6> Require that all the requests to the `/user-info` path have string permissions `openid`, `email` and `profile`.
The same authorization can be required with the `@PermissionsAllowed(value = { "openid", "email", "profile" }, inclusive = true)` annotation instance placed on an endpoint.

[[standard-security-annotations]]
== Authorization using annotations

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.oidc.test;

import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
Expand All @@ -15,6 +16,7 @@
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
import io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication;
import io.quarkus.vertx.http.security.HttpSecurity;
import io.restassured.RestAssured;

@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
Expand All @@ -23,7 +25,7 @@ public class ImplicitBasicAuthAndBearerAuthCombinationTest {
@RegisterExtension
static final QuarkusDevModeTest test = new QuarkusDevModeTest()
.withApplicationRoot((jar) -> jar
.addClasses(BasicBearerResource.class)
.addClasses(BasicBearerResource.class, BearerPathBasedResource.class)
.addAsResource(
new StringAsset("""
quarkus.security.users.embedded.enabled=true
Expand All @@ -41,10 +43,14 @@ public void testBasicEnabledAsSelectedWithAnnotation() {
// endpoint is annotated with 'BasicAuthentication', so basic auth must be enabled
RestAssured.given().auth().oauth2(getAccessToken()).get("/basic-bearer/bearer")
.then().statusCode(200).body(Matchers.is("alice"));
RestAssured.given().auth().oauth2(getAccessToken()).get("/basic-bearer/bearer-path-based")
.then().statusCode(200).body(Matchers.is("alice"));
RestAssured.given().auth().basic("alice", "alice").get("/basic-bearer/basic")
.then().statusCode(204);
RestAssured.given().auth().basic("alice", "alice").get("/basic-bearer/bearer")
.then().statusCode(401);
RestAssured.given().auth().basic("alice", "alice").get("/basic-bearer/bearer-path-based")
.then().statusCode(401);
RestAssured.given().auth().oauth2(getAccessToken()).get("/basic-bearer/basic")
.then().statusCode(401);
}
Expand Down Expand Up @@ -72,6 +78,23 @@ public String basic() {
public String bearer() {
return accessToken.getName();
}

}

@Path("/basic-bearer/bearer-path-based")
public static class BearerPathBasedResource {

@Inject
JsonWebToken accessToken;

@GET
public String bearerPathBased() {
return accessToken.getName();
}

void selectBearerUsingPathRule(@Observes HttpSecurity httpSecurity) {
httpSecurity.path("/basic-bearer/bearer-path-based").bearer();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.DescriptorUtils;
Expand Down Expand Up @@ -265,12 +267,14 @@ void createHttpAuthenticationHandler(HttpSecurityRecorder recorder, Capabilities
}
}

@Consume(RuntimeConfigSetupCompleteBuildItem.class)
@Produce(PreRouterFinalizationBuildItem.class)
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void initializeAuthenticationHandler(Optional<HttpAuthenticationHandlerBuildItem> authenticationHandler,
void initializeHttpSecurity(Optional<HttpAuthenticationHandlerBuildItem> authenticationHandler,
HttpSecurityRecorder recorder, VertxHttpConfig httpConfig, BeanContainerBuildItem beanContainerBuildItem) {
if (authenticationHandler.isPresent()) {
recorder.prepareHttpSecurityConfiguration(httpConfig);
recorder.initializeHttpAuthenticatorHandler(authenticationHandler.get().handler, httpConfig,
beanContainerBuildItem.getValue());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.quarkus.vertx.http.security;

import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class ConfigBasedPathMatchingHttpSecurityPolicyTest extends PathMatchingHttpSecurityPolicyTest {

private static final String APP_PROPS = """
quarkus.http.auth.permission.authenticated.paths=/
quarkus.http.auth.permission.authenticated.policy=authenticated
quarkus.http.auth.permission.public.paths=/api*
quarkus.http.auth.permission.public.policy=permit
quarkus.http.auth.permission.foo.paths=/api/foo/bar
quarkus.http.auth.permission.foo.policy=authenticated
quarkus.http.auth.permission.unsecured.paths=/api/public
quarkus.http.auth.permission.unsecured.policy=permit
quarkus.http.auth.permission.inner-wildcard.paths=/api/*/bar
quarkus.http.auth.permission.inner-wildcard.policy=authenticated
quarkus.http.auth.permission.inner-wildcard2.paths=/api/next/*/prev
quarkus.http.auth.permission.inner-wildcard2.policy=authenticated
quarkus.http.auth.permission.inner-wildcard3.paths=/api/one/*/three/*
quarkus.http.auth.permission.inner-wildcard3.policy=authenticated
quarkus.http.auth.permission.inner-wildcard4.paths=/api/one/*/*/five
quarkus.http.auth.permission.inner-wildcard4.policy=authenticated
quarkus.http.auth.permission.inner-wildcard5.paths=/api/one/*/jamaica/*
quarkus.http.auth.permission.inner-wildcard5.policy=permit
quarkus.http.auth.permission.inner-wildcard6.paths=/api/*/sadly/*/dont-know
quarkus.http.auth.permission.inner-wildcard6.policy=deny
quarkus.http.auth.permission.baz.paths=/api/baz
quarkus.http.auth.permission.baz.policy=authenticated
quarkus.http.auth.permission.static-resource.paths=/static-file.html
quarkus.http.auth.permission.static-resource.policy=authenticated
quarkus.http.auth.permission.fubar.paths=/api/fubar/baz*
quarkus.http.auth.permission.fubar.policy=authenticated
quarkus.http.auth.permission.management.paths=/q/*
quarkus.http.auth.permission.management.policy=authenticated
quarkus.http.auth.policy.shared1.roles.root=admin,user
quarkus.http.auth.permission.shared1.paths=/secured/*
quarkus.http.auth.permission.shared1.policy=shared1
quarkus.http.auth.permission.shared1.shared=true
quarkus.http.auth.policy.unshared1.roles-allowed=user
quarkus.http.auth.permission.unshared1.paths=/secured/user/*
quarkus.http.auth.permission.unshared1.policy=unshared1
quarkus.http.auth.policy.unshared2.roles-allowed=admin
quarkus.http.auth.permission.unshared2.paths=/secured/admin/*
quarkus.http.auth.permission.unshared2.policy=unshared2
quarkus.http.auth.permission.shared2.paths=/*
quarkus.http.auth.permission.shared2.shared=true
quarkus.http.auth.permission.shared2.policy=custom
quarkus.http.auth.roles-mapping.root1=admin,user
quarkus.http.auth.roles-mapping.admin1=admin
quarkus.http.auth.roles-mapping.public1=public2
""";

@RegisterExtension
static QuarkusUnitTest test = createQuarkusUnitTest(APP_PROPS);

}
Loading
Loading