You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2022/08/23 11:27:08 UTC

[isis] 01/01: ISIS-3169: keycloak and secman improvements.

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-3169
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 1a74dbc1dd6df84cc1b99326bb3641cf409b0db8
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Aug 23 12:25:52 2022 +0100

    ISIS-3169: keycloak and secman improvements.
    
    Also renamed shiro secman integration artifact, for consistency.
---
 .../apache/isis/core/config/IsisConfiguration.java | 59 +++++++++++++-
 examples/demo/domain/pom.xml                       |  2 +-
 examples/demo/domain/src/main/resources/shiro.ini  |  4 +-
 extensions/pom.xml                                 |  7 +-
 .../secman/pages/setting-up-with-keycloak.adoc     |  5 +-
 .../secman/pages/setting-up-with-shiro.adoc        |  8 +-
 .../pages/setting-up-with-spring-oauth2.adoc       | 39 ++++++++-
 .../{shiro-realm => delegated-shiro}/pom.xml       |  4 +-
 .../shiro/IsisModuleExtSecmanDelegatedShiro.java}  |  5 +-
 .../shiro/realm}/AuthInfoForApplicationUser.java   |  5 +-
 .../realm/AuthenticationStrategyForSecMan.java}    |  6 +-
 .../realm}/IsisModuleExtSecmanShiroRealm.java      |  5 +-
 .../shiro/realm}/PermissionForMember.java          |  5 +-
 .../PermissionResolverForIsisShiroAuthorizor.java  |  3 +-
 ...lCollectionForApplicationUserOnSingleRealm.java |  3 +-
 ...inglePrincipalForApplicationUserInAnyRealm.java |  3 +-
 .../shiro/realm}/PrincipalForApplicationUser.java  |  3 +-
 .../secman/delegated}/shiro/util/ShiroUtils.java   |  5 +-
 .../pom.xml                                        | 40 ++++------
 .../IsisModuleExtSecmanDelegatedSpringOauth2.java} |  9 ++-
 .../dom/ApplicationUserAutoCreationService.java    | 60 ++++++++++++++
 extensions/security/secman/pom.xml                 |  8 +-
 regressiontests/incubating/pom.xml                 | 22 ++---
 .../test/resources/shiro-secman-ldap-cached.ini    | 10 +--
 .../src/test/resources/shiro-secman-ldap.ini       | 10 +--
 .../incubating/src/test/resources/shiro-secman.ini |  4 +-
 regressiontests/pom.xml                            |  2 +-
 .../keycloak/IsisModuleSecurityKeycloak.java       | 19 +----
 .../services/KeycloakOauth2UserService.java        | 93 +++++++++++++++++-----
 29 files changed, 329 insertions(+), 119 deletions(-)

diff --git a/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java b/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
index e408ce2a5f..2be200a9fe 100644
--- a/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
+++ b/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
@@ -157,7 +157,7 @@ public class IsisConfiguration {
              *         docker run -p 9090:8080 \
              *             -e KEYCLOAK_USER=admin \
              *             -e KEYCLOAK_PASSWORD=admin \
-             *             quay.io/keycloak/keycloak:14.0.0
+             *             quay.io/keycloak/keycloak:19.0.1
              *     </pre>,
              *
              *     then the URL would be "http://localhost:9090/auth".
@@ -171,10 +171,65 @@ public class IsisConfiguration {
              * true.
              */
             private String loginSuccessUrl = "/wicket";
-        }
 
+            /**
+             * Whether to (attempt to) extract realm roles and copy into the <code>DefaultOidcUser</code>.
+             *
+             * <p>
+             *     By default, realm roles are obtained from the token claims using the "User Realm Role" mapping type, into a token claim name "realm_access.roles"
+             * </p>
+             *
+             * <p>
+             *     This has been made a configuration option because some versions of Keycloak seemingly do not correctly extract these roles, see for example
+             *     <a href="https://keycloak.discourse.group/t/resource-access-claim-missing-from-userinfo-until-i-change-the-name/1238/3">this discussion</a> and
+             *     <a href="https://issues.redhat.com/browse/KEYCLOAK-9874">KEYCLOAK-9874</a>.
+             * </p>
+             */
+            private boolean extractRealmRoles = true;
 
+            /**
+             * If {@link #isExtractRealmRoles() realm roles are to be extracted}, this allows the resultant role to be optionally prefixed.
+             */
+            private String realmRolePrefix = null;
 
+            /**
+             * Whether to (attempt to) extract client roles and copy into the <code>DefaultOidcUser</code>.
+             *
+             * <p>
+             *     By default, client roles are extracted using the "User Client Role" mapping type, into a token claim name "resource_access.${client_id}.roles"
+             * </p>
+             *
+             * <p>
+             *     This has been made a configuration option because some versions of Keycloak seemingly do not correctly extract these roles, see for example
+             *     <a href="https://keycloak.discourse.group/t/resource-access-claim-missing-from-userinfo-until-i-change-the-name/1238/3">this discussion</a> and
+             *     <a href="https://issues.redhat.com/browse/KEYCLOAK-9874">KEYCLOAK-9874</a>.
+             * </p>
+             */
+            private boolean extractClientRoles = true;
+            /**
+             * If {@link #isExtractClientRoles()}  client roles are to be extracted}, this allows the resultant role to be optionally prefixed.
+             */
+            private String clientRolePrefix = null;
+
+            /**
+             * Whether to (attempt to) extract any available roles and into the <code>DefaultOidcUser</code>.
+             *
+             * <p>
+             *     This is to support any custom mapping type which maps into a token claim name called simply "roles"
+             * </p>
+             *
+             * <p>
+             *     This has been made a configuration option so that the workaround described in
+             *     <a href="https://keycloak.discourse.group/t/resource-access-claim-missing-from-userinfo-until-i-change-the-name/1238/3">this discussion</a> and
+             *     <a href="https://issues.redhat.com/browse/KEYCLOAK-9874">KEYCLOAK-9874</a> can be implemented.
+             * </p>
+             */
+            private boolean extractRoles = false;
+            /**
+             * If {@link #isExtractRoles()}  roles are to be extracted}, this allows the resultant role to be optionally prefixed.
+             */
+            private String rolePrefix = null;
+        }
     }
 
     private final Applib applib = new Applib();
