Skip to content

Commit

Permalink
Test and document authentication and authorization on both client and…
Browse files Browse the repository at this point in the history
… server side fix #794
  • Loading branch information
ppalaga committed Dec 25, 2023
1 parent 6c9abe1 commit cfffe21
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkiverse.cxf.it.auth.basic;

import jakarta.annotation.security.RolesAllowed;
import jakarta.jws.WebService;

import io.quarkiverse.cxf.it.HelloService;

@WebService(serviceName = "HelloService", targetNamespace = HelloService.NS)
@RolesAllowed("app-user")
public class BasicAuthHelloServiceImpl implements HelloService {
@Override
public String hello(String person) {
return "Hello " + person + "!";
}
}
83 changes: 83 additions & 0 deletions docs/modules/ROOT/examples/client-server/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
client-server.bob.password = bob234

# Basic auth global configuration
# tag::users-and-roles[]
quarkus.http.auth.basic = true
quarkus.security.users.embedded.enabled = true
quarkus.security.users.embedded.plain-text = true
quarkus.security.users.embedded.users.alice = alice123
quarkus.security.users.embedded.roles.alice = admin
quarkus.security.users.embedded.users.bob = bob234
quarkus.security.users.embedded.roles.bob = app-user
# end::users-and-roles[]

quarkus.native.resources.includes = wsdl/*

# Global Quarkus CXF configuration
quarkus.cxf.path = /soap

# Addressing tests
quarkus.cxf.endpoint."/addressing-anonymous".implementor = io.quarkiverse.cxf.it.ws.addressing.server.anonymous.AddressingAnonymousImpl
quarkus.cxf.endpoint."/addressing-decoupled".implementor = io.quarkiverse.cxf.it.ws.addressing.server.decoupled.WsAddressingImpl

# XML Schema validation
quarkus.cxf.codegen.wsdl2java.includes = wsdl/*.wsdl
quarkus.cxf.codegen.wsdl2java.package-names = io.quarkiverse.cxf.it.server.xml.schema.validation.model
quarkus.cxf.codegen.wsdl2java.wsdl-location = classpath:wsdl/calculator.wsdl

# Service endpoints
quarkus.cxf.endpoint."/annotation-schema-validated-calculator".implementor = io.quarkiverse.cxf.it.server.xml.schema.validation.AnnotationSchemaValidatedCalculatorServiceImpl
# https://github.com/quarkiverse/quarkus-cxf/issues/557 @WebServide(wsdlLocation = "wsdl/calculator.wsdl") does not work
quarkus.cxf.endpoint."/annotation-schema-validated-calculator".wsdl = wsdl/calculator.wsdl
quarkus.cxf.endpoint."/annotation-schema-validated-calculator".logging.enabled = true

quarkus.cxf.endpoint."/application-properties-schema-validated-calculator".implementor = io.quarkiverse.cxf.it.server.xml.schema.validation.ApplicationPropertiesSchemaValidatedCalculatorServiceImpl
# https://github.com/quarkiverse/quarkus-cxf/issues/557 @WebServide(wsdlLocation = "wsdl/calculator.wsdl") does not work
quarkus.cxf.endpoint."/application-properties-schema-validated-calculator".wsdl = wsdl/calculator.wsdl
quarkus.cxf.endpoint."/application-properties-schema-validated-calculator".logging.enabled = true
quarkus.cxf.endpoint."/application-properties-schema-validated-calculator".schema-validation.enabled-for = both

# The validated client speaks to this endpoint, hence we want to keep the validation disabled here
quarkus.cxf.endpoint."/unvalidated-calculator".implementor = io.quarkiverse.cxf.it.server.xml.schema.validation.ApplicationPropertiesSchemaValidatedCalculatorServiceImpl
# https://github.com/quarkiverse/quarkus-cxf/issues/557 @WebServide(wsdlLocation = "wsdl/calculator.wsdl") does not work
quarkus.cxf.endpoint."/unvalidated-calculator".wsdl = wsdl/calculator.wsdl
quarkus.cxf.endpoint."/unvalidated-calculator".logging.enabled = true

# XML Schema validation Client
quarkus.cxf.client.application-properties-schema-validated-calculator.wsdl = http://localhost:${quarkus.http.test-port}/soap/unvalidated-calculator?wsdl
quarkus.cxf.client.application-properties-schema-validated-calculator.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/unvalidated-calculator
quarkus.cxf.client.application-properties-schema-validated-calculator.service-interface = io.quarkiverse.cxf.it.server.xml.schema.validation.ApplicationPropertiesSchemaValidatedCalculatorService
quarkus.cxf.client.application-properties-schema-validated-calculator.schema-validation.enabled-for = both


# WSDL not secured by basic auth
quarkus.cxf.endpoint."/basicAuth".implementor = io.quarkiverse.cxf.it.auth.basic.BasicAuthHelloServiceImpl
quarkus.cxf.endpoint."/basicAuth".logging.enabled = true
# Client
# tag::client-basic-auth[]
quarkus.cxf.client.basicAuth.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuth.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuth
quarkus.cxf.client.basicAuth.username = bob
quarkus.cxf.client.basicAuth.password = bob234
# end::client-basic-auth[]

quarkus.cxf.client.basicAuthAnonymous.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuthAnonymous.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuth

quarkus.cxf.client.basicAuthBadUser.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuthBadUser.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuth
quarkus.cxf.client.basicAuthBadUser.username = alice
quarkus.cxf.client.basicAuthBadUser.password = alice123

# WSDL secured by basic auth
quarkus.cxf.endpoint."/basicAuthSecureWsdl".implementor = io.quarkiverse.cxf.it.auth.basic.BasicAuthHelloServiceImpl
quarkus.cxf.endpoint."/basicAuthSecureWsdl".in-interceptors = #wsdlBasicAuthInterceptor
quarkus.cxf.endpoint."/basicAuthSecureWsdl".logging.enabled = true
# Client
# tag::client-basic-auth-wsdl[]
quarkus.cxf.client.basicAuthSecureWsdl.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuthSecureWsdl.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuthSecureWsdl
quarkus.cxf.client.basicAuthSecureWsdl.username = bob
quarkus.cxf.client.basicAuthSecureWsdl.password = ${client-server.bob.password}
quarkus.cxf.client.basicAuthSecureWsdl.secure-wsdl-access = true
# end::client-basic-auth-wsdl[]
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
** xref:user-guide/payload-logging.adoc[Logging]
** xref:user-guide/soap-payloads-with-jaxb.adoc[Complex SOAP payloads with JAXB]
** xref:user-guide/ssl.adoc[SSL]
** xref:user-guide/auth.adoc[Authentication and authorization]
** xref:user-guide/advanced-soap-client-topics.adoc[Advanced SOAP client topics]
** xref:user-guide/advanced-soap-server-topics.adoc[Advanced SOAP server topics]
** xref:user-guide/generate-java-from-wsdl.adoc[Generate Java from WSDL]
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/reference/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Out of https://cxf.apache.org/docs/transports.html[CXF Transports] only the foll

* `quarkus-cxf` implements its own custom transport based on Quarkus and Vert.x for serving SOAP endpoints
* HTTP client via `xref:reference/extensions/quarkus-cxf.adoc[quarkus-cxf]`, including
** xref:user-guide/advanced-soap-client-topics.adoc#basic-auth[Basic Authentication]
** xref:user-guide/auth.adoc##_client_http_basic_authentication[Basic Authentication]
* https://cxf.apache.org/docs/asynchronous-client-http-transport.html[Asynchronous Client HTTP Transport]
via `xref:reference/extensions/quarkus-cxf-rt-transports-http-hc5.adoc[quarkus-cxf-rt-transports-http-hc5]`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,6 @@ quarkus.cxf.client.anotherCalculator.client-endpoint-url = https://acme.com/ws/W
quarkus.cxf.client.anotherCalculator.service-interface = org.jboss.eap.quickstarts.wscalculator.calculator.CalculatorService
----

[[basic-auth]]
== Basic Authentication

Basic authentication for clients is supported by default.
Just add the following properties to your `application.properties`:

[source,properties]
----
quarkus.cxf.client.myCalculator.username = user
quarkus.cxf.client.myCalculator.password = password
----

[[timeouts]]
== Timeouts

Expand Down
62 changes: 62 additions & 0 deletions docs/modules/ROOT/pages/user-guide/auth.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
= Authentication and authorization

TIP: The sample code snippets shown in this section come from the
https://github.com/quarkiverse/quarkus-cxf/tree/main/integration-tests/client-server[Client and server integration test]
in the source tree of {quarkus-cxf-project-name}. You may want to use it as a runnable example.

== Client HTTP basic authentication

Use the following client configuration options provided by
`xref:reference/extensions/quarkus-cxf.adoc[quarkus-cxf]` extension
to pass the username and password for HTTP basic authentication:

* `xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus.cxf.client.-clients-.username[quarkus.cxf.client."clients".username]`
* `xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus.cxf.client.-clients-.password[quarkus.cxf.client."clients".password]`

Here is an example:

.application.properties
[source,properties]
----
include::example$client-server/application.properties[tag=client-basic-auth]
----

=== Accessing WSDL protected by basic authentication

By default, the clients created by {quarkus-cxf-project-name} do not send the `Authorization` header,
unless you set the `xref:reference/extensions/quarkus-cxf.adoc#quarkus-cxf_quarkus.cxf.client.-clients-.secure-wsdl-access[quarkus.cxf.client."clients".secure-wsdl-access]` to `true`:

.application.properties
[source,properties]
----
include::example$client-server/application.properties[tag=client-basic-auth-wsdl]
----

== Securing service endpoints

The server-side authentication and authorization is driven by https://quarkus.io/guides/security-overview[Quarkus Security],
especially when it comes to

* https://quarkus.io/guides/security-authentication-mechanisms[Authentication mechanisms]
* https://quarkus.io/guides/security-identity-providers[Identity providers]
* https://quarkus.io/guides/security-authorize-web-endpoints-reference[Role-based access control (RBAC)]

There is a basic example in our https://github.com/quarkiverse/quarkus-cxf/tree/main/integration-tests/client-server[Client and server integration test].
Its key parts are:

* `io.quarkus:quarkus-elytron-security-properties-file` dependency as an Identity provider
* Basic authentication enabled and users with their roles configured in `application.properties`:
+
.application.properties
[source,properties]
----
include::example$client-server/application.properties[tag=users-and-roles]
----
+
* Role-based access control enfoced via `@RolesAllowed` annotation:

.BasicAuthHelloServiceImpl.java
[source,properties]
----
include::example$client-server/BasicAuthHelloServiceImpl.java[]
----
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.quarkiverse.cxf.auth;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;

import io.quarkus.security.ForbiddenException;
import io.quarkus.security.UnauthorizedException;

/**
* Set the right HTTP status code for the response based on the kind of {@link SecurityException} being thrown.
*/
public class AuthFaultOutInterceptor extends AbstractSoapInterceptor {

public AuthFaultOutInterceptor() {
super(Phase.POST_LOGICAL);
}

@Override
public void handleMessage(SoapMessage message) throws Fault {
final Exception e = message.getContent(Exception.class);
if (e instanceof Fault) {
final Throwable securityException = rootCause(e);
if (securityException instanceof UnauthorizedException) {
((Fault) e).setStatusCode(401);
} else if (securityException instanceof ForbiddenException) {
((Fault) e).setStatusCode(403);
}
}
}

private static Throwable rootCause(Exception e) {
Throwable result = e;
while (result.getCause() != null) {
result = result.getCause();
}
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.quarkiverse.cxf.CxfConfig;
import io.quarkiverse.cxf.CxfFixedConfig;
import io.quarkiverse.cxf.QuarkusRuntimeJaxWsServiceFactoryBean;
import io.quarkiverse.cxf.auth.AuthFaultOutInterceptor;
import io.quarkiverse.cxf.logging.LoggingFactoryCustomizer;
import io.quarkus.arc.ManagedContext;
import io.quarkus.arc.runtime.BeanContainer;
Expand Down Expand Up @@ -135,6 +136,7 @@ public CxfHandler(CXFServletInfos cxfServletInfos, BeanContainer beanContainer,
jaxWsServerFactoryBean.getOutInterceptors());
CXFRuntimeUtils.addBeans(servletInfo.getOutFaultInterceptors(), "outFaultInterceptor", endpointString,
endpointType, jaxWsServerFactoryBean.getOutFaultInterceptors());
jaxWsServerFactoryBean.getOutFaultInterceptors().add(new AuthFaultOutInterceptor());
CXFRuntimeUtils.addBeans(servletInfo.getInFaultInterceptors(), "inFaultInterceptor", endpointString,
endpointType, jaxWsServerFactoryBean.getInFaultInterceptors());

Expand Down
32 changes: 32 additions & 0 deletions integration-tests/client-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,38 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<!-- Copy the sample code to docs module where Antora can see it -->
<id>copy-resources-for-antora</id>
<phase>compile</phase><!-- after source formatting -->
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>
${maven.multiModuleProjectDirectory}/docs/modules/ROOT/examples/client-server</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>application.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/java/io/quarkiverse/cxf/it/auth/basic</directory>
<includes>
<include>BasicAuthHelloServiceImpl.java</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@
@WebService(serviceName = "HelloService", targetNamespace = HelloService.NS)
@RolesAllowed("app-user")
public class BasicAuthHelloServiceImpl implements HelloService {

@Override
public String hello(String person) {
return "Hello " + person + "!";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public class BasicAuthResource {
@CXFClient("basicAuth")
HelloService basicAuth;

@CXFClient("basicAuthAnonymous")
HelloService basicAuthAnonymous;

@CXFClient("basicAuthBadUser")
HelloService basicAuthBadUser;

@CXFClient("basicAuthSecureWsdl")
HelloService basicAuthSecureWsdl;

Expand All @@ -27,6 +33,12 @@ public Response hello(String body, @PathParam("client") String client) {
case "basicAuth": {
yield basicAuth;
}
case "basicAuthBadUser": {
yield basicAuthBadUser;
}
case "basicAuthAnonymous": {
yield basicAuthAnonymous;
}
case "basicAuthSecureWsdl": {
yield basicAuthSecureWsdl;
}
Expand All @@ -37,8 +49,18 @@ public Response hello(String body, @PathParam("client") String client) {
try {
return Response.ok(helloService.hello(body)).build();
} catch (Exception e) {
return Response.serverError().entity(e.getMessage()).build();
Throwable rootCause = rootCause(e);
return Response.serverError().entity(rootCause.getMessage()).build();
}
}

private static Throwable rootCause(Exception e) {
e.printStackTrace();
Throwable result = e;
while (result.getCause() != null) {
result = result.getCause();
}
return result;
}

}
Loading

0 comments on commit cfffe21

Please sign in to comment.