Shiro Security

TODO: this content has not yet been reviewed/updated for v2.0

This guide describes the configuration of the Shiro implementation of Apache Isis' Authenticator and `Authorizor APIs.

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 wih 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.

Telling Apache Isis to use Shiro

To tell Apache Isis to use Shiro, include IsisModuleSecurityShiro module in the top-level "app manifest".

For example, the SimpleApp starter app bootstraps using:

        // ...
        IsisModuleSecurityShiro.class,  (1)
        // ...
// ...
public class AppManifest {
1 configures Shiro.

This installs the appropriate implementation (the ShiroAuthenticatorOrAuthorizor class) that use Shiro’s APIs to perform authentication and authorization:

configure isis to use shiro

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 file:

This configuration property only comes into effect for the Restful Objects viewer; if set then the Shiro subject - if found to be still authenticated - will be logged out anyway and then re-authenticated.

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

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:


Based on this Shiro will then read WEB-INF/shiro.ini file to configure its Realm definitions for authentication and authorization.


The shiro.ini file is used to specify the realm(s) that Shiro will delegate to:

securityManager.realms = $realmName

Shiro’s ini file supports a "poor-man’s" dependency injection (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.

securityManager.realms = $realm1,$realm2

You can learn more about Shiro realms in the Shiro documentation.

Shiro Ini Realm

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

The diagram below shows the Isis and components involved:

configure shiro to use ini realm

The realm is responsible for validating the user credentials, and then creates a Shiro 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:

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:*:*,\
analysis_role = *:ToDoItemAnalysis:*:*,\
self-install_role = *:ToDoItemsFixturesService:install:*
admin_role = *

The value is a comma-separated list of permissions for the role. The format is:



  • 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.


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.

Shiro JDBC Realm

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.

If you are happy to use a database then we strongly recommend you use the 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:

configure shiro to use custom jdbc realm

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:

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.

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 =        \
         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 =  \
         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

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:

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:

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 how to do the setup on Tomcat 8.0.

The name of the DataSource can also vary by servlet container; see for example this StackOverflow answer.

Ldap Realm

Apache Isis ships with an implementation of Apache Shiro's Realm class that allows user authentication and authorization to be performed against an LDAP server.

configure shiro to use isis ldap realm

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:

contextFactory =
contextFactory.url = ldap://localhost:10389
contextFactory.systemUsername = uid=admin,ou=system        (1)
contextFactory.systemPassword = secret
contextFactory.authenticationMechanism = CRAM-MD5          (2)
contextFactory.systemAuthenticationMechanism = simple

ldapRealm =   (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 ApacheDS, v1.5.7. This can be administered using Apache Directory Studio, v1.5.3.

Shiro Realm Mappings

When configuring role based permission mapping, there can only be one of these entries per realm:

realm.groupToRolesMappings = ...


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 '' 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:


where myroles.ini is in src/main/resources/webapp, and takes the form:

user_role = *:ToDoItemsJdo:*:*,\
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 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):

activeds ldap mojo partition

Create the ou=groups,o=mojo hierarchy:

activeds ldap mojo root dse

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:

activeds ldap sasl authentication

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):

activeds ldap users

Configure the users into the groups:

activeds ldap groups

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:



  • 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:

user_role   = !reg/org.estatio.api,\
              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

  • 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:

permissionResolver =
myRealm.permissionResolver = $permissionResolver  (1)
1 myRealm is the handle to the configured realm, eg $iniRealm or $isisLdapRealm etc.


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:

import org.springframework.stereotype.Service;

public class ShiroService {

    public void runAs(String userName) {
        SimplePrincipalCollection principals =
            new SimplePrincipalCollection(userName, "jdbcRealm");                       (1)

    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();

    private UserService userService;
1 "jdbcRealm" is realm as configured in Shiro config (shiro.ini).
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:

@DomainService(nature = NatureOfService.VIEW)
@DomainServiceLayout(menuBar = DomainServiceLayout.MenuBar.TERTIARY)
public class RunAsService {

    public Dashboard runAs(User user) {
        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();

    private ShiroService shiroService;
    private UsersRepository usersRepository;
    private DashboardService dashboardService;                      (1)
1 go to the home page (application-specific)
2 return a list of users to run as

Credits: adapted from this gist.

Caching and other Shiro Features

We don’t want to repeat the entire Shiro documentation set here, but we should flag a number of other features that are worth checking out.


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’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).