diff --git a/examples/demo/domain/pom.xml b/examples/demo/domain/pom.xml
index ee64899dd7..92c3d0fbad 100644
--- a/examples/demo/domain/pom.xml
+++ b/examples/demo/domain/pom.xml
@@ -73,7 +73,7 @@
 		</dependency>
 		<dependency>
 			<groupId>org.apache.isis.extensions</groupId>
-			<artifactId>isis-extensions-secman-shiro-realm</artifactId>
+			<artifactId>isis-extensions-secman-delegated-shiro</artifactId>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.isis.testing</groupId>
diff --git a/examples/demo/domain/src/main/resources/shiro.ini b/examples/demo/domain/src/main/resources/shiro.ini
index e9785da13a..c9b617ea3d 100644
--- a/examples/demo/domain/src/main/resources/shiro.ini
+++ b/examples/demo/domain/src/main/resources/shiro.ini
@@ -19,8 +19,8 @@
 
 [main]
 
-authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
-isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleExtSecmanShiroRealm
+authenticationStrategy=org.apache.isis.extensions.secman.delegated.shiro.realm.AuthenticationStrategyForSecMan
+isisModuleSecurityRealm=org.apache.isis.extensions.secman.delegated.shiro.realm.IsisModuleExtSecmanShiroRealm
 
 securityManager.authenticator.authenticationStrategy = $authenticationStrategy
 securityManager.realms = $isisModuleSecurityRealm
diff --git a/extensions/pom.xml b/extensions/pom.xml
index d4bf006406..c4aa99ef6d 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -375,7 +375,12 @@
 			</dependency>
 			<dependency>
 				<groupId>org.apache.isis.extensions</groupId>
-				<artifactId>isis-extensions-secman-shiro-realm</artifactId>
+				<artifactId>isis-extensions-secman-delegated-shiro</artifactId>
+				<version>2.0.0-SNAPSHOT</version>
+			</dependency>
+			<dependency>
+				<groupId>org.apache.isis.extensions</groupId>
+				<artifactId>isis-extensions-secman-spring-autocreator</artifactId>
 				<version>2.0.0-SNAPSHOT</version>
 			</dependency>
 
diff --git a/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-keycloak.adoc b/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-keycloak.adoc
index 3c8be89427..06b406c16b 100644
--- a/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-keycloak.adoc
+++ b/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-keycloak.adoc
@@ -5,5 +5,8 @@
 
 This section describes how to setup and configure SecMan authorizor combined with xref:security:keycloak:about.adoc[Keycloak] being used as the authenticator.
 
-WARNING: TODO: v2 - to write up...
+In this scenario, we require that SecMan automatically creates any ``ApplicationUser``s as delegated users, meaning that they are defined externally (in keycloak, in fact).
+
+This is actually the exact same scenario as using the xref:security:spring-oauth2:about.adoc[] authenticator (the Keycloak authentication builds on top of Spring Security OAuth2).
+Therefore, perform the same steps as described in xref:setting-up-with-spring-oauth2.adoc[setting up Secman with Spring Oauth2].
 
diff --git a/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-shiro.adoc b/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-shiro.adoc
index 7f88a63c1f..3ad92b8361 100644
--- a/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-shiro.adoc
+++ b/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-shiro.adoc
@@ -19,7 +19,7 @@ Thus:
 
 * Apache Isis' xref:security:shiro:about.adoc[Shiro security] integration sets up Shiro web filters to intercept every http request, as well as the xref:refguide:security:index/shiro/authentication/AuthenticatorShiro.adoc[AuthenticatorShiro] implementation.
 * The `AuthenticatorShiro` calls to the Shiro Security Manager to obtain the authenticated principal.
-* The Shiro Security Manager uses the `shiro.ini` configuration file to look up the realm to perform the authentication; in this case we configure it to use Secman's realm (xref:refguide:extensions:index/secman/shiro/IsisModuleExtSecmanShiroRealm.adoc[IsisModuleExtSecmanShiroRealm]).
+* The Shiro Security Manager uses the `shiro.ini` configuration file to look up the realm to perform the authentication; in this case we configure it to use Secman's realm (xref:refguide:extensions:index/secman/delegated/shiro/realm/IsisModuleExtSecmanShiroRealm.adoc[IsisModuleExtSecmanShiroRealm]).
 * Secman's realm implementation queries the database and uses this to create an instance of `PrincipalForApplicationUser`, where the `Principal` interface is Shiro's representation of an authenticated user.
 The `PrincipalForApplicationUser` is backed by xref:refguide:extensions:index/secman/applib/user/dom/ApplicationUser.adoc[ApplicationUser], which all of the permissions to object members for this particular user.
 * to render a page, the Apache Isis viewer uses configured `Authorizor`, in this case
