Shiro (Authenticator & Authorizor)
This guide describes the design and configuration of the Apache Shiro integration with Apache Causeway.
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’s Subject API defines the notion of a user, and uses the concept of a Realm as the means to authenticate the Subject
s 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. |
For production use, a more sophisticated option is the LDAP realm. Shiro has its own implementation which can be used for authentication. We recommend that it is combined with SecMan for authorization. See setting up SecMan with Shiro for more details.
Configuring to use Shiro
Apache Causeway' 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 Causeway starter app (org.apache.causeway.app:causeway-app-starter-parent
) then that will define the version automatically:
<parent>
<groupId>org.apache.causeway.app</groupId>
<artifactId>causeway-app-starter-parent</artifactId>
<version>2.1.0</version>
<relativePath/>
</parent>
Alternatively, import the core BOM. This is usually done in the top-level parent pom of your application:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.causeway.core</groupId>
<artifactId>causeway-core</artifactId>
<version>2.1.0</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
Dependency
In the webapp module of your application, add the following dependency:
<dependencies>
<dependency>
<groupId>org.apache.causeway.mavendeps</groupId>
<artifactId>causeway-mavendeps-webapp</artifactId>
<type>pom</type>
</dependency>
</dependencies>
Note that this transitively includes the Wicket viewer module (org.apache.causeway.viewer:causeway-viewer-wicket-viewer
in your app).
Update AppManifest
In your application’s AppManifest
(top-level Spring @Configuration
used to bootstrap the app), import the
@Configuration
@Import({
...
CausewayModuleSecurityShiro.class,
...
})
public class AppManifest {
}
Make sure that no other CausewayModuleSecurityXxx
module is imported.
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 Shiro’s own LDAP realm, which can be used for authentication and combined with SecMan for authorization. See setting up SecMan with Shiro for more details.
As noted above, as well as realms many other aspects of configuration can be specified in this file:
-
enable caching for performance; discussed below
-
leverage Apache Causeway' enhanced wildcard permissions, by specifying the Apache Causeway permission resolver; discussed below.
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.
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 = myapp.*,\
causeway.security:*,\
causeway.applib:*
admin_role = *
The value is a comma-separated list of permissions for the role. The format is:
logicalTypeNamespace:logicalTypeSimpleName:memberName:r,w
where:
-
logicalTypeNamespace
is the namespace portion of the domain object’s logical type name … -
... and
logicalTypeSimpleName
is the last portion of the domain object’s logical type name.For example, if
@Named("myapp.customer.Customer")
, then the namespace is "myapp.customer" and the simple type name is "Customer". -
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)
Note that:
-
each part of the permission string can be wildcarded using
*
. -
The namespace can also be wildcarded at any level (for example
myapp.*
). -
Missing levels assume wildcards.
Thus:
myapp.customer:Customer:firstName:r,w # view or edit customer's firstName
myapp.customer:Customer:lastName:r # view customer's lastName only
myapp.customer:Customer:placeOrder:* # view and invoke placeOrder action
myapp.customer:Customer:placeOrder # ditto
myapp.customer:Customer:*:r # view all customer class members
myapp.customer:*:*:r # view-only access for myapp.customer namespace
myapp.customer:*:*:* # view/edit for myapp.customer namespace
myapp:*:* # view/edit for myapp namespace
myapp:* # ditto
myapp # ditto
* # view/edit access to everything
The format of the permissions string is configurable in Shiro, and Apache Causeway uses this to provide an extended wildcard format, described here. |
Providing permissions to Framework-provided Features
Some features of the framework are exposed as actions that must be provided as permissions.
In particular, permission to the features in causeway.security
must be granted in order that end-users can logout.
The snippet below defines a role for each framework feature:
[roles]
default_role = causeway.applib,\
causeway.security
fixtures_role = causeway.testing.fixtures
features_role = causeway.feat
metamodel_role = causeway.metamodel
h2_role = causeway.ext.h2Console
jdo_role = causeway.persistence.jdo
swagger_role = causeway.viewer.restfulobjects
conf_role = causeway.conf
sudo_role = causeway.sudo
Notes:
-
all users should be granted the
default_role
. -
conf_role
provides access to the configuration menu (in production mode), which is potentially sensitive -
sudo_role
provides the ability to impersonate any user, so is extremely sensitive; however it is prototype mode only
Most of the features protected by these roles are only available in prototype mode.
The exceptions are those under default_role
and conf_role
.
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 |
Enhanced Wildcard Permission
If using IniRealm, the string permissions can represent either a grant or a veto for a particular feature.
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 a "causeway" permission (implemented, in fact, by the CausewayPermission class) uses the following format:
(?<vetoFlag>[!]?)(?:(?<permissionGroup>[^\/]+)[\/])?(?<permission>.+)
where:
-
the optional
!
prefix indicates this permission is a vetoing permission -
the mandatory
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/myapp.api,\
!reg/myapp.webapp.services.admin,\
reg/*
api_role = myapp.api
admin_role = adm/*
sets up:
-
the
user_role
with access to all permissions except those with a logical type’s namespace ofmyapp.api
ormyapp.webapp.services.admin
-
the
api_role
with access to all permissions to logical types under the namespacemyapp.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/*
.
In this case the prefixes in reg/*
and adm/*
are required to make the patterns unique.
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 Causeway' extended permission support requires that a custom permission resolver is specified in shiro.ini
file:
permissionResolver = org.apache.causeway.security.shiro.authorization.CausewayPermissionResolver
myRealm.permissionResolver = $permissionResolver (1)
1 | myRealm is the handle to the configured realm, eg $iniRealm . |
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).