You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2023/07/31 11:20:53 UTC
[causeway] branch CAUSEWAY-3543 updated (a568b32a95 -> 705ccb7c4a)
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a change to branch CAUSEWAY-3543
in repository https://gitbox.apache.org/repos/asf/causeway.git
from a568b32a95 CAUSEWAY-3543: fixes unit test
add b0216de43b Merge pull request #1806 from apache/CAUSEWAY-3543
add 9267c1de34 CAUSEWAY-3543: simplify a bit; also fixes potential NPE in BreadcrumbModel
add 15612eee43 CAUSEWAY-3543: fixes regression: NPE in AuthenticatedWebSessionForCauseway
add 9ec4259fb6 CAUSEWAY-3545: adds in Jwt classes, refactors RestfulClient for Oauth support
add 61c5d822cc CAUSEWAY-3545: fixes compile issue
add 89c94b05ec Merge pull request #1807 from apache/CAUSEWAY-3545
new 86e765e430 CAUSEWAY-3543: removes dependency on failsafe, use java.time instead of joda time
new 802e1766a7 CAUSEWAY-3543: adds docs for RestfulClient.
new 705ccb7c4a CAUSEWAY-3543: adds docs on configuring Spring Oauth2 to use Azure AD
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
bom/pom.xml | 4 +-
.../core/config/CausewayConfiguration.java | 8 +-
.../manager/AuthenticationManager.java | 8 +-
.../restclient/api/OutboxClient.java | 158 ++++++--------
.../restclient/api/OutboxClientConfig.java | 56 +++++
.../restclient/integtests/.gitignore | 1 +
.../integtests/OutboxRestClient_E2eTest.java | 96 ++++++++
extensions/security/spring-oauth2/pom.xml | 7 +
.../spring-oauth2/images/azure-api-permissions.png | Bin 0 -> 184927 bytes
.../images/azure-create-client-secret.png | Bin 0 -> 98738 bytes
.../spring-oauth2/images/azure-redirect-uri.png | Bin 0 -> 76041 bytes
.../images/azure-register-new-app.png | Bin 0 -> 324718 bytes
.../adoc/modules/spring-oauth2/pages/about.adoc | 241 +++++++++++++++++++--
.../CausewayModuleExtSpringSecurityOAuth2.java | 3 +
.../AuthenticationConverterOfJwt.java | 54 +++++
.../oauth2/restful/AuthenticationStrategyJwt.java | 97 +++++++++
.../secondary/fetch/CommandFetcher.java | 16 +-
.../testdomain/util/rest/RestEndpointService.java | 10 +-
.../adoc/modules/ROOT/pages/client.adoc | 98 +++++++++
viewers/restfulobjects/client/pom.xml | 5 +-
.../client/src/main/java/module-info.java | 4 +
.../restfulobjects/client/AuthenticationMode.java | 6 +
.../restfulobjects/client/RestfulClient.java | 62 ++++--
.../restfulobjects/client/RestfulClientConfig.java | 41 +++-
.../restfulobjects/client/auth/AuthFilter.java | 37 ++--
.../client/auth/AuthorizationHeaderFactory.java | 31 +++
.../client/auth/BasicAuthFilter.java | 96 --------
.../basic/AuthorizationHeaderFactoryBasic.java | 46 ++++
.../client/auth/basic/BasicCreds.java | 11 +
.../client/auth/oauth2/Oauth2Creds.java | 12 +
.../AuthorizationHeaderFactoryOauth2Azure.java | 51 +++++
.../client/auth/oauth2/azure/TokenCache.java | 91 ++++++++
.../client/auth/oauth2/azure/TokenParser.java | 31 +++
.../client/RestfulClient_Examples.java | 95 ++++++++
.../webmodule/WebModuleJaxrsResteasy.java | 3 +-
.../CausewayRestfulObjectsInteractionFilter.java | 4 +-
.../auth/AuthenticationStrategyBasicAuth.java | 5 +-
...ava => AuthenticationStrategyUsingSession.java} | 27 +--
.../widgets/breadcrumbs/BreadcrumbModel.java | 14 +-
.../AuthenticatedWebSessionForCauseway.java | 73 +++----
...nticatedWebSessionForCauseway_Authenticate.java | 15 --
41 files changed, 1264 insertions(+), 353 deletions(-)
create mode 100644 extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClientConfig.java
create mode 100644 extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/.gitignore
create mode 100644 extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/OutboxRestClient_E2eTest.java
create mode 100644 extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-api-permissions.png
create mode 100644 extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-create-client-secret.png
create mode 100644 extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-redirect-uri.png
create mode 100644 extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-register-new-app.png
create mode 100644 extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/authconverters/AuthenticationConverterOfJwt.java
create mode 100644 extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/restful/AuthenticationStrategyJwt.java
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/AuthenticationMode.java
copy extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/CausewayModuleExtSpringSecurityOAuth2.java => viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/AuthFilter.java (50%)
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/AuthorizationHeaderFactory.java
delete mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/BasicAuthFilter.java
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/basic/AuthorizationHeaderFactoryBasic.java
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/basic/BasicCreds.java
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/Oauth2Creds.java
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/azure/AuthorizationHeaderFactoryOauth2Azure.java
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/azure/TokenCache.java
create mode 100644 viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/azure/TokenParser.java
create mode 100644 viewers/restfulobjects/client/src/test/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient_Examples.java
rename viewers/restfulobjects/viewer/src/main/java/org/apache/causeway/viewer/restfulobjects/viewer/webmodule/auth/{AuthenticationStrategyDefault.java => AuthenticationStrategyUsingSession.java} (70%)
[causeway] 02/03: CAUSEWAY-3543: adds docs for RestfulClient.
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3543
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 802e1766a740b65555109ad3df62fff3600df686
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Jul 31 10:59:42 2023 +0100
CAUSEWAY-3543: adds docs for RestfulClient.
---
.../restclient/integtests/.gitignore | 1 +
.../adoc/modules/ROOT/pages/client.adoc | 98 ++++++++++++++++++++++
.../client/RestfulClient_Examples.java | 95 +++++++++++++++++++++
3 files changed, 194 insertions(+)
diff --git a/extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/.gitignore b/extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/.gitignore
new file mode 100644
index 0000000000..e263024f22
--- /dev/null
+++ b/extensions/core/executionoutbox/restclient/src/test/java/org/apache/causeway/extensions/executionoutbox/restclient/integtests/.gitignore
@@ -0,0 +1 @@
+/OutboxRestClient_EcpE2eTest.java
diff --git a/viewers/restfulobjects/adoc/modules/ROOT/pages/client.adoc b/viewers/restfulobjects/adoc/modules/ROOT/pages/client.adoc
index 498c069f50..0299857b4d 100644
--- a/viewers/restfulobjects/adoc/modules/ROOT/pages/client.adoc
+++ b/viewers/restfulobjects/adoc/modules/ROOT/pages/client.adoc
@@ -6,3 +6,101 @@ This library is for integration scenario to programmatically invoke the xref:vro
A common use case would be to consume information from some external system, for example uploading a scanned PDF or a representation of an incoming invoice.
+
+
+== Initializing
+
+We create a `RestfulClient` using a static factory method that accepts a `RestfulClientConfig` object.
+
+For example:
+
+* to create a `RestfulClient` that authenticates itself using basic auth:
++
+[source,java]
+----
+RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder()
+ .restfulBaseUrl("localhost:8080")
+ .authenticationMode(AuthenticationMode.BASIC)
+ .basicAuthUser("xxxx")
+ .basicAuthPassword("xxxx")
+ .build());
+----
+
+* to create a `RestfulClient` that authenticates itself using OAuth2 (Azure):
++
+[source,java]
+----
+RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder()
+ .restfulBaseUrl("localhost:8080")
+ .authenticationMode(AuthenticationMode.OAUTH2_AZURE)
+ .oauthTenantId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") // <.>
+ .oauthClientId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") // <.>
+ .oauthClientSecret("xxXXx~XXxxXx-XxXX.XxXX.XxxXX-XxxXX~XxxXX") // <.>
+ .build());
+----
++
+IMPORTANT: For the Causeway app being connected to, this will need to be configured appropriately; see xref:security:spring-oauth2:about.adoc[Spring Oauth2] extension for more details.
+
+* It's also possible to create a `RestfulClient` that uses some other arbitrary authentication mechanism:
++
+[source,java]
+----
+RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder() // <.>
+ .restfulBaseUrl("localhost:8080")
+ .build(),
+ new AuthorizationHeaderFactory() {
+ @Override
+ public String create() {
+ return "some authorization header"; // <.>
+ }
+ });
+);
+----
+<.> No need to provide any authentication details in the `RestfulClientConfig`
+<.> Used as the value of the `Authorization` HTTP header.
+
+
+
+== Making a Request
+
+[source,java]
+----
+Invocation.Builder request = // <.>
+ restfulClient.request("services/customers.CustomerApi/actions/findAll/invoke"); // <.>
+
+Response response = request.get(); // <.>
+// Response response = request.put(...); // <3>
+// Response response = request.post(...); // <3>
+// Response response = request.delete(); // <3>
+
+----
+<.> instance of `javax.ws.rs.client.Invocation.Builder`
+<.> specify the endpoint
+<.> call appropriately using either HTTP `GET`, `PUT`, `POST` or `DELETE`.
+The `PUT` and `POST` methods will require an entity body, and all might require additional headers to be set.
+
+
+== Refining the Client
+
+The `RestfulClient` delegates to an instance of `javax.ws.rs.client.Client` to actually perform the invocation.
+The configuration of this underlying `Client` can be refined by providing a "refining" operator of `ClientBuilder`, when the `RestfulClient` is created.
+
+For example, to specify a connection and read timeout:
+
+[source,java]
+----
+RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder()
+ .restfulBaseUrl("localhost:8080")
+ //... // <.>
+ .build(),
+ clientBuilder -> // <.>
+ clientBuilder.connectTimeout(5, TimeUnit.SECONDS)
+ .readTimeout(2, TimeUnit.SECONDS)
+);
+----
+<.> authentication details omitted
+<.> instance of `javax.ws.rs.client.ClientBuilder`
diff --git a/viewers/restfulobjects/client/src/test/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient_Examples.java b/viewers/restfulobjects/client/src/test/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient_Examples.java
new file mode 100644
index 0000000000..54275f9654
--- /dev/null
+++ b/viewers/restfulobjects/client/src/test/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient_Examples.java
@@ -0,0 +1,95 @@
+package org.apache.causeway.viewer.restfulobjects.client;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.core.Response;
+
+import org.apache.causeway.viewer.restfulobjects.client.auth.AuthorizationHeaderFactory;
+
+class RestfulClient_Examples {
+
+ void basic_auth() {
+
+ RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder()
+ .restfulBaseUrl("localhost:8080")
+ .authenticationMode(AuthenticationMode.BASIC)
+ .basicAuthUser("sven")
+ .basicAuthPassword("pass")
+ .build());
+
+ Invocation.Builder request = restfulClient.request("services/customers.CustomerApi/actions/findAll/invoke");
+
+ Response response = request.get();
+// Response response = request.put(...);
+// Response response = request.post(...);
+// Response response = request.delete();
+
+ }
+
+ void oauth2_auth() {
+
+ RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder()
+ .restfulBaseUrl("localhost:8080")
+ .authenticationMode(AuthenticationMode.OAUTH2_AZURE)
+ .oauthTenantId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
+ .oauthClientId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
+ .oauthClientSecret("xxXXx~XXxxXx-XxXX.XxXX.XxxXX-XxxXX~XxxXX")
+ .build());
+
+ Invocation.Builder request = restfulClient.request("services/customers.CustomerApi/actions/findAll/invoke");
+
+ Response response = request.get();
+// Response response = request.put(...);
+// Response response = request.post(...);
+// Response response = request.delete();
+
+ }
+
+ void auth_other() {
+
+ RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder()
+ .restfulBaseUrl("localhost:8080")
+ .build(),
+ new AuthorizationHeaderFactory() {
+ @Override
+ public String create() {
+ return "some authorization header";
+ }
+ });
+ }
+
+ void usage() {
+
+ RestfulClient restfulClient = null;
+
+ Invocation.Builder request = restfulClient.request("services/customers.CustomerApi/actions/findAll/invoke");
+
+ Response response = request.get();
+// Response response = request.put(...);
+// Response response = request.post(...);
+// Response response = request.delete();
+
+ }
+
+ // javax.ws.rs.client.ClientBuilder
+ void refiner() {
+
+ RestfulClient restfulClient = RestfulClient.ofConfig(
+ RestfulClientConfig.builder()
+ .restfulBaseUrl("localhost:8080")
+ //...
+ .build(),
+ clientBuilder ->
+ clientBuilder.connectTimeout(5, TimeUnit.SECONDS)
+ .readTimeout(2, TimeUnit.SECONDS)
+ );
+ }
+
+
+
+}
\ No newline at end of file
[causeway] 01/03: CAUSEWAY-3543: removes dependency on failsafe, use java.time instead of joda time
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3543
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 86e765e430876b6a4e8f4025377b348f790c233c
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Jul 31 10:59:27 2023 +0100
CAUSEWAY-3543: removes dependency on failsafe, use java.time instead of joda time
---
bom/pom.xml | 7 ---
viewers/restfulobjects/client/pom.xml | 5 ---
.../client/src/main/java/module-info.java | 1 -
.../client/auth/oauth2/azure/TokenCache.java | 52 ++++------------------
4 files changed, 9 insertions(+), 56 deletions(-)
diff --git a/bom/pom.xml b/bom/pom.xml
index 61cbeee2cf..ddca9245b3 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -123,8 +123,6 @@ It is therefore a copy of org.apache:apache, with customisations clearly identif
<htmlparser.version>2.1</htmlparser.version>
- <failsafe.version>2.4.4</failsafe.version>
-
<javafaker.version>1.0.2</javafaker.version>
<javassist.version>3.29.2-GA</javassist.version>
@@ -1446,11 +1444,6 @@ It is therefore a copy of org.apache:apache, with customisations clearly identif
<artifactId>woodstox-core</artifactId>
<version>6.5.1</version>
</dependency>
- <dependency>
- <groupId>net.jodah</groupId>
- <artifactId>failsafe</artifactId>
- <version>${failsafe.version}</version>
- </dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>stax2-api</artifactId>
diff --git a/viewers/restfulobjects/client/pom.xml b/viewers/restfulobjects/client/pom.xml
index 23617db97d..f26067c527 100644
--- a/viewers/restfulobjects/client/pom.xml
+++ b/viewers/restfulobjects/client/pom.xml
@@ -46,11 +46,6 @@
<artifactId>spring-context</artifactId>
</dependency>
- <dependency>
- <groupId>net.jodah</groupId>
- <artifactId>failsafe</artifactId>
- </dependency>
-
<!-- TEST DEPENDENCIES -->
<dependency>
diff --git a/viewers/restfulobjects/client/src/main/java/module-info.java b/viewers/restfulobjects/client/src/main/java/module-info.java
index ba25a29276..4f62cbb1ba 100644
--- a/viewers/restfulobjects/client/src/main/java/module-info.java
+++ b/viewers/restfulobjects/client/src/main/java/module-info.java
@@ -36,6 +36,5 @@ module org.apache.causeway.viewer.restfulobjects.client {
requires org.apache.logging.log4j;
requires spring.core;
requires org.apache.causeway.schema;
- requires net.jodah.failsafe;
requires org.slf4j;
}
\ No newline at end of file
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/azure/TokenCache.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/azure/TokenCache.java
index 22a05b7219..da63a1746a 100644
--- a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/azure/TokenCache.java
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/auth/oauth2/azure/TokenCache.java
@@ -1,51 +1,21 @@
package org.apache.causeway.viewer.restfulobjects.client.auth.oauth2.azure;
-import java.time.temporal.ChronoUnit;
+import lombok.val;
+
+import java.time.ZonedDateTime;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.apache.causeway.commons.functional.Railway;
-
-import org.apache.causeway.commons.internal.exceptions._Exceptions;
import org.apache.causeway.viewer.restfulobjects.client.auth.oauth2.Oauth2Creds;
-import org.joda.time.DateTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import lombok.val;
-
-import net.jodah.failsafe.Failsafe;
-import net.jodah.failsafe.RetryPolicy;
-
public class TokenCache {
- protected static Logger LOG = LoggerFactory.getLogger(TokenCache.class);
-
- private final RetryPolicy<Response> retryPolicyForAnyException = retryPolicyForAnyException();
-
- private static <T> RetryPolicy<T> retryPolicyForAnyException() {
- return retryPolicyFor(Exception.class);
- }
-
- private static <T> RetryPolicy<T> retryPolicyFor(Class<? extends Exception>... exceptionClasses) {
- return new RetryPolicy<T>()
- .handle(exceptionClasses)
- .withBackoff(500, 16000, ChronoUnit.MILLIS)
- .withMaxRetries(3)
- .onRetry(ctx -> {
- LOG.warn("Failed attempt {}, retrying...", (ctx.getAttemptCount() - 1));
- })
- .onFailure(ctx -> LOG.warn("Failed retry policy after {} attempts. Exception:\n {}", (ctx.getAttemptCount() - 1), _Exceptions.asStacktrace(
- ctx.getFailure())));
- }
-
private final Oauth2Creds creds;
private final ClientBuilder clientBuilder;
@@ -55,7 +25,7 @@ public class TokenCache {
}
private String jwtToken;
- private DateTime jwtTokenExpiresAt;
+ private ZonedDateTime jwtTokenExpiresAt;
public Railway<Exception, String> getToken() {
@@ -75,7 +45,7 @@ public class TokenCache {
.param("grant_type", "client_credentials");
val invocation = invocationBuilder.buildPost(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
- val response = Failsafe.with(retryPolicyForAnyException).get(() -> invocation.invoke());
+ val response = invocation.invoke();
val entity = response.readEntity(String.class);
@@ -104,22 +74,18 @@ public class TokenCache {
return Railway.success(jwtToken);
}
- public boolean isTokenExpired() {
- return !isTokenValid();
- }
-
private boolean isTokenValid() {
if (jwtTokenExpiresAt == null) {
return false;
}
- // token must remain valid for at least 5 minutes from now.
- val inFiveMinutesTime = now().plusMinutes(5);
+ // token must remain valid for at least 2 additional minutes from now.
+ val inFiveMinutesTime = now().plusMinutes(2);
return jwtTokenExpiresAt.isBefore(inFiveMinutesTime);
}
- private static DateTime now() {
- return DateTime.now();
+ private static ZonedDateTime now() {
+ return ZonedDateTime.now();
}
}
[causeway] 03/03: CAUSEWAY-3543: adds docs on configuring Spring Oauth2 to use Azure AD
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3543
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 705ccb7c4aa43b041da6cfa95c42ca9767f1bcd9
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Jul 31 12:20:47 2023 +0100
CAUSEWAY-3543: adds docs on configuring Spring Oauth2 to use Azure AD
---
.../spring-oauth2/images/azure-api-permissions.png | Bin 0 -> 184927 bytes
.../images/azure-create-client-secret.png | Bin 0 -> 98738 bytes
.../spring-oauth2/images/azure-redirect-uri.png | Bin 0 -> 76041 bytes
.../images/azure-register-new-app.png | Bin 0 -> 324718 bytes
.../adoc/modules/spring-oauth2/pages/about.adoc | 241 +++++++++++++++++++--
.../CausewayModuleExtSpringSecurityOAuth2.java | 3 +
.../oauth2/restful/AuthenticationStrategyJwt.java | 24 ++
7 files changed, 247 insertions(+), 21 deletions(-)
diff --git a/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-api-permissions.png b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-api-permissions.png
new file mode 100644
index 0000000000..f2a3ef462f
Binary files /dev/null and b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-api-permissions.png differ
diff --git a/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-create-client-secret.png b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-create-client-secret.png
new file mode 100644
index 0000000000..a5d383ec67
Binary files /dev/null and b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-create-client-secret.png differ
diff --git a/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-redirect-uri.png b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-redirect-uri.png
new file mode 100644
index 0000000000..7381e1cc77
Binary files /dev/null and b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-redirect-uri.png differ
diff --git a/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-register-new-app.png b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-register-new-app.png
new file mode 100644
index 0000000000..5b26c9a2b8
Binary files /dev/null and b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/images/azure-register-new-app.png differ
diff --git a/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/pages/about.adoc b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/pages/about.adoc
index a0862d8a5f..c58f42b973 100644
--- a/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/pages/about.adoc
+++ b/extensions/security/spring-oauth2/src/main/adoc/modules/spring-oauth2/pages/about.adoc
@@ -5,7 +5,7 @@
This guide describes the integration of Spring security's OAuth2 client.
-Authentication is done via OAuth2 and authorization via Shiro.
+Authentication is done via OAuth2 and authorization via either Shiro or Secman.
include::docs:mavendeps:partial$setup-and-configure-dependencyManagement.adoc[leveloffset=+1]
@@ -29,6 +29,23 @@ In the webapp module of your application, add the following dependency:
----
+== Design
+
+The module brings in a transitive dependency to `org.springframework.boot:spring-boot-starter-oauth2-client`.
+Using this, it provides an implementation of the xref:security:spring:about.adoc#authenticatorconverter-spi[AuthenticatorConverter SPI] that recognises the `OAuth2` principal provided by the Spring OAuth2 client.
+
+
+
+[#walk-through-oauth2]
+== Walk-through (Oauth with Github and Shiro authorizor)
+
+Using Spring Security we can configure your app with various authentication providers.
+In this section we describe how to modify the xref:docs:starters:helloworld.adoc[HelloWorld] starter app to use link:https://github.com[github] as an OAuth2 provider.
+The steps here are based on link:https://spring.io/guides/tutorials/spring-boot-oauth2/[this Spring tutorial].
+
+
+=== AppManifest
+
In your application's xref:userguide::modules.adoc#appmanifest[App Manifest], import `CausewayModuleExtSpringSecurityOAuth2` module:
[source,java]
@@ -45,31 +62,27 @@ public class AppManifest {
}
----
<.> The `CausewayModuleExtSpringSecurityOAuth2` module brings in a transitive dependency on `CausewayModuleSecuritySpring`.
-<.> An implementation of `Authorizor` is also required; see discussion below.
+<.> An implementation of `Authorizor` is also required.
+For the purpose of this walkthrough, we'll use the implementation provided by xref:security:shiro:about.adoc[Shiro], namely `AuthorizorShiro`.
[IMPORTANT]
====
Make sure that no other `CausewayModuleSecurityXxx` module is imported, either directly or indirectly.
====
-Neither of these modules provide an implementation of the xref:refguide:core:index/security/authorization/Authorizor.adoc[] SPI, so we must also configure one.
-For the purpose of this walkthrough, we'll use the implementation provided by xref:security:shiro:about.adoc[Shiro], namely `AuthorizorShiro`.
-However, it is also possible to use with other `Authorizor` implementations; for example, using xref:security:secman:setting-up-with-spring-oauth2.adoc[SecMan].
-
-
-== Design
-
-The module brings in a transitive dependency to `org.springframework.boot:spring-boot-starter-oauth2-client`.
-Using this, it provides an implementation of the xref:security:spring:about.adoc#authenticatorconverter-spi[AuthenticatorConverter SPI] that recognises the `OAuth2` principal provided by the Spring OAuth2 client.
+To use the Shiro authorizor, also add in the corresponding dependency:
-
-
-[#walk-through-oauth2]
-== Walk-through
-
-Using Spring Security we can configure your app with various authentication providers.
-In this section we describe how to modify the xref:docs:starters:helloworld.adoc[HelloWorld] starter app to use link:https://github.com[github] as an OAuth2 provider.
-The steps here are based on link:https://spring.io/guides/tutorials/spring-boot-oauth2/[this Spring tutorial].
+[source,xml]
+.pom.xml
+----
+<dependencies>
+ <dependency>
+ <groupId>org.apache.causeway.security</groupId>
+ <artifactId>causeway-security-shiro</artifactId>
+ <version>{page-causewayrel}</version>
+ </dependency>
+</dependencies>
+----
=== Code Changes
@@ -118,8 +131,6 @@ Next, add in `LoginController` to the `AppManifest`.
[source,java]
.AppManifest.java
----
-public class AppManifest {
-}
@Configuration
@Import({
...
@@ -169,6 +180,7 @@ Lastly (and optionally), the swagger/REST API is not configured for oauth2, so w
</html>
----
+
=== Configuration
We are now ready to configure the app.
@@ -221,3 +233,190 @@ Finally, if you log out then Spring will show a page to allow you to trigger the
image::github-sign-in-again.png[width=600px]
+
+
+[#walk-through-oauth2-azure]
+== Walk-through (Oauth with Azure and Secman authorizor)
+
+Using Spring Security we can configure your app with various authentication providers.
+In this section we briefly discuss configuring an app to use Microsoft Azure for both:
+
+* server-side login flow
++
+in other words for end-users accessing the xref:vw::about.adoc[Wicket viewer]), and
+
+* client-side login flow with JWT
++
+in other words for REST programmatic clients accessing the xref:vro::about.adoc[Restful Objects viewer].
+
+The server-side login flow is (as you might expect) quite similar to that required by Github.
+
+=== AppManifest
+
+[source,java]
+.AppManifest.java
+----
+@Configuration
+@Import({
+ ...
+ CausewayModuleExtSpringSecurityOAuth2.class,
+ CausewayModuleExtSecmanDelegatedSpringOauth2.class, // <.>
+})
+@EnableWebSecurity // <.>
+@ComponentScan(basePackageClasses = AppManifest.class)
+public class AppManifest {
+
+ // ...
+}
+----
+<.> Using secman for permissions (rather than Shiro, as was described in the xref:#walk-through-oauth2[github walkthrough] above)
+<.> We'll use Spring APIs to configure the authentication flows
+
+For the purpose of this walkthrough, we'll use the implementation provided by xref:security:secman:setting-up-with-spring-oauth2.adoc[SecMan].
+
+To use the Secman authorizor, also add in the corresponding dependency:
+
+[source,xml]
+.pom.xml
+----
+<dependencies>
+ <dependency>
+ <groupId>org.apache.causeway.extensions</groupId>
+ <artifactId>causeway-extensions-secman-integration</artifactId>
+ <version>{page-causewayrel}</version>
+ </dependency>
+</dependencies>
+----
+
+You'll need to ensure that the users have access to the appropriate roles that grant them permissions; see the Secman docs.
+
+In addition, we use Spring's APIs to configure the two authentication flows:
+
+[source,java]
+.AppManifest.java
+----
+// ,...
+@EnableWebSecurity // <.>
+public class AppManifest {
+
+ @Bean
+ @Order(2)
+ SecurityFilterChain webSecurity(HttpSecurity http) throws Exception { // <.>
+ http
+ .cors()
+ .and()
+ .authorizeHttpRequests()
+ .antMatchers("/**") // <.>
+ .authenticated()
+ .anyRequest()
+ .authenticated()
+ .and()
+ .oauth2Login()
+ .and()
+ .logout()
+ .permitAll()
+ ;
+ return http.build();
+ }
+
+ @Bean
+ @Order(1)
+ SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception { // <.>
+ http
+ .requestMatcher(new RequestHeaderRequestMatcher("Authorization"))
+ .cors()
+ .and()
+ .authorizeHttpRequests()
+ .antMatchers("/**") // <4>
+ .authenticated()
+ .and()
+ .oauth2ResourceServer()
+ .jwt()
+ .and()
+ .and()
+ .sessionManagement()
+ .disable()
+ ;
+ return http.build();
+ }
+}
+----
+<.> as noted above, we use Spring APIs to configure the authentication flows
+<.> configures the server-side flow
+<.> configures the client-side flow
+<.> uses link:https://ant.apache.org[Apache Ant] style matching syntax
+
+
+=== Azure AD Config
+
+Login to the Azure Portal:
+
+* open the _Azure Active Directory_ blade
+* _App Registrations_
+* New registration
++
+Enter any name; leave the other fields empty for now:
++
+image::azure-register-new-app.png[width=1200px]
+
+* Create new client secret (link to do so is top right)
++
+image::azure-create-client-secret.png[width=800px]
++
+Copy this value immediately; it is not accessible later.
+
+* Add redirect URI, specifying the "Web" platform first.
++
+For example:
++
+image::azure-redirect-uri.png[800px]
++
+In the above, `https://mywebapp.com` is the server to which the deployed, while the suffix `myapp` is the name of your app.
+This app name is used in the configuration of the app, see xref:#configuration-properties[below]
+
+* Add API permissions:
++
+image::azure-api-permissions.png[]
+
+
+[#configuration-properties]
+=== Configuration Properties
+
+Finally need to configure the app to use Oauth:
+
+[source,properties]
+.application.properties
+----
+o365.tenantId=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX // <.>
+o365.client=myapp // <.>
+
+spring.security.oauth2.client.registration.${o365.client}.provider=o365
+spring.security.oauth2.client.registration.${o365.client}.client-name=MyApp // <.>
+spring.security.oauth2.client.registration.${o365.client}.client-id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX // <.>
+spring.security.oauth2.client.registration.${o365.client}.client-secret=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // <.>
+spring.security.oauth2.client.registration.${o365.client}.redirect-uri=http://mywebapp.com:8080/login/oauth2/code/${o365.client} // <.>
+spring.security.oauth2.client.registration.${o365.client}.scope=openid, profile, email, user.read // <.>
+spring.security.oauth2.client.registration.${o365.client}.authorization-grant-type=authorization_code
+
+spring.security.oauth2.client.provider.o365.token-uri=https://login.microsoftonline.com/${o365.tenantId}/oauth2/v2.0/token
+spring.security.oauth2.client.provider.o365.jwk-set-uri=https://login.microsoftonline.com//${o365.tenantId}/discovery/v2.0/keys
+spring.security.oauth2.client.provider.o365.authorization-uri=https://login.microsoftonline.com//${o365.tenantId}/oauth2/v2.0/authorize
+spring.security.oauth2.client.provider.o365.user-name-attribute=email
+spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://login.microsoftonline.com/${o365.tenantId}/discovery/v2.0/keys
+----
+<.> Azure tenant Id
+<.> Application id, as used in the suffix of the redirect URI
+<.> Application (or client) name, recommend that this corresponds to the app name when the client was registered in Azure
+<.> Azure client Id, obtained from the application registration page
+<.> Azure client secret, as setup in the Azure portal
+<.> Redirect URI; must exactly match the redirect URI specified in the Azure portal
+<.> Scope matches the API permissions set up in the Azure portal.
+
+And, to enable JWT (REST client access), also add in:
+
+[source,properties]
+.application.properties
+----
+causeway.viewer.restfulobjects.authentication.strategy-class-name=\
+ org.apache.causeway.extensions.spring.security.oauth2.restful.AuthenticationStrategyJwt
+----
diff --git a/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/CausewayModuleExtSpringSecurityOAuth2.java b/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/CausewayModuleExtSpringSecurityOAuth2.java
index ed5a86c1d9..038f8df6f3 100644
--- a/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/CausewayModuleExtSpringSecurityOAuth2.java
+++ b/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/CausewayModuleExtSpringSecurityOAuth2.java
@@ -18,6 +18,8 @@
*/
package org.apache.causeway.extensions.spring.security.oauth2;
+import org.apache.causeway.extensions.spring.security.oauth2.authconverters.AuthenticationConverterOfJwt;
+
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@@ -37,6 +39,7 @@ import org.apache.causeway.security.spring.CausewayModuleSecuritySpring;
// @Component's
AuthenticationConverterOfOAuth2UserPrincipal.class,
+ AuthenticationConverterOfJwt.class,
})
//@Log4j2
diff --git a/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/restful/AuthenticationStrategyJwt.java b/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/restful/AuthenticationStrategyJwt.java
index 1204a356db..6ecd819be2 100644
--- a/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/restful/AuthenticationStrategyJwt.java
+++ b/extensions/security/spring-oauth2/src/main/java/org/apache/causeway/extensions/spring/security/oauth2/restful/AuthenticationStrategyJwt.java
@@ -31,6 +31,30 @@ import org.apache.causeway.viewer.restfulobjects.viewer.webmodule.auth.Authentic
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
+/**
+ * This class enables support for JWT for Restful clients.
+ *
+ * <p>
+ * To use, it requires that the restfulobjects viewer is on the classpath:
+ *
+ * <pre>
+ * <dependency>
+ * <groupId>org.apache.causeway.viewer</groupId>
+ * <artifactId>causeway-viewer-restfulobjects-viewer</artifactId>
+ * </dependency>
+ * </pre>
+ *
+ * <p>
+ * It should then additionally be specified as the authentication strategy:
+ *
+ * <pre>
+ * isis.viewer.restfulobjects.authentication.strategy-class-name=\
+ * org.apache.causeway.extensions.spring.security.oauth2.restful.AuthenticationStrategyJwt
+ * </pre>
+ * </p>
+ *
+ *
+ */
public class AuthenticationStrategyJwt extends AuthenticationStrategyAbstract {
@Override