@@ -35,7 +35,7 @@ These are called "local" users, as per the xref:refguide:extensions:index/secman
 
 Local authentication - as described in the previous section - does not actually accomplish much; although Shiro's `Authenticator` implementation is in use, since the Shiro Realm just queries the SecMan database, there is no real difference from simply using SecMan's own `Authenticator` impplementation.
 
-Where things become more interesting and useful is that Secman's xref:refguide:extensions:index/secman/shiro/IsisModuleExtSecmanShiroRealm.adoc[Realm implementation] also allows a "delegate" realm to be configured, meaning that an additional alternative Realm (eg LDAP) can be queried.
+Where things become more interesting and useful is that Secman's xref:refguide:extensions:index/secman/delegated/shiro/realm/IsisModuleExtSecmanShiroRealm.adoc[Realm implementation] also allows an alternative "delegate" realm (eg LDAP) to be queried.
 In such cases Shiro can obtain authentication of "delegated" users is performed by the delegate realm rather than locally.
 
 The diagram below shows where this delegation occurs:
@@ -66,7 +66,7 @@ In addition to the xref:setting-up.adoc#dependencies[regular dependencies] requi
     </dependency>
     <dependency>
         <groupId>org.apache.isis.extensions</groupId>
-        <artifactId>isis-extensions-secman-shiro-realm</artifactId>
+        <artifactId>isis-extensions-secman-delegated-shiro</artifactId>
     </dependency>
 </dependencies>
 ----
@@ -116,7 +116,7 @@ The following sets up Shiro _without_ delegation:
 [main]
 
 authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
-isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleExtSecmanShiroRealm
+isisModuleSecurityRealm=org.apache.isis.extensions.secman.delegated.shiro.realm.IsisModuleExtSecmanShiroRealm
 
 securityManager.authenticator.authenticationStrategy = $authenticationStrategy
 securityManager.realms = $isisModuleSecurityRealm
diff --git a/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-spring-oauth2.adoc b/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-spring-oauth2.adoc
index 2307a5e37c..dde940b192 100644
--- a/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-spring-oauth2.adoc
+++ b/extensions/security/secman/adoc/modules/secman/pages/setting-up-with-spring-oauth2.adoc
@@ -5,5 +5,42 @@
 
 This section describes how to setup and configure SecMan authorizor combined with xref:security:spring:about.adoc[Spring] being used as the authenticator, configured with xref:security:spring-oauth2:about.adoc[OAuth2].
 
-WARNING: TODO: v2 - to write up...
+In this scenario, we require that SecMan automatically creates any ``ApplicationUser``s as delegated users, meaning that they are defined externally (in the OAuth2 external store).
 
+We use an extension module to register a service that performs this task.
+
+
+== Configuration
+
+Update your pom.xml:
+
+[source,xml]
+.pom.xml
+----
+<dependencies>
+    <dependency>
+        <groupId>org.apache.isis.extensions</groupId>
+        <artifactId>isis-extensions-secman-delegated-spring-oauth2</artifactId>    <!--.-->
+    </dependency>
+</dependencies>
+----
+
+And update your `AppManifest`:
+
+[source,java]
+.AppManifest.java
+----
+@Configuration
+@Import({
+        ...
+        IsisModuleExtSecmanDelegatedSpringOauth2.class,
+        ...
+})
+public class AppManifest {
+}
+----
+
+== ApplicationUsers cannot be DISABLED
+
+This integration has one small limitation: it is not possible to disable delegated ``ApplicationUser``s.
+Or rather, they can be disabled, but this will have no effect; the user will still be able to log in.
diff --git a/extensions/security/secman/shiro-realm/pom.xml b/extensions/security/secman/delegated-shiro/pom.xml
similarity index 94%
copy from extensions/security/secman/shiro-realm/pom.xml
copy to extensions/security/secman/delegated-shiro/pom.xml
index dabcd3b05f..bb0a9aa6dd 100644
--- a/extensions/security/secman/shiro-realm/pom.xml
+++ b/extensions/security/secman/delegated-shiro/pom.xml
@@ -27,8 +27,8 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>isis-extensions-secman-shiro-realm</artifactId>
-    <name>Apache Isis Ext - Sec Man Realm (Using Shiro)</name>
+    <artifactId>isis-extensions-secman-delegated-shiro</artifactId>
+    <name>Apache Isis Ext - Sec Man Delegated (Using Shiro)</name>
     <description></description>
 
     <properties>
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanRealmShiro.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/IsisModuleExtSecmanDelegatedShiro.java
similarity index 90%
copy from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanRealmShiro.java
copy to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/IsisModuleExtSecmanDelegatedShiro.java
index 54858236c5..fbbe1f1881 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanRealmShiro.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/IsisModuleExtSecmanDelegatedShiro.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
@@ -30,6 +31,6 @@ import org.apache.isis.security.shiro.IsisModuleSecurityShiro;
 @Import({
         IsisModuleSecurityShiro.class
 })
