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 2019/10/25 07:21:26 UTC

[isis] branch ISIS-1267 created (now 64d8414)

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

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


      at 64d8414  ISIS-1267: disables the logout menu

This branch includes the following new commits:

     new a2c8518  ISIS-1267: adds keycloak module and extends AuthenticationSession
     new 64d8414  ISIS-1267: disables the logout menu

The 2 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.



[isis] 02/02: ISIS-1267: disables the logout menu

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 64d8414ff7af23f12996426f89e356b2848307a6
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Oct 25 08:20:45 2019 +0100

    ISIS-1267: disables the logout menu
---
 .../serviceactions/TertiaryActionsPanel.java           | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/core/viewer-wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryActionsPanel.java b/core/viewer-wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryActionsPanel.java
index 53831ab..ae8ed32 100644
--- a/core/viewer-wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryActionsPanel.java
+++ b/core/viewer-wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/actionmenu/serviceactions/TertiaryActionsPanel.java
@@ -22,8 +22,15 @@ import java.util.List;
 
 import javax.inject.Inject;
 
+import org.apache.isis.core.runtime.web.AuthenticationSessionWormhole;
+import org.apache.isis.metamodel.commons.Wormhole;
+import org.apache.isis.security.authentication.AuthenticationSession;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+import org.apache.isis.viewer.wicket.ui.util.Tooltips;
+import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.Page;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
 import org.apache.wicket.markup.head.CssHeaderItem;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.html.WebComponent;
