Shiro Security

This guide describes the design and configuration of the Apache Shiro integration with Apache Isis.

Design

The Shiro integration provides an implementation for both the Authenticator and Authorizor SPIs. These both delegate to Shiro’s SubjectUtils class that in turn delegates to the SecurityManager. These are available as thread-locals (set up in a servlet filter):

shiro design.drawio
Figure 1. High-level design of the Shiro integration

Shiro’s Subject API defines the notion of a user, and uses the concept of a Realm as the means to authenticate the Subjects and optionally populate it with permissions.

Shiro ships with a simple text-based realm — the IniRealm — which reads users (and password), user roles and role permissions from the shiro.ini file. Configuring this realm is described below

The HelloWorld and SimpleApp starter apps are both configured to use this realm.

A more sophisticated option is the LDAP realm. Shiro has its own implementation which is extended in the provides the LDAP realm This realm also supports permissions.

Permissions are ultimately represented as strings, and their interpretation of them is the responsibility of AuthorizorShiro. An enhanced "wildcard" syntax, described below in more detail, incorporates the notion of read and write permissions, and also of vetoing permissions (as well as the usual allow permissions).

Configuring to use Shiro

Apache Isis' security mechanism is configurable, specifying an Authenticator and an Authorizor (non-public) APIs. The Shiro security mechanism is an integration with Apache Shiro that implements both interfaces.

Both the HelloWorld and SimpleApp starter apps are pre-configured to use Apache Shiro, so much of what follows may well have been set up already.

Maven pom.xml

Dependency Management

If your application inherits from the Apache Isis starter app (org.apache.isis.app:isis-app-starter-parent then that will define the version automatically:

pom.xml
<parent>
    <groupId>org.apache.isis.app</groupId>
    <artifactId>isis-app-starter-parent</artifactId>
    <version>2.0.0-M5</version>
    <relativePath/>
</parent>

Alternatively, import the core BOM. This is usually done in the top-level parent pom of your application:

pom.xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.isis.core</groupId>
            <artifactId>isis-core</artifactId>
            <version>2.0.0-M5</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

Dependency

In the webapp module of your application, add the following dependency:

pom.xml
<dependencies>
    <dependency>
        <groupId>org.apache.isis.mavendeps</groupId>
        <artifactId>isis-mavendeps-webapp</artifactId>
        <type>pom</type>
    </dependency>
</dependencies>

Update AppManifest

In your application’s AppManifest (top-level Spring @Configuration used to bootstrap the app), import the

AppManifest.java
@Configuration
@Import({
        ...
        IsisModuleSecurityShiro.class,
        ...
})
public class AppManifest {
}

Make sure that no other IsisModuleSecurityXxx module is imported.

Configuration Properties

The Shiro integration supports the following config properties:

Shiro Realms and shiro.ini

Shiro uses the shiro.ini file for configuration, which resides in the default package (in other words, in src/main/resources in the webapp module).

Shiro uses the concept of realms to define its own set of authenticated users and their roles, and this is the most important configuration specified in the shiro.ini file. Either one or many realms can be configured.

For example:

securityManager.realms = $realmName

where $realmName in the above example is a reference to a realm defined elsewhere in shiro.ini. This is an example of Shiro’s "poor-man’s" dependency injection (their words).

It’s also possible to configure Shiro to support multiple realms.

securityManager.realms = $realm1,$realm2

How to configure the text-based ini realm is explained below. Another option alternative is the LDAP realm.

As noted above, as well as realms many other aspects of configuration can be specified in this file:

Shiro Ini Realm

The Shiro concept of a Realm allows different implementations of both the authentication and authorisation mechanism to be plugged in.

The simplest realm to use is Shiro’s built-in IniRealm, which reads from the (same) shiro.ini file.

shiro ini realm.drawio

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 HelloWorld and SimpleApp starter apps are configured to use this realm.

Shiro Configuration

To use the built-in IniRealm, we add the following to shiro.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:

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:

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:

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:

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

The format of the permissions string is configurable in Shiro, and Apache Isis uses this to provide an extended wildcard format, described 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:

$realm1=org.apache.shiro.realm.text.IniRealm (1)
realm1.resourcePath=classpath:webapp/realm1.ini (2)
1 happens to (coincidentally) be the 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

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.

Enhanced Wildcard Permission

If using the text-based IniRealm or 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:

(?<vetoFlag>[!]?)(?:(?<permissionGroup>[^\/]+)[\/])?(?<permission>.+)

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)

Use an online regex tester, eg https://regex101.com/ to get an idea of how this works.

For example:

user_role   = !reg/com.mycompany.myapp.api,\
              !reg/com.mycompany.myapp.webapp.services.admin,\
              reg/* ; \
api_role    = com.mycompany.myapp.api ;\
admin_role = adm/*

sets up:

  • the user_role with access to all permissions except those in com.mycompany.myapp.api and com.mycompany.myapp.webapp.services.admin

  • the api_role with access to all permissions in com.mycompany.myapp.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.

Configuration

To configure Apache Isis' extended permission support requires that a custom permission resolver is specified in shiro.ini file:

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.

Caching

To ensure that security operations does not impede performance, Shiro supports caching. For example, this sets up a simple memory-based cache manager:

memoryCacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $memoryCacheManager

Other implementations can be plugged in; see the Shiro documentation for further details.

Further Reading

Shiro provides many other features. Check out:

  • Shiro’s documentation page can be found here.

  • community-contributed articles can be found here.

    These include for instance this interesting article describing how to perform certificate-based authentication (ie login using Google or Facebook credentials).