-public class IsisModuleExtSecmanRealmShiro {
+public class IsisModuleExtSecmanDelegatedShiro {
 
 }
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/AuthInfoForApplicationUser.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/AuthInfoForApplicationUser.java
similarity index 95%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/AuthInfoForApplicationUser.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/AuthInfoForApplicationUser.java
index 0b03aa068a..cd93c05bc9 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/AuthInfoForApplicationUser.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/AuthInfoForApplicationUser.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import java.util.Collection;
 
@@ -27,7 +28,7 @@ import org.apache.shiro.subject.PrincipalCollection;
 import org.apache.shiro.subject.SimplePrincipalCollection;
 
 import org.apache.isis.commons.internal.base._Lazy;
-import org.apache.isis.extensions.secman.shiro.util.ShiroUtils;
+import org.apache.isis.extensions.secman.delegated.shiro.util.ShiroUtils;
 
 import lombok.AccessLevel;
 import lombok.Getter;
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/AuthenticationStrategyForIsisModuleSecurityRealm.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/AuthenticationStrategyForSecMan.java
similarity index 94%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/AuthenticationStrategyForIsisModuleSecurityRealm.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/AuthenticationStrategyForSecMan.java
index 4409c53fce..c7bfda9162 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/AuthenticationStrategyForIsisModuleSecurityRealm.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/AuthenticationStrategyForSecMan.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import java.util.Collection;
 
@@ -27,9 +28,10 @@ import org.apache.shiro.authc.SimpleAuthenticationInfo;
 import org.apache.shiro.authc.pam.AllSuccessfulStrategy;
 import org.apache.shiro.realm.Realm;
 
+
 import lombok.val;
 
-public class AuthenticationStrategyForIsisModuleSecurityRealm extends AllSuccessfulStrategy {
+public class AuthenticationStrategyForSecMan extends AllSuccessfulStrategy {
 
     /**
      * Reconfigures the SimpleAuthenticationInfo to use an implementation for storing its PrincipalCollections.
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanShiroRealm.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/IsisModuleExtSecmanShiroRealm.java
similarity index 98%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanShiroRealm.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/IsisModuleExtSecmanShiroRealm.java
index 3cfbdf7dfc..55e039c995 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanShiroRealm.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/IsisModuleExtSecmanShiroRealm.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import java.util.concurrent.Callable;
 import java.util.function.Supplier;
@@ -50,7 +51,7 @@ import org.apache.isis.core.config.IsisConfiguration.Extensions.Secman.Delegated
 import org.apache.isis.core.security.authorization.Authorizor;
 import org.apache.isis.extensions.secman.applib.user.dom.AccountType;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
-import org.apache.isis.extensions.secman.shiro.util.ShiroUtils;
+import org.apache.isis.extensions.secman.delegated.shiro.util.ShiroUtils;
 
 import lombok.Getter;
 import lombok.Setter;
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PermissionForMember.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PermissionForMember.java
similarity index 93%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PermissionForMember.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PermissionForMember.java
index 56beff13ba..496d8cd8de 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PermissionForMember.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PermissionForMember.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import org.apache.shiro.authz.Permission;
 
@@ -24,7 +25,7 @@ import org.apache.isis.applib.services.appfeat.ApplicationFeatureId;
 import org.apache.isis.extensions.secman.applib.permission.dom.ApplicationPermissionMode;
 
 /**
- * As created by {@link org.apache.isis.extensions.secman.shiro.PermissionResolverForIsisShiroAuthorizor}, interprets the
+ * As created by {@link PermissionResolverForIsisShiroAuthorizor}, interprets the
  * permission strings formatted by <code>IsisShiroAuthorizor</code>.
  */
 class PermissionForMember implements org.apache.shiro.authz.Permission {
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PermissionResolverForIsisShiroAuthorizor.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PermissionResolverForIsisShiroAuthorizor.java
similarity index 94%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PermissionResolverForIsisShiroAuthorizor.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PermissionResolverForIsisShiroAuthorizor.java
index 348976dc43..b0fb69bdd8 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PermissionResolverForIsisShiroAuthorizor.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PermissionResolverForIsisShiroAuthorizor.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import org.apache.shiro.authz.Permission;
 import org.apache.shiro.authz.permission.PermissionResolver;
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionForApplicationUserOnSingleRealm.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalCollectionForApplicationUserOnSingleRealm.java
similarity index 97%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionForApplicationUserOnSingleRealm.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalCollectionForApplicationUserOnSingleRealm.java
index 4cbe8777f4..6506cc93d5 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionForApplicationUserOnSingleRealm.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalCollectionForApplicationUserOnSingleRealm.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import java.util.Collection;
 import java.util.Collections;
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
similarity index 96%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
index 742b42bf06..61428bd03b 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalCollectionWithSinglePrincipalForApplicationUserInAnyRealm.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import java.util.Collection;
 
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalForApplicationUser.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalForApplicationUser.java
similarity index 98%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalForApplicationUser.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalForApplicationUser.java
index 7aa106e4b6..cdca3338ec 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/PrincipalForApplicationUser.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/realm/PrincipalForApplicationUser.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.shiro.realm;
 
 import java.util.Collection;
 import java.util.Collections;
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/util/ShiroUtils.java b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/util/ShiroUtils.java
similarity index 93%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/util/ShiroUtils.java
rename to extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/util/ShiroUtils.java
index 71006758a8..ceebd903f3 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/util/ShiroUtils.java
+++ b/extensions/security/secman/delegated-shiro/src/main/java/org/apache/isis/extensions/secman/delegated/shiro/util/ShiroUtils.java
@@ -15,8 +15,9 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro.util;
+package org.apache.isis.extensions.secman.delegated.shiro.util;
 
 import java.util.Collection;
 
@@ -26,7 +27,7 @@ import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.mgt.RealmSecurityManager;
 import org.apache.shiro.realm.Realm;
 
-import org.apache.isis.extensions.secman.shiro.IsisModuleExtSecmanShiroRealm;
+import org.apache.isis.extensions.secman.delegated.shiro.realm.IsisModuleExtSecmanShiroRealm;
 
 import lombok.experimental.UtilityClass;
 
diff --git a/extensions/security/secman/shiro-realm/pom.xml b/extensions/security/secman/delegated-springoauth2/pom.xml
similarity index 61%
rename from extensions/security/secman/shiro-realm/pom.xml
rename to extensions/security/secman/delegated-springoauth2/pom.xml
index dabcd3b05f..0adcb817d1 100644
--- a/extensions/security/secman/shiro-realm/pom.xml
+++ b/extensions/security/secman/delegated-springoauth2/pom.xml
@@ -27,36 +27,30 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>isis-extensions-secman-shiro-realm</artifactId>
-    <name>Apache Isis Ext - Sec Man Realm (Using Shiro)</name>
-    <description></description>
+    <artifactId>isis-extensions-secman-spring-autocreator</artifactId>
+    <name>Apache Isis Ext - Sec Man Autocreator (for Spring)</name>
+    <description>Provides a domain service to automatically create a delegated ApplicationUser if authentication has been performed externally, eg by Spring OAuth2 or by Keycloak.</description>
 
     <properties>
-        <jar-plugin.automaticModuleName>org.apache.isis.extensions.secman.shiro.realm</jar-plugin.automaticModuleName>
-        <git-plugin.propertiesDir>org/apache/isis/extensions/secman/shiro-realm</git-plugin.propertiesDir>
+        <jar-plugin.automaticModuleName>org.apache.isis.extensions.secman.spring.autocreator</jar-plugin.automaticModuleName>
+        <git-plugin.propertiesDir>org/apache/isis/extensions/secman/spring-autocreator</git-plugin.propertiesDir>
 
     </properties>
 
     <dependencies>
 
-    	<dependency>
-            <groupId>org.apache.isis.security</groupId>
-			<artifactId>isis-security-shiro</artifactId>
-			<scope>provided</scope>
-		</dependency>
-
-		<dependency>
-			<groupId>org.apache.isis.core</groupId>
-			<artifactId>isis-core-runtime</artifactId>
-			<scope>provided</scope>
-		</dependency>
-
-		<dependency>
-			<groupId>org.apache.isis.extensions</groupId>
-			<artifactId>isis-extensions-secman-applib</artifactId>
-			<version>2.0.0-SNAPSHOT</version>
-			<scope>provided</scope>
-		</dependency>
+        <dependency>
+            <groupId>org.apache.isis.extensions</groupId>
+            <artifactId>isis-extensions-secman-applib</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-core</artifactId>
+        </dependency>
 
     </dependencies>
 
diff --git a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanRealmShiro.java b/extensions/security/secman/delegated-springoauth2/src/main/java/org/apache/isis/extensions/secman/delegated/springoauth2/IsisModuleExtSecmanDelegatedSpringOauth2.java
similarity index 80%
rename from extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanRealmShiro.java
rename to extensions/security/secman/delegated-springoauth2/src/main/java/org/apache/isis/extensions/secman/delegated/springoauth2/IsisModuleExtSecmanDelegatedSpringOauth2.java
index 54858236c5..4d599bb42c 100644
--- a/extensions/security/secman/shiro-realm/src/main/java/org/apache/isis/extensions/secman/shiro/IsisModuleExtSecmanRealmShiro.java
+++ b/extensions/security/secman/delegated-springoauth2/src/main/java/org/apache/isis/extensions/secman/delegated/springoauth2/IsisModuleExtSecmanDelegatedSpringOauth2.java
@@ -15,21 +15,22 @@
  *  KIND, either express or implied.  See the License for the
  *  specific language governing permissions and limitations
  *  under the License.
+ *
  */
-package org.apache.isis.extensions.secman.shiro;
+package org.apache.isis.extensions.secman.delegated.springoauth2;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
-import org.apache.isis.security.shiro.IsisModuleSecurityShiro;
+import org.apache.isis.extensions.secman.applib.IsisModuleExtSecmanApplib;
 
 /**
  * @since 2.0 {@index}
  */
 @Configuration
 @Import({
-        IsisModuleSecurityShiro.class
+        IsisModuleExtSecmanApplib.class
 })
-public class IsisModuleExtSecmanRealmShiro {
+public class IsisModuleExtSecmanDelegatedSpringOauth2 {
 
 }
diff --git a/extensions/security/secman/delegated-springoauth2/src/main/java/org/apache/isis/extensions/secman/delegated/springoauth2/dom/ApplicationUserAutoCreationService.java b/extensions/security/secman/delegated-springoauth2/src/main/java/org/apache/isis/extensions/secman/delegated/springoauth2/dom/ApplicationUserAutoCreationService.java
new file mode 100644
index 0000000000..f485a6825e
--- /dev/null
+++ b/extensions/security/secman/delegated-springoauth2/src/main/java/org/apache/isis/extensions/secman/delegated/springoauth2/dom/ApplicationUserAutoCreationService.java
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+
+package org.apache.isis.extensions.secman.delegated.springoauth2.dom;
+
+import javax.inject.Inject;
+
+import org.springframework.context.ApplicationListener;
+import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
+import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.services.iactnlayer.InteractionService;
+import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
+import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserStatus;
+
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+@Service
+@RequiredArgsConstructor(onConstructor_ = {@Inject})
+public class ApplicationUserAutoCreationService
+        implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
+
+    private final ApplicationUserRepository applicationUserRepository;
+    private final InteractionService interactionService;
+
+    @Override
+    public void onApplicationEvent(final InteractiveAuthenticationSuccessEvent event) {
+        val authentication = event.getAuthentication();
+        val principal = authentication.getPrincipal();
+        if (!(principal instanceof DefaultOidcUser)) {
+            return;
+        }
+
+        val oidcUser = (DefaultOidcUser) principal;
+        val username = oidcUser.getIdToken().getPreferredUsername();
+        val email = oidcUser.getIdToken().getEmail();
+        val applicationUser = interactionService.callAnonymous(() -> applicationUserRepository.findOrCreateUserByUsername(username));
+        applicationUser.setEmailAddress(email);
+        applicationUser.setStatus(ApplicationUserStatus.UNLOCKED);  // locking not supported for keycloak
+    }
+}
diff --git a/extensions/security/secman/pom.xml b/extensions/security/secman/pom.xml
index 02e391ef69..11a297e8c9 100644
--- a/extensions/security/secman/pom.xml
+++ b/extensions/security/secman/pom.xml
@@ -79,7 +79,12 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.isis.extensions</groupId>
-                <artifactId>isis-extensions-secman-shiro-realm</artifactId>
+                <artifactId>isis-extensions-secman-delegated-shiro</artifactId>
+                <version>2.0.0-SNAPSHOT</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.isis.extensions</groupId>
+                <artifactId>isis-extensions-secman-spring-autocreator</artifactId>
                 <version>2.0.0-SNAPSHOT</version>
             </dependency>
 
@@ -116,5 +121,6 @@
     	<module>persistence-jdo</module>
     	<module>persistence-jpa</module>
     	<module>shiro-realm</module>
+    	<module>spring-autocreator</module>
     </modules>
 </project>
diff --git a/regressiontests/incubating/pom.xml b/regressiontests/incubating/pom.xml
index 95beebf793..fa039370a9 100644
--- a/regressiontests/incubating/pom.xml
+++ b/regressiontests/incubating/pom.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
-	license agreements. See the NOTICE file distributed with this work for additional 
-	information regarding copyright ownership. The ASF licenses this file to 
-	you under the Apache License, Version 2.0 (the "License"); you may not use 
-	this file except in compliance with the License. You may obtain a copy of 
-	the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
-	by applicable law or agreed to in writing, software distributed under the 
-	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
-	OF ANY KIND, either express or implied. See the License for the specific 
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+	license agreements. See the NOTICE file distributed with this work for additional
+	information regarding copyright ownership. The ASF licenses this file to
+	you under the Apache License, Version 2.0 (the "License"); you may not use
+	this file except in compliance with the License. You may obtain a copy of
+	the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+	by applicable law or agreed to in writing, software distributed under the
+	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+	OF ANY KIND, either express or implied. See the License for the specific
 	language governing permissions and limitations under the License. -->
 <project xmlns="http://maven.apache.org/POM/4.0.0"
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -32,7 +32,7 @@
 			<artifactId>isis-regressiontests-stable</artifactId>
 			<scope>test</scope>
 		</dependency>
-		
+
 		<dependency>
 			<groupId>org.apache.isis.mavendeps</groupId>
 			<artifactId>isis-mavendeps-webapp</artifactId>
@@ -90,7 +90,7 @@
 
 		<dependency>
 			<groupId>org.apache.isis.extensions</groupId>
-			<artifactId>isis-extensions-secman-shiro-realm</artifactId>
+			<artifactId>isis-extensions-secman-delegated-shiro</artifactId>
 			<scope>test</scope>
 		</dependency>
 
diff --git a/regressiontests/incubating/src/test/resources/shiro-secman-ldap-cached.ini b/regressiontests/incubating/src/test/resources/shiro-secman-ldap-cached.ini
index 7fa769ffb9..ebcc3536db 100644
--- a/regressiontests/incubating/src/test/resources/shiro-secman-ldap-cached.ini
+++ b/regressiontests/incubating/src/test/resources/shiro-secman-ldap-cached.ini
@@ -28,13 +28,13 @@ contextFactory.systemPassword = secret
 
 ldapRealm = org.apache.isis.extensions.shirorealmldap.realm.impl.IsisLdapRealm
 ldapRealm.contextFactory = $contextFactory
-ldapRealm.searchBase = ou=groups,o=mojo                    
-ldapRealm.groupObjectClass = groupOfUniqueNames            
-ldapRealm.uniqueMemberAttribute = uniqueMember             
+ldapRealm.searchBase = ou=groups,o=mojo
+ldapRealm.groupObjectClass = groupOfUniqueNames
+ldapRealm.uniqueMemberAttribute = uniqueMember
 ldapRealm.uniqueMemberAttributeValueTemplate = uid={0}
 
-authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
-isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleExtSecmanShiroRealm
+authenticationStrategy=org.apache.isis.extensions.secman.delegated.shiro.realm.AuthenticationStrategyForSecMan
+isisModuleSecurityRealm=org.apache.isis.extensions.secman.delegated.shiro.realm.IsisModuleExtSecmanShiroRealm
 isisModuleSecurityRealm.delegateAuthenticationRealm=$ldapRealm
 isisModuleSecurityRealm.authenticationCachingEnabled = true
 
diff --git a/regressiontests/incubating/src/test/resources/shiro-secman-ldap.ini b/regressiontests/incubating/src/test/resources/shiro-secman-ldap.ini
index a2310263c8..29c9418c67 100644
--- a/regressiontests/incubating/src/test/resources/shiro-secman-ldap.ini
+++ b/regressiontests/incubating/src/test/resources/shiro-secman-ldap.ini
@@ -28,13 +28,13 @@ contextFactory.systemPassword = secret
 
 ldapRealm = org.apache.isis.extensions.shirorealmldap.realm.impl.IsisLdapRealm
 ldapRealm.contextFactory = $contextFactory
-ldapRealm.searchBase = ou=groups,o=mojo                    
-ldapRealm.groupObjectClass = groupOfUniqueNames            
-ldapRealm.uniqueMemberAttribute = uniqueMember             
+ldapRealm.searchBase = ou=groups,o=mojo
+ldapRealm.groupObjectClass = groupOfUniqueNames
+ldapRealm.uniqueMemberAttribute = uniqueMember
 ldapRealm.uniqueMemberAttributeValueTemplate = uid={0}
 
-authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
-isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleExtSecmanShiroRealm
+authenticationStrategy=org.apache.isis.extensions.secman.delegated.shiro.realm.AuthenticationStrategyForSecMan
+isisModuleSecurityRealm=org.apache.isis.extensions.secman.delegated.shiro.realm.IsisModuleExtSecmanShiroRealm
 isisModuleSecurityRealm.delegateAuthenticationRealm=$ldapRealm
 
 securityManager.authenticator.authenticationStrategy = $authenticationStrategy
diff --git a/regressiontests/incubating/src/test/resources/shiro-secman.ini b/regressiontests/incubating/src/test/resources/shiro-secman.ini
index 4104fba57a..34a080577b 100644
--- a/regressiontests/incubating/src/test/resources/shiro-secman.ini
+++ b/regressiontests/incubating/src/test/resources/shiro-secman.ini
@@ -19,8 +19,8 @@
 
 [main]
 
-authenticationStrategy=org.apache.isis.extensions.secman.shiro.AuthenticationStrategyForIsisModuleSecurityRealm
-isisModuleSecurityRealm=org.apache.isis.extensions.secman.shiro.IsisModuleExtSecmanShiroRealm
+authenticationStrategy=org.apache.isis.extensions.secman.delegated.shiro.realm.AuthenticationStrategyForSecMan
+isisModuleSecurityRealm=org.apache.isis.extensions.secman.delegated.shiro.realm.IsisModuleExtSecmanShiroRealm
 
 securityManager.authenticator.authenticationStrategy = $authenticationStrategy
 securityManager.realms = $isisModuleSecurityRealm
diff --git a/regressiontests/pom.xml b/regressiontests/pom.xml
index ab2b35ad81..94c0b6150a 100644
--- a/regressiontests/pom.xml
+++ b/regressiontests/pom.xml
@@ -236,7 +236,7 @@
 
 			<dependency>
 				<groupId>org.apache.isis.extensions</groupId>
-				<artifactId>isis-extensions-secman-shiro-realm</artifactId>
+				<artifactId>isis-extensions-secman-delegated-shiro</artifactId>
 				<version>${project.version}</version>
 			</dependency>
 
diff --git a/security/keycloak/src/main/java/org/apache/isis/security/keycloak/IsisModuleSecurityKeycloak.java b/security/keycloak/src/main/java/org/apache/isis/security/keycloak/IsisModuleSecurityKeycloak.java
index 84d2e89713..a29aa772ae 100644
--- a/security/keycloak/src/main/java/org/apache/isis/security/keycloak/IsisModuleSecurityKeycloak.java
+++ b/security/keycloak/src/main/java/org/apache/isis/security/keycloak/IsisModuleSecurityKeycloak.java
@@ -77,7 +77,6 @@ import lombok.val;
 @EnableWebSecurity
 public class IsisModuleSecurityKeycloak {
 
-
     @Bean
     public WebSecurityConfigurerAdapter webSecurityConfigurer(
             final IsisConfiguration isisConfiguration,
@@ -90,23 +89,9 @@ public class IsisModuleSecurityKeycloak {
         );
     }
 
-//    @RequiredArgsConstructor
-//    public static class AuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
-//
-//        final List<LoginSuccessHandler> loginSuccessHandlers;
-//
-//        @Override
-//        public void onAuthenticationSuccess(
-//                final HttpServletRequest request,
-//                final HttpServletResponse response,
-//                final Authentication authentication) throws ServletException, IOException {
-//            super.onAuthenticationSuccess(request, response, authentication);
-//            loginSuccessHandlers.forEach(LoginSuccessHandler::onSuccess);
-//        }
-//    }
 
     @Bean
-    KeycloakOauth2UserService keycloakOidcUserService(final OAuth2ClientProperties oauth2ClientProperties) {
+    KeycloakOauth2UserService keycloakOidcUserService(final OAuth2ClientProperties oauth2ClientProperties, final IsisConfiguration isisConfiguration) {
 
         val jwtDecoder = createNimbusJwtDecoder(
                 oauth2ClientProperties.getProvider().get("keycloak").getJwkSetUri(),
@@ -115,7 +100,7 @@ public class IsisModuleSecurityKeycloak {
         val authoritiesMapper = new SimpleAuthorityMapper();
         authoritiesMapper.setConvertToUpperCase(true);
 
-        return new KeycloakOauth2UserService(jwtDecoder, authoritiesMapper);
+        return new KeycloakOauth2UserService(jwtDecoder, authoritiesMapper, isisConfiguration);
     }
 
     @RequiredArgsConstructor
diff --git a/security/keycloak/src/main/java/org/apache/isis/security/keycloak/services/KeycloakOauth2UserService.java b/security/keycloak/src/main/java/org/apache/isis/security/keycloak/services/KeycloakOauth2UserService.java
index 63a678f5c6..df14363e4d 100644
--- a/security/keycloak/src/main/java/org/apache/isis/security/keycloak/services/KeycloakOauth2UserService.java
+++ b/security/keycloak/src/main/java/org/apache/isis/security/keycloak/services/KeycloakOauth2UserService.java
@@ -18,12 +18,16 @@
  */
 package org.apache.isis.security.keycloak.services;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.function.Consumer;
 
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
@@ -40,6 +44,8 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtException;
 import org.springframework.util.CollectionUtils;
 
+import org.apache.isis.core.config.IsisConfiguration;
+
 import lombok.RequiredArgsConstructor;
 import lombok.val;
 
@@ -50,6 +56,7 @@ public class KeycloakOauth2UserService extends OidcUserService {
 
     final JwtDecoder jwtDecoder;
     final GrantedAuthoritiesMapper authoritiesMapper;
+    final IsisConfiguration isisConfiguration;
 
     /**
      * Augments {@link OidcUserService#loadUser(OidcUserRequest)} to add authorities
@@ -82,32 +89,78 @@ public class KeycloakOauth2UserService extends OidcUserService {
 
         Jwt token = parseJwt(userRequest.getAccessToken().getTokenValue());
 
-        // Would be great if Spring Security would provide something like a pluggable
-        // OidcUserRequestAuthoritiesExtractor interface to hide the junk below...
-
-        @SuppressWarnings("unchecked")
-        val resourceMap = (Map<String, Object>) token.getClaims().get("resource_access");
-        String clientId = userRequest.getClientRegistration().getClientId();
-
-        @SuppressWarnings("unchecked")
-        val clientResource = (Map<String, Map<String, Object>>) resourceMap.get(clientId);
-        if (CollectionUtils.isEmpty(clientResource)) {
-            return Collections.emptyList();
+        List<String> combinedRoles = new ArrayList<>();
+
+        if(isisConfiguration.getSecurity().getKeycloak().isExtractClientRoles()) {
+
+            // attempt to parse out 'resource_access.${client_id}.roles'
+
+            val resourceObj = token.getClaims().get("resource_access");
+            if (resourceObj instanceof Map) {
+                @SuppressWarnings("rawtypes")
+                val resourceMap = (Map) resourceObj;
+
+                val clientId = userRequest.getClientRegistration().getClientId();
+                val clientResourceObj = resourceMap.get(clientId);
+                if(clientResourceObj instanceof Map) {
+                    @SuppressWarnings("rawtypes")
+                    val clientResource = (Map) clientResourceObj;
+                    if (!CollectionUtils.isEmpty(clientResource)) {
+                        val clientRolesObj = clientResource.get("roles");
+                        if (clientResourceObj instanceof List) {
+                            @SuppressWarnings("unchecked")
+                            val clientRoles = (List<Object>) clientRolesObj;
+                            if (!CollectionUtils.isEmpty(clientRoles)) {
+                                val prefix = Optional.ofNullable(isisConfiguration.getSecurity().getKeycloak().getClientRolePrefix()).orElse("");
+                                clientRoles.stream()
+                                        .filter(Objects::nonNull)
+                                        .map(clientRole -> prefix + clientRole)
+                                        .forEach(combinedRoles::add);
+                            }
+                        }
+                    }
+                }
+            }
         }
 
-        @SuppressWarnings("unchecked")
-        List<String> clientRoles = (List<String>) clientResource.get("roles");
-        if (CollectionUtils.isEmpty(clientRoles)) {
-            return Collections.emptyList();
+        if (isisConfiguration.getSecurity().getKeycloak().isExtractRealmRoles()) {
+            // attempt to parse out 'realm_access.roles'
+            val realmAccessObj = token.getClaims().get("realm_access");
+            if (realmAccessObj instanceof Map) {
+                @SuppressWarnings("rawtypes")
+                val realmAccessMap = (Map)realmAccessObj;
+                Object realmRolesObj = realmAccessMap.get("roles");
+                if (realmRolesObj instanceof List) {
+                    @SuppressWarnings("unchecked")
+                    val realmRoles = (List<Object>) realmRolesObj;
+                    val prefix = Optional.ofNullable(isisConfiguration.getSecurity().getKeycloak().getRealmRolePrefix()).orElse("");
+                    realmRoles.stream()
+                            .filter(Objects::nonNull)
+                            .map(realmRole -> prefix + realmRole)
+                            .forEach(combinedRoles::add);
+                }
+            }
         }
 
-        Collection<? extends GrantedAuthority> authorities = AuthorityUtils
-                .createAuthorityList(clientRoles.toArray(new String[0]));
-        if (authoritiesMapper == null) {
-            return authorities;
+        if (isisConfiguration.getSecurity().getKeycloak().isExtractRoles()) {
+            // attempt to parse out 'roles'
+            val rolesObj = token.getClaims().get("roles");
+            if (rolesObj instanceof List) {
+                @SuppressWarnings("unchecked")
+                val roles = (List<Object>) rolesObj;
+                val prefix = Optional.ofNullable(isisConfiguration.getSecurity().getKeycloak().getRolePrefix()).orElse("");
+                roles.stream()
+                        .filter(Objects::nonNull)
+                        .map(role -> prefix + role)
+                        .forEach(combinedRoles::add);
+            }
         }
 
-        return authoritiesMapper.mapAuthorities(authorities);
+        Collection<? extends GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(combinedRoles.toArray(new String[]{}));
+        return authoritiesMapper == null
+                ? authorities
+                : authoritiesMapper.mapAuthorities(authorities);
+
     }
 
     private Jwt parseJwt(String accessTokenValue) {