@@ -92,6 +99,7 @@ public class TertiaryActionsPanel extends PanelBase {
     }
 
     private void addLogoutLink(MarkupContainer themeDiv) {
+
         Link<Void> logoutLink = new Link<Void>("logoutLink") {
 
             private static final long serialVersionUID = 1L;
@@ -104,6 +112,16 @@ public class TertiaryActionsPanel extends PanelBase {
 
         };
         themeDiv.add(logoutLink);
+
+        // this is hacky, would rather ask AuthenticatedWebSessionForIsis, but that type isn't visible here.
+        final AuthenticationSession authenticationSession = AuthenticationSessionWormhole.sessionByThread.get();
+        if(authenticationSession != null && authenticationSession.getType() == AuthenticationSession.Type.EXTERNAL) {
+            logoutLink.setEnabled(false);
+            // TODO: need to improve the styling, show as grayed out.
+            logoutLink.add(new CssClassAppender("disabled"));
+            Tooltips.addTooltip(logoutLink, "External");
+        }
+
     }
 
     private Class<? extends Page> getSignInPage() {


[isis] 01/02: ISIS-1267: adds keycloak module and extends AuthenticationSession

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a2c8518ea48b331f352c2e252bc9ada1576641ef
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Oct 25 07:59:51 2019 +0100

    ISIS-1267: adds keycloak module and extends AuthenticationSession
    
    in order to pass in through a wormhole to wicket.
---
 core/pom.xml                                       |   7 +
 .../runtime/web/AuthenticationSessionWormhole.java |  33 +++++
 .../authentication/AuthenticationSession.java      |  20 +++
 .../standard/AuthenticationManagerStandard.java    |   6 +
 .../authentication/standard/SimpleSession.java     |   7 +
 core/security/keycloak/NOTICE                      |   7 +
 core/security/keycloak/_adoc/antora.yml            |   2 +
 .../modules/shiro-realm-jdbc/_attributes.adoc      |   6 +
 .../modules/shiro-realm-jdbc/attachments/.gitkeep  |   0
 .../modules/shiro-realm-jdbc/examples/.gitkeep     |   0
 .../configure-shiro-to-use-custom-jdbc-realm.png   | Bin 0 -> 33149 bytes
 .../_adoc/modules/shiro-realm-jdbc/nav.adoc        |   1 +
 .../shiro-realm-jdbc/pages/_attributes.adoc        |   4 +
 .../modules/shiro-realm-jdbc/pages/about.adoc      | 111 ++++++++++++++++
 .../shiro-realm-jdbc/partials/_attributes.adoc     |   4 +
 .../shiro-realm-jdbc/partials/module-nav.adoc      |   1 +
 .../modules/shiro-realm-ldap/_attributes.adoc      |   6 +
 .../modules/shiro-realm-ldap/attachments/.gitkeep  |   0
 .../modules/shiro-realm-ldap/examples/.gitkeep     |   0
 .../images/activeds-ldap-groups.png                | Bin 0 -> 128224 bytes
 .../images/activeds-ldap-mojo-partition.png        | Bin 0 -> 123256 bytes
 .../images/activeds-ldap-mojo-root-dse.png         | Bin 0 -> 113723 bytes
 .../images/activeds-ldap-sasl-authentication.png   | Bin 0 -> 119544 bytes
 .../images/activeds-ldap-users.png                 | Bin 0 -> 123030 bytes
 .../configure-shiro-to-use-isis-ldap-realm.PNG     | Bin 0 -> 32310 bytes
 .../_adoc/modules/shiro-realm-ldap/nav.adoc        |   1 +
 .../shiro-realm-ldap/pages/_attributes.adoc        |   4 +
 .../modules/shiro-realm-ldap/pages/about.adoc      | 145 +++++++++++++++++++++
 .../shiro-realm-ldap/partials/_attributes.adoc     |   4 +
 .../shiro-realm-ldap/partials/module-nav.adoc      |   1 +
 .../keycloak/_adoc/modules/shiro/_attributes.adoc  |   6 +
 .../_adoc/modules/shiro/attachments/.gitkeep       |   0
 .../keycloak/_adoc/modules/shiro/examples/.gitkeep |   0
 .../ldap/activeds-ldap-groups.png                  | Bin 0 -> 128224 bytes
 .../ldap/activeds-ldap-mojo-partition.png          | Bin 0 -> 123256 bytes
 .../ldap/activeds-ldap-mojo-root-dse.png           | Bin 0 -> 113723 bytes
 .../ldap/activeds-ldap-sasl-authentication.png     | Bin 0 -> 119544 bytes
 .../configuring-shiro/ldap/activeds-ldap-users.png | Bin 0 -> 123030 bytes
 .../configure-isis-to-use-shiro.png                | Bin 0 -> 11356 bytes
 .../configure-shiro-to-use-custom-jdbc-realm.png   | Bin 0 -> 33149 bytes
 .../configure-shiro-to-use-ini-realm.PNG           | Bin 0 -> 29914 bytes
 .../configure-shiro-to-use-isis-ldap-realm.PNG     | Bin 0 -> 32310 bytes
 .../security/keycloak/_adoc/modules/shiro/nav.adoc |   1 +
 .../_adoc/modules/shiro/pages/_attributes.adoc     |   4 +
 .../keycloak/_adoc/modules/shiro/pages/about.adoc  |   6 +
 .../shiro/pages/configuring-isis-to-use-shiro.adoc | 110 ++++++++++++++++
 .../shiro/pages/enhanced-wildcard-permission.adoc  |  66 ++++++++++
 .../_adoc/modules/shiro/pages/ini-realm.adoc       | 142 ++++++++++++++++++++
 .../keycloak/_adoc/modules/shiro/pages/run-as.adoc | 101 ++++++++++++++
 .../_adoc/modules/shiro/pages/shiro-caching.adoc   |  41 ++++++
 .../_adoc/modules/shiro/partials/_attributes.adoc  |   4 +
 .../_adoc/modules/shiro/partials/module-nav.adoc   |  10 ++
 core/security/{shiro => keycloak}/pom.xml          |  29 +----
 .../appended-resources/supplemental-models.xml     |  44 +++++++
 .../keycloak/src/main/java/META-INF/MANIFEST.MF    |   3 +
 .../keycloak/IsisBootSecurityKeycloak.java         |  58 +++++++++
 .../isis/security/keycloak/KeycloakFilter.java     |  53 ++++++++
 .../isis/security/keycloak/WebModuleKeycloak.java  |  92 +++++++++++++
 .../authentication/KeycloakAuthenticator.java      |  66 ++++++++++
 .../keycloak/authorization/KeycloakAuthorizor.java |  61 +++++++++
 core/security/shiro/pom.xml                        |   2 +-
 .../wicket/AuthenticatedWebSessionForIsis.java     |  72 ++++++++--
 examples/apps/helloworld/pom.xml                   |  11 +-
 .../application/HelloWorldAppManifest.java         |   7 +-
 examples/apps/simpleapp/application/pom.xml        |  45 +++++--
 mavendeps/webapp/pom.xml                           |   6 -
 66 files changed, 1353 insertions(+), 57 deletions(-)

diff --git a/core/pom.xml b/core/pom.xml
index e1001ec..e13a295 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -1410,6 +1410,12 @@
 
 			<dependency>
 				<groupId>org.apache.isis.core</groupId>
+				<artifactId>isis-security-keycloak</artifactId>
+				<version>${isis.version}</version>
+			</dependency>
+
+			<dependency>
+				<groupId>org.apache.isis.core</groupId>
 				<artifactId>isis-plugins-codegen-bytebuddy</artifactId>
 				<version>${isis.version}</version>
 			</dependency>
@@ -2517,6 +2523,7 @@
 		<module>plugins/jdo/common</module>
 		<module>plugins/jdo/datanucleus-5</module>
 		<module>security/shiro</module>
+		<module>security/keycloak</module>
 
 		<!-- to break cyclic dependencies some tests needed to be moved to their 
 			own modules -->
diff --git a/core/runtime-web/src/main/java/org/apache/isis/core/runtime/web/AuthenticationSessionWormhole.java b/core/runtime-web/src/main/java/org/apache/isis/core/runtime/web/AuthenticationSessionWormhole.java
new file mode 100644
index 0000000..28e07d1
--- /dev/null
+++ b/core/runtime-web/src/main/java/org/apache/isis/core/runtime/web/AuthenticationSessionWormhole.java
@@ -0,0 +1,33 @@
+/*
+ *  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.core.runtime.web;
+
+import lombok.experimental.UtilityClass;
+
+import org.apache.isis.security.authentication.AuthenticationSession;
+
+/**
+ * Just a mechanism to pass an {@link AuthenticationSession} from a filter (eg keycloak) to a viewer (eg Wicket).
+ */
+@UtilityClass
+public class AuthenticationSessionWormhole {
+
+    public final static ThreadLocal<AuthenticationSession> sessionByThread = new ThreadLocal<>();
+
+}
diff --git a/core/security/api/src/main/java/org/apache/isis/security/authentication/AuthenticationSession.java b/core/security/api/src/main/java/org/apache/isis/security/authentication/AuthenticationSession.java
index 266d85f..bd3191b 100644
--- a/core/security/api/src/main/java/org/apache/isis/security/authentication/AuthenticationSession.java
+++ b/core/security/api/src/main/java/org/apache/isis/security/authentication/AuthenticationSession.java
@@ -26,6 +26,7 @@ import java.util.stream.Stream;
 
 import org.apache.isis.applib.security.UserMemento;
 import org.apache.isis.commons.internal.encoding.Encodable;
+import org.apache.isis.security.authentication.standard.AuthenticationManagerStandard;
 
 /**
  * The representation within the system of an authenticated user.
@@ -88,4 +89,23 @@ public interface AuthenticationSession extends Encodable, Serializable {
     MessageBroker getMessageBroker();
 
     UserMemento createUserMemento();
+
+    /**
+     * To support external security mechanisms such as keycloak, where the validity of the session is defined by
+     * headers in the request.
+     */
+    default Type getType() {
+        return Type.DEFAULT;
+    }
+
+    public enum Type {
+        DEFAULT,
+        /**
+         * Instructs the {@link AuthenticationManagerStandard} to not cache this session in its internal map of
+         * sessions by validation code, and therefore to ignore this aspect when considering if an
+         * {@link org.apache.isis.security.authentication.AuthenticationSession} is
+         * {@link org.apache.isis.security.authentication.manager.AuthenticationManager#isSessionValid(AuthenticationSession) valid} or not.
+         */
+        EXTERNAL
+    }
 }
diff --git a/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/AuthenticationManagerStandard.java b/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/AuthenticationManagerStandard.java
index 9c21c52..8cdf28b 100644
--- a/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/AuthenticationManagerStandard.java
+++ b/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/AuthenticationManagerStandard.java
@@ -142,6 +142,12 @@ public class AuthenticationManagerStandard implements AuthenticationManager {
 
     @Override
     public final boolean isSessionValid(final AuthenticationSession session) {
+        if(session instanceof SimpleSession) {
+            final SimpleSession simpleSession = (SimpleSession) session;
+            if(simpleSession.getType() == AuthenticationSession.Type.EXTERNAL) {
+                return true;
+            }
+        }
         final String userName = userByValidationCode.get(session.getValidationCode());
         return session.hasUserNameOf(userName);
     }
diff --git a/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/SimpleSession.java b/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/SimpleSession.java
index 27a2d8a..58edc13 100644
--- a/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/SimpleSession.java
+++ b/core/security/api/src/main/java/org/apache/isis/security/authentication/standard/SimpleSession.java
@@ -19,10 +19,14 @@
 
 package org.apache.isis.security.authentication.standard;
 
+import lombok.Getter;
+import lombok.Setter;
+
 import java.io.IOException;
 import java.util.stream.Stream;
 
 import org.apache.isis.commons.internal.encoding.DataInputExtended;
+import org.apache.isis.security.authentication.AuthenticationSession;
 import org.apache.isis.security.authentication.AuthenticationSessionAbstract;
 
 import static org.apache.isis.commons.internal.base._NullSafe.stream;
@@ -69,6 +73,9 @@ public class SimpleSession extends AuthenticationSessionAbstract {
         super(input);
     }
 
+    @Getter @Setter
+    private Type type = Type.DEFAULT;
+
     // ///////////////////////////////////////////////////////////////
     // equals, hashCode
     // ///////////////////////////////////////////////////////////////
diff --git a/core/security/keycloak/NOTICE b/core/security/keycloak/NOTICE
new file mode 100644
index 0000000..a93e145
--- /dev/null
+++ b/core/security/keycloak/NOTICE
@@ -0,0 +1,7 @@
+Apache Isis
+Copyright 2010-2014 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+
diff --git a/core/security/keycloak/_adoc/antora.yml b/core/security/keycloak/_adoc/antora.yml
new file mode 100644
index 0000000..09a6b1a
--- /dev/null
+++ b/core/security/keycloak/_adoc/antora.yml
@@ -0,0 +1,2 @@
+name: security
+version: master
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/_attributes.adoc
new file mode 100644
index 0000000..43cb529
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/_attributes.adoc
@@ -0,0 +1,6 @@
+ifndef::env-site,env-github[]
+:attachmentsdir: {moduledir}/attachments
+:examplesdir: {moduledir}/examples
+:imagesdir: {moduledir}/images
+:partialsdir: {moduledir}/partials
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/attachments/.gitkeep b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/attachments/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/examples/.gitkeep b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/examples/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/images/configure-shiro-to-use-custom-jdbc-realm.png b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/images/configure-shiro-to-use-custom-jdbc-realm.png
new file mode 100644
index 0000000..d64751a
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/images/configure-shiro-to-use-custom-jdbc-realm.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/nav.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/nav.adoc
new file mode 100644
index 0000000..4ec4e15
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/nav.adoc
@@ -0,0 +1 @@
+include::security:ROOT:partial$component-nav.adoc[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/pages/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/pages/_attributes.adoc
new file mode 100644
index 0000000..e8ada7c
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/pages/_attributes.adoc
@@ -0,0 +1,4 @@
+ifndef::env-site,env-github[]
+:moduledir: ..
+include::{moduledir}/_attributes.adoc[]
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/pages/about.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/pages/about.adoc
new file mode 100644
index 0000000..f8b6376
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/pages/about.adoc
@@ -0,0 +1,111 @@
+= Shiro JDBC Realm
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+
+There is nothing to stop you from using some other `Realm` implementation (or indeed writing one yourself).  For example, you could use Shiro's own JDBC realm that loads user/password details from a database.
+
+[WARNING]
+====
+If you are happy to use a database then we strongly recommend you use the xref:ext-secman:ROOT:about.adoc[SecMan extension] instead of a vanilla JDBC; it is far more sophisticated and moreover gives you the ability to administer the system from within your Apache Isis application.
+====
+
+If you go down this route, then the architecture is as follows:
+
+image::configure-shiro-to-use-custom-jdbc-realm.png[width="600px",link="{imagesdir}/configure-shiro-to-use-custom-jdbc-realm.png"]
+
+
+
+
+There's quite a lot of configuration required (in `WEB-INF/shiro.ini`) to set up a JDBC realm, so we'll break it out into sections.
+
+First, we need to set up the connection to JDBC:
+
+[source,ini]
+----
+jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm        # <1>
+
+jof = org.apache.shiro.jndi.JndiObjectFactory          # <2>
+jof.resourceName = jdbc/postgres                       # <3>
+jof.requiredType = javax.sql.DataSource
+jof.resourceRef = true
+
+jdbcRealm.dataSource = $jof                            # <4>
+----
+<1> instantiate the JDBC realm
+<2> instantiate factory object to lookup DataSource from servlet container
+<3> name of the datasource (as configured in `web.xml`)
+<4> instruct JDBC realm to obtain datasource from the JNDI
+
+
+We next need to tell the realm how to query the database.  Shiro supports any schema; what matters is the input search argument and the output results.
+
+[source,ini]
+----
+
+jdbcRealm.authenticationQuery =         \              # <1>
+        select password                 \
+          from users                    \
+         where username = ?
+
+jdbcRealm.userRolesQuery =              \              # <2>
+        select r.label                  \
+          from users_roles ur           \
+    inner join roles r                  \
+            on ur.role_id = r.id        \
+         where user_id = (              \
+            select id                   \
+             from users                 \
+            where username = ?);        \
+
+jdbcRealm.permissionsQuery=             \               # <3>
+        select p.permission             \
+          from roles_permissions rp     \
+    inner join permissions p            \
+            on rp.permission_id = p.id  \
+         where rp.role_id = (           \
+            select id                   \
+             from roles                 \
+            where label = ?);
+
+jdbcRealm.permissionsLookupEnabled=true                 # <4>
+----
+<1> query to find password for user
+<2> query to find roles for user
+<3> query to find permissions for role
+<4> enable permissions lookup
+
+[WARNING]
+====
+The `permissionsLookupEnabled` is very important, otherwise Shiro just returns an empty list of permissions and your users will have no access to any features(!).
+====
+
+We also should ensure that the passwords are not stored as plain-text:
+
+[source,ini]
+----
+dps = org.apache.shiro.authc.credential.DefaultPasswordService   # <1>
+pm = org.apache.shiro.authc.credential.PasswordMatcher           # <2>
+pm.passwordService = $dps
+jdbcRealm.credentialsMatcher = $pm                               # <3>
+----
+<1> mechanism to encrypts password
+<2> service to match passwords
+<3> instruct JDBC realm to use password matching service when authenticating
+
+
+And finally we need to tell Shiro to use the realm, in the usual fashion:
+
+[source,ini]
+----
+securityManager.realms = $jdbcRealm
+----
+
+Using the above configuration you will also need to setup a `DataSource`.  The details vary by servlet container, for example this is link:https://tomcat.apache.org/tomcat-8.0-doc/jndi-datasource-examples-howto.html[how to do the setup on Tomcat 8.0].
+
+[WARNING]
+====
+The name of the `DataSource` can also vary by servlet container; see for example link:http://stackoverflow.com/questions/17441019/how-to-configure-jdbcrealm-to-obtain-its-datasource-from-jndi/23784702#23784702[this StackOverflow answer].
+====
+
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/partials/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/partials/_attributes.adoc
new file mode 100644
index 0000000..e8ada7c
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/partials/_attributes.adoc
@@ -0,0 +1,4 @@
+ifndef::env-site,env-github[]
+:moduledir: ..
+include::{moduledir}/_attributes.adoc[]
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/partials/module-nav.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/partials/module-nav.adoc
new file mode 100644
index 0000000..c9d2bc7
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-jdbc/partials/module-nav.adoc
@@ -0,0 +1 @@
+// this module's pages are referenced by security-shiro module
\ No newline at end of file
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/_attributes.adoc
new file mode 100644
index 0000000..43cb529
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/_attributes.adoc
@@ -0,0 +1,6 @@
+ifndef::env-site,env-github[]
+:attachmentsdir: {moduledir}/attachments
+:examplesdir: {moduledir}/examples
+:imagesdir: {moduledir}/images
+:partialsdir: {moduledir}/partials
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/attachments/.gitkeep b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/attachments/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/examples/.gitkeep b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/examples/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-groups.png b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-groups.png
new file mode 100644
index 0000000..0496a84
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-groups.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-mojo-partition.png b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-mojo-partition.png
new file mode 100644
index 0000000..42d7480
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-mojo-partition.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-mojo-root-dse.png b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-mojo-root-dse.png
new file mode 100644
index 0000000..9c4c480
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-mojo-root-dse.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-sasl-authentication.png b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-sasl-authentication.png
new file mode 100644
index 0000000..91d1a61
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-sasl-authentication.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-users.png b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-users.png
new file mode 100644
index 0000000..7ff133c
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/activeds-ldap-users.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/configure-shiro-to-use-isis-ldap-realm.PNG b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/configure-shiro-to-use-isis-ldap-realm.PNG
new file mode 100644
index 0000000..2a52910
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/images/configure-shiro-to-use-isis-ldap-realm.PNG differ
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/nav.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/nav.adoc
new file mode 100644
index 0000000..4ec4e15
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/nav.adoc
@@ -0,0 +1 @@
+include::security:ROOT:partial$component-nav.adoc[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/pages/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/pages/_attributes.adoc
new file mode 100644
index 0000000..e8ada7c
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/pages/_attributes.adoc
@@ -0,0 +1,4 @@
+ifndef::env-site,env-github[]
+:moduledir: ..
+include::{moduledir}/_attributes.adoc[]
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/pages/about.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/pages/about.adoc
new file mode 100644
index 0000000..5aecbbf
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/pages/about.adoc
@@ -0,0 +1,145 @@
+= Ldap Realm
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+
+Apache Isis ships with an implementation of http://shiro.apache.org[Apache Shiro]'s `Realm` class that allows user authentication and authorization to be performed against an LDAP server.
+
+image::configure-shiro-to-use-isis-ldap-realm.PNG[width="600px",link="{imagesdir}/configure-shiro-to-use-isis-ldap-realm.PNG"]
+
+The LDAP database stores the user/passwords and user groups, while the `shiro.ini` file is used to map the LDAP groups to roles, and to map the roles to permissions.
+
+== Shiro Configuration
+
+To use LDAP involves telling Shiro how to instantiate the realm.  This bootstrapping info lives in the `WEB-INF/shiro.ini`:
+
+[source,ini]
+----
+contextFactory = org.apache.isis.security.shiro.IsisLdapContextFactory
+contextFactory.url = ldap://localhost:10389
+contextFactory.systemUsername = uid=admin,ou=system        # <1>
+contextFactory.systemPassword = secret
+contextFactory.authenticationMechanism = CRAM-MD5          # <2>
+contextFactory.systemAuthenticationMechanism = simple
+
+ldapRealm = org.apache.isis.security.shiro.IsisLdapRealm   # <3>
+ldapRealm.contextFactory = $contextFactory
+
+ldapRealm.searchBase = ou=groups,o=mojo                    # <4>
+ldapRealm.groupObjectClass = groupOfUniqueNames            # <5>
+ldapRealm.uniqueMemberAttribute = uniqueMember             # <6>
+ldapRealm.uniqueMemberAttributeValueTemplate = uid={0}
+
+# optional mapping from physical groups to logical application roles
+ldapRealm.rolesByGroup = \                                 # <7>
+    LDN_USERS: user_role,\
+    NYK_USERS: user_role,\
+    HKG_USERS: user_role,\
+    GLOBAL_ADMIN: admin_role,\
+    DEMOS: self-install_role
+
+ldapRealm.permissionsByRole=\                              # <8>
+   user_role = *:ToDoItemsJdo:*:*,\
+               *:ToDoItem:*:*; \
+   self-install_role = *:ToDoItemsFixturesService:install:* ; \
+   admin_role = *
+
+securityManager.realms = $ldapRealm
+----
+<1> user accounts are searched using a dedicated service account
+<2> SASL (CRAM-MD5) authentication is used for this authentication
+<3> Apache Isis' implementation of the LDAP realm.
+<4> groups are searched under `ou=groups,o=mojo` (where `mojo` is the company name)
+<5> each group has an LDAP objectClass of `groupOfUniqueNames`
+<6> each group has a vector attribute of `uniqueMember`
+<7> groups looked up from LDAP can optionally be mapped to logical roles; otherwise groups are used as role names directly
+<8> roles are mapped in turn to permissions
+
+The value of `uniqueMember` is in the form `uid=xxx`, with `xxx` being the uid of the user
+* users searched under `ou=system`
+* users have, at minimum, a `uid` attribute and a password
+* the users credentials are used to verify their user/password
+
+The above configuration has been tested against http://directory.apache.org/apacheds/[ApacheDS], v1.5.7. This can be administered using http://directory.apache.org/studio/[Apache Directory Studio], v1.5.3.
+
+
+[TIP]
+.Shiro Realm Mappings
+====
+When configuring role based permission mapping, there can only be one of these entries per realm:
+
+[source,ini]
+----
+realm.groupToRolesMappings = ...
+----
+
+and
+
+[source,ini]
+----
+realm.roleToPermissionsMappings = ...
+----
+
+This forces you to put everything on one line for each of the above.  This is, unfortunately, a Shiro "feature".  And if you repeat the entries above then it's "last one wins".)
+
+To make the configuration maintainable, use "\" to separate the mappings onto separate lines in the file.  Use this technique for both group to roles mapping and role to permission mapping. If you use the '&#39; after the "," that separates the key:value pairs it is more readable.
+====
+
+
+
+
+
+
+== Externalizing role perms
+
+As an alternative to injecting the `permissionsByRole` property, the role/permission mapping can alternatively be specified by injecting a resource path:
+
+[source,ini]
+----
+ldapRealm.resourcePath=classpath:webapp/myroles.ini
+----
+
+where `myroles.ini` is in `src/main/resources/webapp`, and takes the form:
+
+[source,ini]
+----
+[roles]
+user_role = *:ToDoItemsJdo:*:*,\
+            *:ToDoItem:*:*
+self-install_role = *:ToDoItemsFixturesService:install:*
+admin_role = *
+----
+
+This separation of the role/mapping can be useful if Shiro is configured to support multiple realms (eg an LdapRealm based one and also an TextRealm)
+
+
+
+
+== Active DS LDAP tutorial
+
+The screenshots below show how to setup LDAP accounts in ApacheDS using the Apache Directory Studio.
+
+The setup here was initially based on http://krams915.blogspot.co.uk/2011/01/ldap-apache-directory-studio-basic.html[this tutorial], however we have moved the user accounts so that they are defined in a separate LDAP node.
+
+To start, create a partition in order to hold the mojo node (holding the groups):
+
+image::activeds-ldap-mojo-partition.png[link="{imagesdir}/activeds-ldap-mojo-partition.png"]
+
+Create the `ou=groups,o=mojo` hierarchy:
+
+image::activeds-ldap-mojo-root-dse.png[link="{imagesdir}/activeds-ldap-mojo-root-dse.png"]
+
+Configure SASL authentication. This means that the checking of user/password is done implicitly by virtue of Apache Isis connecting to LDAP using these credentials:
+
+image::activeds-ldap-sasl-authentication.png[link="{imagesdir}/activeds-ldap-sas"]
+
+In order for SASL to work, it seems to be necessary to put users under `o=system`. (This is why the setup is slightly different than the tutorial mentioned above):
+
+image::activeds-ldap-users.png[link="{imagesdir}/activeds-ldap-users.png"]
+
+Configure the users into the groups:
+
+image::activeds-ldap-groups.png[link="{imagesdir}/activeds-ldap-groups.png"]
+
+
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/partials/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/partials/_attributes.adoc
new file mode 100644
index 0000000..e8ada7c
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/partials/_attributes.adoc
@@ -0,0 +1,4 @@
+ifndef::env-site,env-github[]
+:moduledir: ..
+include::{moduledir}/_attributes.adoc[]
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro-realm-ldap/partials/module-nav.adoc b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/partials/module-nav.adoc
new file mode 100644
index 0000000..c9d2bc7
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro-realm-ldap/partials/module-nav.adoc
@@ -0,0 +1 @@
+// this module's pages are referenced by security-shiro module
\ No newline at end of file
diff --git a/core/security/keycloak/_adoc/modules/shiro/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro/_attributes.adoc
new file mode 100644
index 0000000..43cb529
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/_attributes.adoc
@@ -0,0 +1,6 @@
+ifndef::env-site,env-github[]
+:attachmentsdir: {moduledir}/attachments
+:examplesdir: {moduledir}/examples
+:imagesdir: {moduledir}/images
+:partialsdir: {moduledir}/partials
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro/attachments/.gitkeep b/core/security/keycloak/_adoc/modules/shiro/attachments/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/core/security/keycloak/_adoc/modules/shiro/examples/.gitkeep b/core/security/keycloak/_adoc/modules/shiro/examples/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-groups.png b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-groups.png
new file mode 100644
index 0000000..0496a84
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-groups.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-mojo-partition.png b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-mojo-partition.png
new file mode 100644
index 0000000..42d7480
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-mojo-partition.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-mojo-root-dse.png b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-mojo-root-dse.png
new file mode 100644
index 0000000..9c4c480
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-mojo-root-dse.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-sasl-authentication.png b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-sasl-authentication.png
new file mode 100644
index 0000000..91d1a61
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-sasl-authentication.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-users.png b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-users.png
new file mode 100644
index 0000000..7ff133c
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/configuration/configuring-shiro/ldap/activeds-ldap-users.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-isis-to-use-shiro.png b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-isis-to-use-shiro.png
new file mode 100644
index 0000000..891d373
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-isis-to-use-shiro.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-custom-jdbc-realm.png b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-custom-jdbc-realm.png
new file mode 100644
index 0000000..d64751a
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-custom-jdbc-realm.png differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-ini-realm.PNG b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-ini-realm.PNG
new file mode 100644
index 0000000..5576701
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-ini-realm.PNG differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-isis-ldap-realm.PNG b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-isis-ldap-realm.PNG
new file mode 100644
index 0000000..2a52910
Binary files /dev/null and b/core/security/keycloak/_adoc/modules/shiro/images/security/security-apis-impl/configure-shiro-to-use-isis-ldap-realm.PNG differ
diff --git a/core/security/keycloak/_adoc/modules/shiro/nav.adoc b/core/security/keycloak/_adoc/modules/shiro/nav.adoc
new file mode 100644
index 0000000..4ec4e15
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/nav.adoc
@@ -0,0 +1 @@
+include::security:ROOT:partial$component-nav.adoc[]
diff --git a/core/security/keycloak/_adoc/modules/shiro/pages/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro/pages/_attributes.adoc
new file mode 100644
index 0000000..e8ada7c
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/pages/_attributes.adoc
@@ -0,0 +1,4 @@
+ifndef::env-site,env-github[]
+:moduledir: ..
+include::{moduledir}/_attributes.adoc[]
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro/pages/about.adoc b/core/security/keycloak/_adoc/modules/shiro/pages/about.adoc
new file mode 100644
index 0000000..54d76c5
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/pages/about.adoc
@@ -0,0 +1,6 @@
+= Shiro Security
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+This guide describes the configuration of the Shiro implementation of Apache Isis' `Authenticator and `Authorizor` APIs.
diff --git a/core/security/keycloak/_adoc/modules/shiro/pages/configuring-isis-to-use-shiro.adoc b/core/security/keycloak/_adoc/modules/shiro/pages/configuring-isis-to-use-shiro.adoc
new file mode 100644
index 0000000..7310e1a
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/pages/configuring-isis-to-use-shiro.adoc
@@ -0,0 +1,110 @@
+[[configuring-isis-to-use-shiro]]
+= Configuring to use Shiro
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+
+Apache Isis' security mechanism is configurable, specifying an `Authenticator` and an `Authorizor` (non-public) APIs.
+The Shiro security mechanism is an integration wih Apache Shiro that implements both interfaces.
+
+[TIP]
+====
+Both the xref:helloworld:ROOT:about.adoc[HelloWorld] and xref:simpleapp:ROOT:about.adoc[SimpleApp] archetypes are pre-configured to use Apache Shiro, so much of what follows may well have been set up already.
+====
+
+
+== Telling Apache Isis to use Shiro
+
+To tell Apache Isis to use Shiro when using an xref:rg:cms:rgcms.adoc#__rgcms_classes_AppManifest-bootstrapping_bootstrapping_AppManifestAbstract[`AppManifestAbstract.BUILDER`], simply specify the "authMechanism" as "shiro".
+
+For example, the xref:helloworld:ROOT:about.adoc[HelloWorld archetype] bootstraps using:
+
+[source,java]
+----
+public class HelloWorldAppManifest extends AppManifestAbstract {
+
+    public static final Builder BUILDER = Builder
+            .forModules(HelloWorldModule.class)
+            .withAuthMechanism("shiro");                        // <1>
+            ...
+}
+----
+<1> configures Shiro.
+
+This installs the appropriate implementation (the `ShiroAuthenticatorOrAuthorizor` class) that use Shiro's APIs to perform authentication and authorization:
+
+image::security/security-apis-impl/configure-isis-to-use-shiro.png[width="600px",link="{imagesdir}/security/security-apis-impl/configure-isis-to-use-shiro.png"]
+
+The figure above doesn't tell the whole story; we haven't yet seen how Shiro itself is configured to use realms.
+The `ShiroAuthenticatorOrAuthorizor` is in essence the glue between the Apache Isis runtime and Shiro.
+
+
+== Configuring Shiro Authenticator
+
+The `ShiroAuthenticatorOrAuthorizor` class itself supports a single optional property.
+This can be configured in `authentication_shiro.properties` file:
+
+[source,ini]
+----
+isis.authentication.shiro.autoLogoutIfAlreadyAuthenticated=false
+----
+
+This configuration property only comes into effect for the xref:vro:ROOT:about.adoc[Restful Objects viewer]; if set then the Shiro subject - if found to be still authenticated - will be logged out anyway and then re-authenticated.
+
+[WARNING]
+====
+There should generally be no need to change this property from its default (`false`).
+Setting it to `true` may cause a race condition resulting in exceptions being logged.
+====
+
+
+
+== Bootstrapping Shiro
+
+TODO: v2: this configuration is no longer required in `web.xml`, instead is configured programmatically by the framework (`WebModule_Shiro`).
+
+
+The Shiro environment (in essence, thread-locals holding the security credentials) needs to be bootstrapped using the following settings in the `WEB-INF/web.xml` file:
+
+[source,xml]
+----
+<listener>
+    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
+</listener>
+<filter>
+    <filter-name>ShiroFilter</filter-name>
+    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
+</filter>
+<filter-mapping>
+    <filter-name>ShiroFilter</filter-name>
+    <url-pattern>/*</url-pattern>
+</filter-mapping>
+----
+
+Based on this Shiro will then read `WEB-INF/shiro.ini` file to configure its Realm definitions for authentication and authorization.
+
+
+
+== `WEB-INF/shiro.ini`
+
+The `shiro.ini` file is used to specify the realm(s) that Shiro will delegate to:
+
+[source,ini]
+----
+securityManager.realms = $realmName
+----
+
+Shiro's ini file supports a "poor-man's" dependency injection (link:https://shiro.apache.org/configuration.html[their words]), and so `$realmName` in the above example is a reference to a realm defined elsewhere in `shiro.ini`.
+The subsequent sections describe the specifics for thevarious realm implementations available to you.
+
+
+It's also possible to configure Shiro to support multiple realms.
+
+[source,ini]
+----
+securityManager.realms = $realm1,$realm2
+----
+
+You can learn more about Shiro realms in the link:http://shiro.apache.org/realm.html[Shiro documentation].
+
diff --git a/core/security/keycloak/_adoc/modules/shiro/pages/enhanced-wildcard-permission.adoc b/core/security/keycloak/_adoc/modules/shiro/pages/enhanced-wildcard-permission.adoc
new file mode 100644
index 0000000..e1f4d6c
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/pages/enhanced-wildcard-permission.adoc
@@ -0,0 +1,66 @@
+[[enhanced-wildcard-permission]]
+= Enhanced Wildcard Permission
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+
+If using the text-based xref:security:shiro:ini-realm.adoc[`IniRealm`] or xref:security:shiro-realm-ldap:about.adoc[Apache Isis' LDAP realm], then note that Shiro also allows the string representation of the permissions to be mapped (resolved) to alternative `Permission` instances.
+Apache Isis provides its own `IsisPermission` which introduces the concept of a "veto".
+
+A vetoing permission is one that prevents access to a feature, rather than grants it.
+This is useful in some situations where most users have access to most features, and only a small number of features are particularly sensitive.
+The configuration can therefore be set up to grant fairly broad-brush permissions and then veto permission for the sensitive features for those users that do not have access.
+
+The string representation of the `IsisPermission` uses the following format:
+
+[source,ini]
+----
+([!]?)([^/]+)[/](.+)
+----
+
+where:
+
+* the optional `!` prefix indicates this permission is a vetoing permission
+* the optional `xxx/` prefix is a permission group that scopes any vetoing permissions
+* the remainder of the string is the permission (possibly wild-carded, with :rw as optional suffix)
+
+
+For example:
+
+[source,ini]
+----
+user_role   = !reg/org.estatio.api,\
+              !reg/org.estatio.webapp.services.admin,\
+              reg/* ; \
+api_role    = org.estatio.api ;\
+admin_role = adm/*
+----
+
+sets up:
+
+* the `user_role` with access to all permissions except those in `org.estatio.api` and `org.estatio.webapp.services.admin`
+* the `api_role` with access to all permissions in `org.estatio.api`
+* the `admin_role` with access to everything.
+
+The permission group concept is required to scope the applicability of any veto permission.
+This is probably best explained by an example.
+Suppose that a user has both `admin_role` and `user_role`; we would want the `admin_role` to trump the vetos of the `user_role`, in other words to give the user access to everything.
+
+
+Because of the permission groups, the two "+++!reg/...+""" vetos in user_role only veto out selected permissions granted by the "+++reg/*+++" permissions, but they do not veto the permissions granted by a different scope, namely "+++adm/*+++".
+
+The net effect is therefore what we would want: that a user with both `admin_role` and `user_role` would have access to everything, irrespective of those two veto permissions of the `user_role`.
+
+
+
+Finally, the Apache Isis permission resolver is specified in `WEB-INF/shiro.ini` file:
+
+[source,ini]
+----
+permissionResolver = org.apache.isis.security.shiro.authorization.IsisPermissionResolver
+myRealm.permissionResolver = $permissionResolver  # <1>
+----
+<1> `myRealm` is the handle to the configured realm, eg `$iniRealm` or `$isisLdapRealm` etc.
+
+
diff --git a/core/security/keycloak/_adoc/modules/shiro/pages/ini-realm.adoc b/core/security/keycloak/_adoc/modules/shiro/pages/ini-realm.adoc
new file mode 100644
index 0000000..6190188
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/pages/ini-realm.adoc
@@ -0,0 +1,142 @@
+[[ini-realm]]
+= Shiro Ini Realm
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+
+The Shiro concept of a `Realm` allows different implementations of both the authentication and authorisation mechanism to be plugged in.
+
+Probably the simplest realm to use is Shiro's built-in `IniRealm`, which reads from the (same) `WEB-INF/shiro.ini` file.
+
+This is suitable for prototyping, but isn't intended for production use, if only because user/password credentials are stored in plain text.
+Nevertheless, it's a good starting point.
+The app generated by both the xref:helloworld:ROOT:about.adoc[HelloWorld] and xref:simpleapp:ROOT:about.adoc[SimpleApp]  archetypes are configured to use this realm.
+
+The diagram below shows the Isis and components involved:
+
+image::security/security-apis-impl/configure-shiro-to-use-ini-realm.PNG[width="600px",link="{imagesdir}/security/security-apis-impl/configure-shiro-to-use-ini-realm.PNG"]
+
+The realm is responsible for validating the user credentials, and then creates a Shiro link:http://shiro.apache.org/static/latest/apidocs/org/apache/shiro/subject/Subject.html[`Subject`] which represents the user (for the current request).
+Apache Isis `Authenticator` component then interacts with the `Subject` in order to check permissions.
+
+
+
+
+== Shiro Configuration
+
+To use the built-in `IniRealm`, we add the following to `WEB-INF/shiro.ini`:
+
+[source,ini]
+----
+securityManager.realms = $iniRealm
+----
+
+(Unlike other realms) there is no need to "define" `$iniRealm`; it is automatically available to us.
+
+Specifying `$iniRealm` means that the usernames/passwords, roles and permissions are read from the `shiro.ini` file itself.
+Specifically:
+
+* the users/passwords and their roles from the `[users]` sections;
+* the roles are mapped to permissions in the `[roles]` section.
+
+The format of these is described below.
+
+=== `[users]` section
+
+This section lists users, passwords and their roles.
+
+For example:
+
+[source,ini]
+----
+sven = pass, admin_role
+dick = pass, user_role, analysis_role, self-install_role
+bob  = pass, user_role, self-install_role
+----
+The first value is the password (eg "pass", the remaining values are the role(s).
+
+
+=== `[roles]` section
+
+This section lists roles and their corresponding permissions.
+
+For example:
+
+[source,ini]
+----
+user_role = *:ToDoItems:*:*,\
+            *:ToDoItem:*:*,\
+            *:ToDoAppDashboard:*:*
+analysis_role = *:ToDoItemAnalysis:*:*,\
+            *:ToDoItemsByCategoryViewModel:*:*,\
+            *:ToDoItemsByDateRangeViewModel:*:*
+self-install_role = *:ToDoItemsFixturesService:install:*
+admin_role = *
+----
+
+The value is a comma-separated list of permissions for the role.  The format is:
+
+[source,ini]
+----
+packageName:className:memberName:r,w
+----
+
+where:
+
+* `memberName` is the property, collection or action name.
+* `r` indicates that the member is visible
+* `w` indicates that the member is usable (editable or invokable)
+
+and where each of the parts of the permission string can be wildcarded using `*`.
+
+Because these are wildcards, a '*' can be used at any level. Additionally, missing levels assume wildcards.
+
+Thus:
+
+[source,ini]
+----
+com.mycompany.myapp:Customer:firstName:r,w   # view or edit customer's firstName
+com.mycompany.myapp:Customer:lastName:r      # view customer's lastName only
+com.mycompany.myapp:Customer:placeOrder:*    # view and invoke placeOrder action
+com.mycompany.myapp:Customer:placeOrder      # ditto
+com.mycompany.myapp:Customer:*:r             # view all customer class members
+com.mycompany.myapp:*:*:r                    # view-only access for all classes in myapp package
+com.mycompany.myapp:*:*:*                    # view/edit for all classes in myapp package
+com.mycompany.myapp:*:*                      # ditto
+com.mycompany.myapp:*                        # ditto
+com.mycompany.myapp                          # ditto
+*                                            # view/edit access to everything
+----
+
+[TIP]
+====
+The format of the permissions string is configurable in Shiro, and Apache Isis uses this to provide an extended wildcard format, described xref:security:ROOT:enhanced-wildcard-permission.adoc[here].
+====
+
+
+
+
+== Externalized IniRealm
+
+There's no requirement for all users/roles to be defined in the `shiro.ini` file.
+Instead, a realm can be defined that loads its users/roles from some other resource.
+
+For example:
+
+[source,ini]
+----
+$realm1=org.apache.shiro.realm.text.IniRealm # <1>
+realm1.resourcePath=classpath:webapp/realm1.ini # <2>
+----
+<1> happens to (coincidentally) be the link:http://shiro.apache.org/static/latest/apidocs/org/apache/shiro/realm/text/IniRealm.html[same implementation] as Shiro's built-in $iniRealm
+<2> in this case load the users/roles from the `src/main/resources/webapp/realm1.ini` file.
+
+Note that a URL could be provided as the `resourcePath`, so a centralized config file could be used.
+Even so, the
+
+[NOTE]
+====
+If configured this way then the `[users]` and `[roles]` sections of `shiro.ini` become unused.
+Instead, the corresponding sections from for `realm1.ini` are used instead.
+====
diff --git a/core/security/keycloak/_adoc/modules/shiro/pages/run-as.adoc b/core/security/keycloak/_adoc/modules/shiro/pages/run-as.adoc
new file mode 100644
index 0000000..7d1e488
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/pages/run-as.adoc
@@ -0,0 +1,101 @@
+[[run-as]]
+= Run-as
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+
+This hint shows how to temporarily change the current user as reported by Shiro.
+This can be useful to support "Run As", for example.
+
+The heavy lifting is done in `ShiroService`:
+
+[source,java]
+----
+@DomainService(nature = NatureOfService.DOMAIN)
+public class ShiroService {
+
+    public void runAs(String userName) {
+        SimplePrincipalCollection principals =
+            new SimplePrincipalCollection(userName, "jdbcRealm");                       // <1>
+        getSubject().runAs(principals);
+    }
+
+    public String releaseRunAs() {
+        final PrincipalCollection principals = getSubject().releaseRunAs();
+        String username = (String)principals.asList().get(0);
+        return username;
+    }
+
+    public String getUsername() {                                                       // <2>
+        String principalAsString = ((String)getSubject().getPrincipal());
+        return principalAsString.toLowerCase();
+    }
+
+    public String getRealUsername() {                                                   // <3>
+        return userService.getUser().getName().toLowerCase();
+    }
+
+    public boolean isRunAs() {
+        return getSubject().isRunAs();
+    }
+
+    private static Subject getSubject() {
+        return org.apache.shiro.SecurityUtils.getSubject();
+    }
+
+    @Inject
+    private UserService userService;
+}
+----
+<1> "jdbcRealm" is realm as configured in Shiro config (shiro.ini).
+Might want to look this up from `ConfigurationService`.
+<2> The username of the currently logged in user (by which permissions are determined).
+This could be the user name the real user is running as.
+<3> The username of the real currently logged in user.
+
+
+This could be exposed in the UI using a simple `RunAsService`, for example:
+
+
+[source,java]
+----
+@DomainService(nature = NatureOfService.VIEW_MENU_ONLY)
+@DomainServiceLayout(menuBar = DomainServiceLayout.MenuBar.TERTIARY)
+public class RunAsService {
+
+    public Dashboard runAs(User user) {
+        shiroService.runAs(user.getUsername());
+        return dashboardService.openDashboard();                    // <1>
+    }
+    public List<User> choices0RunAs() {
+        return ...                                                  // <2>
+    }
+    public boolean hideRunAs() {
+        return shiroService.isRunAs();
+    }
+
+
+
+    public User releaseRunAs() {
+        String username = shiroService.releaseRunAs();
+        return usersRepository.findByUsername(username);
+    }
+    public boolean hideReleaseRunAs() {
+        return !shiroService.isRunAs();
+    }
+
+
+    @Inject
+    private ShiroService shiroService;
+    @Inject
+    private UsersRepository usersRepository;
+    @Inject
+    private DashboardService dashboardService;                      // <1>
+}
+----
+<1> go to the home page (application-specific)
+<2> return a list of users to run as
+
+
+Credits: adapted from link:https://gist.github.com/erikdehair/efa3005440ca982cca41ebe5347e82d8[this gist].
diff --git a/core/security/keycloak/_adoc/modules/shiro/pages/shiro-caching.adoc b/core/security/keycloak/_adoc/modules/shiro/pages/shiro-caching.adoc
new file mode 100644
index 0000000..b4454c0
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/pages/shiro-caching.adoc
@@ -0,0 +1,41 @@
+[[shiro-caching]]
+= Caching and other Shiro Features
+:Notice: 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 ag [...]
+include::_attributes.adoc[]
+:page-partial:
+
+
+We don't want to repeat the entire link:http://shiro.apache.org/documentation.html[Shiro documentation set] here, but we should flag a number of other features that are worth checking out.
+
+
+
+
+== Caching
+
+To ensure that security operations does not impede performance, Shiro supports caching.  For example, this sets up a simple memory-based cache manager:
+
+[source,ini]
+----
+memoryCacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
+securityManager.cacheManager = $memoryCacheManager
+----
+
+Other implementations can be plugged in; see the Shiro link:http://shiro.apache.org/caching.html[documentation] for further details.
+
+
+
+
+== Further Reading
+
+
+* Shiro's documentation page can be found link:http://shiro.apache.org/documentation.html[here].
+
+* community-contributed articles can be found link:http://shiro.apache.org/articles.html[here]. +
++
+These include for instance link:http://meri-stuff.blogspot.co.uk/2011/04/apache-shiro-part-2-realms-database-and.html[this interesting article] describing how to perform certificate-based authentication (ie login using Google or Facebook credentials).
+
+
+
+
+
+
diff --git a/core/security/keycloak/_adoc/modules/shiro/partials/_attributes.adoc b/core/security/keycloak/_adoc/modules/shiro/partials/_attributes.adoc
new file mode 100644
index 0000000..e8ada7c
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/partials/_attributes.adoc
@@ -0,0 +1,4 @@
+ifndef::env-site,env-github[]
+:moduledir: ..
+include::{moduledir}/_attributes.adoc[]
+endif::[]
diff --git a/core/security/keycloak/_adoc/modules/shiro/partials/module-nav.adoc b/core/security/keycloak/_adoc/modules/shiro/partials/module-nav.adoc
new file mode 100644
index 0000000..a209cde
--- /dev/null
+++ b/core/security/keycloak/_adoc/modules/shiro/partials/module-nav.adoc
@@ -0,0 +1,10 @@
+* Shiro Implementation
+
+** xref:security:shiro:configuring-isis-to-use-shiro.adoc[Configuring to use Shiro]
+** Realms
+*** xref:security:shiro:ini-realm.adoc[INI Realm]
+*** xref:security:shiro-realm-jdbc:about.adoc[JDBC Realm]
+*** xref:security:shiro-realm-ldap:about.adoc[LDAP Realm]
+** xref:security:shiro:enhanced-wildcard-permission.adoc[Enhanced Wildcard Permissions]
+** xref:security:shiro:run-as.adoc[Run As]
+** xref:security:shiro:shiro-caching.adoc[Shiro Caching]
diff --git a/core/security/shiro/pom.xml b/core/security/keycloak/pom.xml
similarity index 87%
copy from core/security/shiro/pom.xml
copy to core/security/keycloak/pom.xml
index 580c0cc..ad44a2b 100644
--- a/core/security/shiro/pom.xml
+++ b/core/security/keycloak/pom.xml
@@ -27,16 +27,16 @@
     	<relativePath>../../pom.xml</relativePath>
     </parent>
 
-    <artifactId>isis-security-shiro</artifactId>
+    <artifactId>isis-security-keycloak</artifactId>
 
-    <name>Apache Isis Plugin for Security (Shiro)</name>
+    <name>Apache Isis Extension for Security (Keycloak)</name>
 	<description>
-        Authentication and Authorization using Apache Shiro.
+        Authentication and Authorization using Keycloak
     </description>
 
     <properties>
-        <jar-plugin.automaticModuleName>org.apache.isis.plugins.security.shiro</jar-plugin.automaticModuleName>
-        <git-plugin.propertiesDir>org/apache/isis/plugins/security/shiro</git-plugin.propertiesDir>
+        <jar-plugin.automaticModuleName>org.apache.isis.plugins.security.keycloak</jar-plugin.automaticModuleName>
+        <git-plugin.propertiesDir>org/apache/isis/plugins/security/keycloak</git-plugin.propertiesDir>
     </properties>
 
 
@@ -72,7 +72,7 @@
     	<dependencies>
             <dependency>
                 <groupId>org.apache.isis.core</groupId>
-                <artifactId>isis-security-shiro</artifactId>
+                <artifactId>isis-security-keycloak</artifactId>
                 <version>${isis.version}</version>
             </dependency>
     	</dependencies>
@@ -89,7 +89,7 @@
             <groupId>org.apache.isis.core</groupId>
             <artifactId>isis-runtime-web</artifactId>
         </dependency>
-	
+
         <dependency>
             <groupId>org.apache.isis.core</groupId>
             <artifactId>isis-runtime</artifactId>
@@ -102,21 +102,6 @@
             <scope>test</scope>
         </dependency>
 	
-        <dependency>
-            <groupId>org.apache.shiro</groupId>
-            <artifactId>shiro-core</artifactId>
-            <exclusions>
-            	<exclusion>
-            		<groupId>org.slf4j</groupId>
-            		<artifactId>slf4j-api</artifactId>
-            	</exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.shiro</groupId>
-            <artifactId>shiro-web</artifactId>
-        </dependency>
-
 
 
     </dependencies>
diff --git a/core/security/keycloak/src/main/appended-resources/supplemental-models.xml b/core/security/keycloak/src/main/appended-resources/supplemental-models.xml
new file mode 100644
index 0000000..5210ece
--- /dev/null
+++ b/core/security/keycloak/src/main/appended-resources/supplemental-models.xml
@@ -0,0 +1,44 @@
+<?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 
+    language governing permissions and limitations under the License. -->
+<supplementalDataModels xmlns="http://maven.apache.org/supplemental-model/1.0.0"
+                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                        xsi:schemaLocation="http://maven.apache.org/supplemental-model/1.0.0 http://maven.apache.org/xsd/supplemental-model-1.0.0.xsd">
+
+  <supplement>
+    <project>
+      <groupId>aopalliance</groupId>
+      <artifactId>aopalliance</artifactId>
+      <version>1.0</version>
+      <licenses>
+          <license>
+              <name>Public Domain</name>
+          </license>
+      </licenses>
+    </project>
+  </supplement>
+
+  <supplement>
+    <project>
+      <groupId>dom4j</groupId>
+      <artifactId>dom4j</artifactId>
+      <version>1.6.1</version>
+      <licenses>
+        <license>
+            <name>BSD License</name>
+            <url>http://dom4j.sourceforge.net/dom4j-1.6.1/license.html</url>
+            <distribution>repo</distribution>          
+        </license>
+      </licenses>
+    </project>
+  </supplement>
+
+</supplementalDataModels>
diff --git a/core/security/keycloak/src/main/java/META-INF/MANIFEST.MF b/core/security/keycloak/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..254272e
--- /dev/null
+++ b/core/security/keycloak/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path: 
+
diff --git a/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/IsisBootSecurityKeycloak.java b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/IsisBootSecurityKeycloak.java
new file mode 100644
index 0000000..248ce1e
--- /dev/null
+++ b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/IsisBootSecurityKeycloak.java
@@ -0,0 +1,58 @@
+/*
+ *  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.security.keycloak;
+
+import javax.inject.Singleton;
+
+import org.apache.isis.security.keycloak.authentication.KeycloakAuthenticator;
+import org.apache.isis.security.keycloak.authorization.KeycloakAuthorizor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+import org.apache.isis.security.authentication.manager.AuthorizationManagerStandard;
+import org.apache.isis.security.authentication.standard.AuthenticationManagerStandard;
+import org.apache.isis.security.authentication.standard.Authenticator;
+import org.apache.isis.security.authorization.standard.Authorizor;
+
+/**
+ * Configuration Bean to support Isis Security using Shiro.
+ *  
+ * @since 2.0
+ */
+@Configuration
+@Import({
+    AuthorizationManagerStandard.class,
+    AuthenticationManagerStandard.class,
+    WebModuleKeycloak.class
+})
+public class IsisBootSecurityKeycloak {
+
+    @Bean @Singleton
+    public Authenticator authenticator() {
+        return new KeycloakAuthenticator();
+    }
+
+    @Bean @Singleton
+    public Authorizor autorizor() {
+        return new KeycloakAuthorizor();
+    }
+
+
+}
diff --git a/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/KeycloakFilter.java b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/KeycloakFilter.java
new file mode 100644
index 0000000..cdf00ad
--- /dev/null
+++ b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/KeycloakFilter.java
@@ -0,0 +1,53 @@
+package org.apache.isis.security.keycloak;
+
+import lombok.val;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.isis.core.runtime.web.AuthenticationSessionWormhole;
+import org.apache.isis.security.authentication.AuthenticationSession;
+import org.apache.isis.security.authentication.standard.SimpleSession;
+
+public class KeycloakFilter implements Filter {
+
+    @Override
+    public void doFilter(
+            final ServletRequest servletRequest,
+            final ServletResponse servletResponse,
+            final FilterChain filterChain) throws IOException, ServletException {
+
+        final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
+        final HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
+        final String userid = header(httpServletRequest, "X-Auth-Userid");
+        if(userid == null) {
+            httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+
+        try {
+            val authenticationSession = new SimpleSession(userid, Collections.singletonList("org.apache.isis.viewer.wicket.roles.USER"));
+            authenticationSession.setType(AuthenticationSession.Type.EXTERNAL);
+            AuthenticationSessionWormhole.sessionByThread.set(authenticationSession);
+            filterChain.doFilter(servletRequest, servletResponse);
+        } finally {
+            AuthenticationSessionWormhole.sessionByThread.remove();
+        }
+    }
+
+    private String header(final HttpServletRequest httpServletRequest, final String headerName) {
+        final Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
+        while(headerNames.hasMoreElements()) {
+            final String header = headerNames.nextElement();
+            if(header.toLowerCase().equals(headerName.toLowerCase())) {
+                return httpServletRequest.getHeader(header);
+            }
+        }
+        return null;
+    }
+}
diff --git a/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/WebModuleKeycloak.java b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/WebModuleKeycloak.java
new file mode 100644
index 0000000..4c4aaf1
--- /dev/null
+++ b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/WebModuleKeycloak.java
@@ -0,0 +1,92 @@
+/*
+ *  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.security.keycloak;
+
+import lombok.val;
+
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+
+import org.apache.isis.security.authentication.AuthenticationSession;
+import org.apache.isis.webapp.modules.WebModule;
+import org.apache.isis.webapp.modules.WebModuleContext;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
+import static org.apache.isis.commons.internal.context._Context.getDefaultClassLoader;
+import static org.apache.isis.commons.internal.exceptions._Exceptions.unexpectedCodeReach;
+
+/**
+ * WebModule to enable support for Keycloak.
+ */
+@Service @Order(Ordered.HIGHEST_PRECEDENCE)
+public final class WebModuleKeycloak implements WebModule  {
+
+    public final static ThreadLocal<AuthenticationSession> sessionByThread = new ThreadLocal<>();
+
+    private final static String KEYCLOAK_FILTER_CLASS_NAME =
+            KeycloakFilter.class.getName();
+
+    private final static String KEYCLOAK_FILTER_NAME = "KeycloakFilter";
+
+    // -- CONFIGURATION
+
+
+    // -- 
+
+    @Override
+    public String getName() {
+        return "Keycloak";
+    }
+    
+    @Override
+    public void prepare(WebModuleContext ctx) {
+    }
+
+    @Override
+    public ServletContextListener init(ServletContext ctx) throws ServletException {
+
+        final Dynamic filter;
+        try {
+            val filterClass = getDefaultClassLoader().loadClass(KEYCLOAK_FILTER_CLASS_NAME);
+            val filterInstance = ctx.createFilter(uncheckedCast(filterClass));
+            filter = ctx.addFilter(KEYCLOAK_FILTER_NAME, filterInstance);
+            if(filter==null) {
+                return null; // filter was already registered somewhere else (eg web.xml)
+            }
+        } catch (ClassNotFoundException e) {
+            // guarded against by isAvailable()
+            throw unexpectedCodeReach();
+        }
+
+        val urlPattern = "/*";
+        filter.addMappingForUrlPatterns(null, false, urlPattern); // filter is forced first
+
+        return null;
+    }
+
+    @Override
+    public boolean isApplicable(WebModuleContext ctx) {
+        return true;
+    }
+}
diff --git a/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/authentication/KeycloakAuthenticator.java b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/authentication/KeycloakAuthenticator.java
new file mode 100644
index 0000000..07341a8
--- /dev/null
+++ b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/authentication/KeycloakAuthenticator.java
@@ -0,0 +1,66 @@
+/*
+ *  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.security.keycloak.authentication;
+
+import lombok.NoArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+import javax.inject.Inject;
+
+import org.apache.isis.config.IsisConfiguration;
+import org.apache.isis.security.authentication.AuthenticationRequest;
+import org.apache.isis.security.authentication.AuthenticationSession;
+import org.apache.isis.security.authentication.standard.Authenticator;
+import org.apache.isis.security.keycloak.WebModuleKeycloak;
+
+@Log4j2 @NoArgsConstructor
+public class KeycloakAuthenticator implements Authenticator {
+
+    @Inject private IsisConfiguration configuration;
+
+
+    // -- init, shutdown
+
+    @Override
+    public void init() {
+    }
+
+
+    @Override
+    public void shutdown() {
+    }
+
+    // -- Authenticator API
+
+    @Override
+    public final boolean canAuthenticate(final Class<? extends AuthenticationRequest> authenticationRequestClass) {
+        return true;
+    }
+
+    @Override
+    public AuthenticationSession authenticate(final AuthenticationRequest request, final String code) {
+        return WebModuleKeycloak.sessionByThread.get();
+    }
+
+    @Override
+    public void logout(final AuthenticationSession session) {
+    }
+
+
+}
diff --git a/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/authorization/KeycloakAuthorizor.java b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/authorization/KeycloakAuthorizor.java
new file mode 100644
index 0000000..49db1bc
--- /dev/null
+++ b/core/security/keycloak/src/main/java/org/apache/isis/security/keycloak/authorization/KeycloakAuthorizor.java
@@ -0,0 +1,61 @@
+/*
+ *  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.security.keycloak.authorization;
+
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.security.authorization.standard.Authorizor;
+
+public class KeycloakAuthorizor implements Authorizor {
+
+    @Override
+    public void init() {
+    }
+
+
+    @Override
+    public void shutdown() {
+    }
+
+    @Override
+    public boolean isVisibleInRole(String role, Identifier identifier) {
+        return isVisibleInAnyRole(identifier);
+    }
+
+    @Override
+    public boolean isUsableInRole(String role, Identifier identifier) {
+        return isUsableInAnyRole(identifier);
+    }
+
+    @Override
+    public boolean isVisibleInAnyRole(Identifier identifier) {
+        return isPermitted(identifier, "r");
+    }
+
+    @Override
+    public boolean isUsableInAnyRole(Identifier identifier) {
+        return isPermitted(identifier, "w");
+    }
+
+    private boolean isPermitted(Identifier identifier, String qualifier) {
+        return true;
+    }
+
+
+}
diff --git a/core/security/shiro/pom.xml b/core/security/shiro/pom.xml
index 580c0cc..184ca0d 100644
--- a/core/security/shiro/pom.xml
+++ b/core/security/shiro/pom.xml
@@ -29,7 +29,7 @@
 
     <artifactId>isis-security-shiro</artifactId>
 
-    <name>Apache Isis Plugin for Security (Shiro)</name>
+    <name>Apache Isis Extension for Security (Shiro)</name>
 	<description>
         Authentication and Authorization using Apache Shiro.
     </description>
diff --git a/core/viewer-wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java b/core/viewer-wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
index 4f2af9d..e665d8f 100644
--- a/core/viewer-wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
+++ b/core/viewer-wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.viewer.wicket.viewer.integration.wicket;
 
+import org.apache.isis.core.runtime.web.AuthenticationSessionWormhole;
 import org.apache.wicket.Session;
 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
 import org.apache.wicket.authroles.authorization.strategies.role.Roles;
@@ -41,6 +42,9 @@ import org.apache.isis.webapp.context.IsisWebAppCommonContext;
 import lombok.Getter;
 import lombok.val;
 
+import java.util.List;
+import java.util.Objects;
+
 /**
  * Viewer-specific implementation of {@link AuthenticatedWebSession}, which
  * delegates to the Isis' configured {@link AuthenticationManager}, and which
@@ -63,24 +67,33 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, IsisWebAppComm
     private BreadcrumbModel breadcrumbModel;
     private BookmarkedPagesModel bookmarkedPagesModel;
 
+    /**
+     * As populated in {@link #signIn(String, String)}.
+     *
+     * <p>
+     * However, if a valid session has been set up previously and stored in {@link AuthenticationSessionWormhole}, then
+     * it will be used instead.
+     * </p>
+     */
     private AuthenticationSession authenticationSession;
 
     public AuthenticatedWebSessionForIsis(Request request) {
         super(request);
     }
-    
+
     public void init(IsisWebAppCommonContext commonContext) {
         this.commonContext = commonContext;
         bookmarkedPagesModel = new BookmarkedPagesModel(commonContext);
         breadcrumbModel = new BreadcrumbModel(commonContext);
+
     }
 
     @Override
     public synchronized boolean authenticate(final String username, final String password) {
         AuthenticationRequest authenticationRequest = new AuthenticationRequestPassword(username, password);
         authenticationRequest.addRole(USER_ROLE);
-        authenticationSession = getAuthenticationManager().authenticate(authenticationRequest);
-        if (authenticationSession != null) {
+        this.authenticationSession = getAuthenticationManager().authenticate(authenticationRequest);
+        if (this.authenticationSession != null) {
             log(SessionLoggingService.Type.LOGIN, username, null);
             return true;
         } else {
@@ -106,7 +119,7 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, IsisWebAppComm
         //        org.apache.isis.security.shiro.ShiroAuthenticatorOrAuthorizor.logout(ShiroAuthenticatorOrAuthorizor.java:179)
         //        org.apache.isis.runtime.authentication.standard.AuthenticationManagerStandard.closeSession(AuthenticationManagerStandard.java:141)
 
-        getAuthenticationManager().closeSession(authenticationSession);
+        getAuthenticationManager().closeSession(getAuthenticationSession());
         getIsisSessionFactory().closeSession();
 
         super.invalidateNow();
@@ -121,15 +134,58 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, IsisWebAppComm
                         : SessionLoggingService.CausedBy.SESSION_EXPIRATION;
 
         String userName = null;
-        if (authenticationSession != null) {
-            userName = authenticationSession.getUserName();
+        if (getAuthenticationSession() != null) {
+            userName = getAuthenticationSession().getUserName();
         }
 
         log(SessionLoggingService.Type.LOGOUT, userName, causedBy);
     }
 
     public synchronized AuthenticationSession getAuthenticationSession() {
-        return authenticationSession;
+        final AuthenticationSession authenticationSession = AuthenticationSessionWormhole.sessionByThread.get();
+        if (authenticationSession != null) {
+            if (getAuthenticationManager().isSessionValid(authenticationSession)) {
+                if (this.authenticationSession != null) {
+                    if (Objects.equals(authenticationSession.getUserName(), this.authenticationSession.getUserName())) {
+                        // ok, same session so far as Wicket is concerned
+                        if (isSignedIn()) {
+                            // nothing to do...
+                        } else {
+                            // force as signed in (though not sure if this case can occur)
+                            signIn(true);
+                            this.authenticationSession = authenticationSession;
+                        }
+                    } else {
+                        // different user name
+                        if (isSignedIn()) {
+                            // invalidate previous session
+                            super.invalidate();
+                        }
+
+                        // either way, the current one is now signed in
+                        signIn(true);
+                        this.authenticationSession = authenticationSession;
+                    }
+                } else {
+                    signIn(true);
+                    this.authenticationSession = authenticationSession;
+                }
+            }
+        }
+        return this.authenticationSession;
+    }
+
+    /**
+     * This is a no-op if the {@link #getAuthenticationSession() authentication session}'s
+     * {@link AuthenticationSession#getType() type} is {@link AuthenticationSession.Type#EXTERNAL external} (eg as
+     * managed by keycloak).
+     */
+    public void invalidate() {
+        if(this.authenticationSession.getType() == AuthenticationSession.Type.EXTERNAL) {
+            return;
+        }
+        // otherwise
+        super.invalidate();
     }
 
     @Override
@@ -139,7 +195,7 @@ implements BreadcrumbModelProvider, BookmarkedPagesModelProvider, IsisWebAppComm
         }
 
         final Roles roles = new Roles();
-        authenticationSession.streamRoles()
+        getAuthenticationSession().streamRoles()
         .forEach(roles::add);
         return roles;
     }
diff --git a/examples/apps/helloworld/pom.xml b/examples/apps/helloworld/pom.xml
index be6d9e6..c7aafb2 100644
--- a/examples/apps/helloworld/pom.xml
+++ b/examples/apps/helloworld/pom.xml
@@ -64,14 +64,13 @@
 			<artifactId>isis-security-shiro</artifactId>
 		</dependency>
 
-		<!-- OTHER DEPENDENCIES -->
-		
-		<!--
 		<dependency>
-			<groupId>org.hsqldb</groupId>
-			<artifactId>hsqldb</artifactId>
+			<groupId>org.apache.isis.core</groupId>
+			<artifactId>isis-security-keycloak</artifactId>
 		</dependency>
-		-->
+
+		<!-- OTHER DEPENDENCIES -->
+		
 		<dependency>
 			<groupId>com.h2database</groupId>
 			<artifactId>h2</artifactId>
diff --git a/examples/apps/helloworld/src/main/java/domainapp/application/HelloWorldAppManifest.java b/examples/apps/helloworld/src/main/java/domainapp/application/HelloWorldAppManifest.java
index d3afff0..c050410 100644
--- a/examples/apps/helloworld/src/main/java/domainapp/application/HelloWorldAppManifest.java
+++ b/examples/apps/helloworld/src/main/java/domainapp/application/HelloWorldAppManifest.java
@@ -20,6 +20,8 @@ package domainapp.application;
 
 import javax.inject.Singleton;
 
+import org.apache.isis.security.keycloak.IsisBootSecurityKeycloak;
+import org.apache.isis.security.shiro.IsisBootSecurityShiro;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
@@ -32,7 +34,7 @@ import org.apache.isis.config.IsisPresets;
 import org.apache.isis.config.beans.WebAppConfigBean;
 import org.apache.isis.jdo.IsisBootDataNucleus;
 import org.apache.isis.runtime.spring.IsisBoot;
-import org.apache.isis.security.shiro.IsisBootSecurityShiro;
+//import org.apache.isis.security.shiro.IsisBootSecurityShiro;
 import org.apache.isis.viewer.restfulobjects.IsisBootWebRestfulObjects;
 import org.apache.isis.viewer.wicket.viewer.IsisBootWebWicket;
 
@@ -50,7 +52,8 @@ import domainapp.dom.HelloWorldModule;
 })
 @Import({
     IsisBoot.class,
-    IsisBootSecurityShiro.class,
+//    IsisBootSecurityShiro.class,
+    IsisBootSecurityKeycloak.class,
     IsisBootDataNucleus.class,
     IsisBootWebRestfulObjects.class,
     IsisBootWebWicket.class,
diff --git a/examples/apps/simpleapp/application/pom.xml b/examples/apps/simpleapp/application/pom.xml
index 8946688..6165a97 100644
--- a/examples/apps/simpleapp/application/pom.xml
+++ b/examples/apps/simpleapp/application/pom.xml
@@ -65,11 +65,6 @@
             <artifactId>isis-plugins-jaxrs-resteasy-4</artifactId>
         </dependency>
         
-        <dependency>
-            <groupId>org.apache.isis.core</groupId>
-            <artifactId>isis-security-shiro</artifactId>
-        </dependency>
-        
         <!-- PERSISTENCE -->
 
 		<dependency>
@@ -116,13 +111,39 @@
             <scope>test</scope>
         </dependency>
 
-        <dependency>
-            <groupId>org.hsqldb</groupId>
-            <artifactId>hsqldb</artifactId>
-            <scope>test</scope>
-        </dependency>
-
-
     </dependencies>
 
+    <profiles>
+        <profile>
+            <id>shiro</id>
+            <activation>
+                <property>
+                    <name>!keycloak</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.isis.core</groupId>
+                    <artifactId>isis-security-shiro</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+
+        <profile>
+            <id>keycloak</id>
+            <activation>
+                <property>
+                    <name>keycloak</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.isis.core</groupId>
+                    <artifactId>isis-security-keycloak</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+
+    </profiles>
+
 </project>
diff --git a/mavendeps/webapp/pom.xml b/mavendeps/webapp/pom.xml
index 9c92778..9b44346 100644
--- a/mavendeps/webapp/pom.xml
+++ b/mavendeps/webapp/pom.xml
@@ -90,12 +90,6 @@
 					<artifactId>isis-security</artifactId>
 				</dependency>
 
-				<!-- FIXME[ISIS-1976] plugins are optional -->
-				<dependency>
-					<groupId>org.apache.isis.core</groupId>
-					<artifactId>isis-security-shiro</artifactId>
-				</dependency>
-				
 				<dependency>
 					<groupId>com.google.guava</groupId>
 					<artifactId>guava</artifactId>