Zest™
Introduction
Tutorials
Javadoc
Samples
Core
Libraries
Extensions
Tools
Glossary 

Shiro Security

code

docs

tests

This library provides integration with the Apache Shiro Java Security Framework.

Note

If you are working on a HTTP based application, see the Shiro Web Security Library that leverages this very library and is directly usable with the HTTP Library, the Servlet Library or any other Servlet based stack.

“Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.” says the Apache Shiro website.

Altough Apache Shiro can be used as-is with Zest™ Applications, this library provides integrations that can come in handy. If your use case do not fit any of theses integrations, look at their respective code. You should find out pretty easily how to compose the provided code to write your integration. Don’t hesitate to contribute interesting integrations to this very library.

We invite you to read the comprehensive Apache Shiro documentation, we will mostly discuss Zest™ related matters here.

Table 46. Artifact

Group IDArtifact IDVersion

org.qi4j.library

org.qi4j.library.shiro-core

2.1


Basic usage

For standalone applications, you can use plain Shiro easily. The only thing to do is to register a configured SecurityManager when activating your Zest™ Application. It can be done outside the application, before its activation, "à là" by-hand ;

IniSecurityManagerFactory factory = new IniSecurityManagerFactory( "classpath:standalone-shiro.ini" );
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager( securityManager );

note that this example code register the SecurityManager as a VM static singleton.

However we recommend to use the provided IniSecurityManagerService that does exactly this when activated and unregister the SecurityManager when passivated:

new StandaloneShiroAssembler().
    withConfig( configModule, Visibility.layer ).
    assemble( module );

You can change the INI resource path through the ShiroIniConfiguration:

public interface ShiroIniConfiguration
        extends ConfigurationComposite
{

    /**
     * Resource path of the ini configuration file.
     * "classpath:", "file": and "url:" prefixes are supported.
     * Defaulted to "classpath:shiro.ini".
     */
    @Optional
    Property<String> iniResourcePath();

}

Remember that this setup use a ThreadLocal SecurityManager singleton. Among other things it means that, althoug the IniSecurityManagerService is activated on Application activation, if you need to use Shiro in other Services that are activated on Application activation you should tell Zest™ about this dependency by injecting the SecurityManagerService in the laters.

Once started you must remember to register the SecurityManager in Shiro’s ThreadContext ;

@Service
private IniSecurityManagerService securityManagerService;

  [...snip...]

public void interactionBegins()
{
    ThreadContext.bind( securityManagerService.getSecurityManager() );
}

  [...snip...]

public void interactionEnds()
{
    ThreadContext.unbindSubject();
    ThreadContext.unbindSecurityManager();
}

that’s how Shiro works.

Security Concern

This library provides the SecurityConcern that should be used alongside the provided method annotations that mimic Apache Shiro annotations:

  • The @RequiresAuthentication annotation requires the current Subject to have been authenticated during their current session for the annotated class/instance/method to be accessed or invoked.
  • The @RequiresGuest annotation requires the current Subject to be a "guest", that is, they are not authenticated or remembered from a previous session for the annotated class/instance/method to be accessed or invoked.
  • The @RequiresPermissions annotation requires the current Subject be permitted one or more permissions in order to execute the annotated method.
  • The @RequiresRoles annotation requires the current Subject to have all of the specified roles. If they do not have the role(s), the method will not be executed and an AuthorizationException is thrown.
  • The @RequiresUser annotation requires the current Subject to be an application user for the annotated class/instance/method to be accessed or invoked. An application user is defined as a Subject that has a known identity, either known due to being authenticated during the current session or remembered from RememberMe services from a previous session.

Realms Services

All the above is sufficient as long as you use the ini file to store user credentials and permissions or a Realm that has no dependency on your application code and can be specified in the ini file to be instanciated by Shiro outside the Zest™ scope.

One usecase where it’s not sufficient comes quickly as you would like to provide user credentials and permissions from Entities stored in an EntityStore or perform any custom logic involving your Zest™ Application.

Let’s look at a complete example using a Realm Service that extends one of the Shiro provided Realm which use in-memory credientials and configuring it ;

@Mixins( MyRealmMixin.class )
public interface MyRealmService
        extends Realm, ServiceComposite, ServiceActivation
{
}

  [...snip...]

public class MyRealmMixin
        extends SimpleAccountRealm
        implements ServiceActivation
{

    private final PasswordService passwordService;

    public MyRealmMixin()
    {
        super();
        passwordService = new DefaultPasswordService();
        PasswordMatcher matcher = new PasswordMatcher();
        matcher.setPasswordService( passwordService );
        setCredentialsMatcher( matcher );
    }

    public void activateService()
            throws Exception
    {
        // Create a test account
        addAccount( "foo", passwordService.encryptPassword( "bar" ) );
    }

      [...snip...]


}

@Override
public void assemble( ModuleAssembly module )
        throws AssemblyException
{
  [...snip...]

    new StandaloneShiroAssembler().
        withConfig( configModule, Visibility.layer ).
        assemble( module );
    module.services( MyRealmService.class );

      [...snip...]

}

We start by defining a Realm Service and its Mixin that’s based on SimpleAccountRealm that we configure to handle hashed passwords. For the sake of the example it is shown how to hash a password using Shiro built in mecanisms. Then comes the Assembly where we simply reuse the Standalone Shiro Assembly and declare our Realm as a Service. It works the same way when using the Shiro Web Security Library.

Note that under the hood, assembled Realm Services are added to the ones configured in the INI file.

Security Domain

Going further, if you want to persist credentials and permissions in an EntityStore, the Shiro Security Library provides skeletons to easily setup some usecases consisting of Shiro setup facilities and base state model for you to compose with.

Passwords

Password storage is not a simple subject. Shiro provide best practice mecanisms using salt and repeated-hashing out of the box. It is possible to setup your Realm so that hashed passwords are stored in a future proof manner, meaning that you can change the used algorithms while retaining compatibility with passwords already stored.

Shiro use the Shiro1CryptFormat which is a fully reversible Modular Crypt Format (MCF).

This library provides a PasswordRealmService to be used with a PasswordSecurable. Let’s look at a complete example.

First you need to define your User (or Account, or whatever fits your domain), for the sake of the example we define a UserFactory too. Note that the factory uses the PasswordService implemented by the PasswordRealm to hash the password:

public interface User
        extends PasswordSecurable
{
}

  [...snip...]

@Mixins( UserFactoryMixin.class )
public interface UserFactory
{

    User createNewUser( String username, String password );

}

  [...snip...]

public static class UserFactoryMixin
        implements UserFactory
{

    @Structure
    private Module module;

    @Service
    private PasswordService passwordService;

    @Override
    public User createNewUser( String username, String password )
    {
        EntityBuilder<User> userBuilder = module.currentUnitOfWork().newEntityBuilder( User.class );
        User user = userBuilder.instance();
        user.subjectIdentifier().set( username );
        user.password().set( passwordService.encryptPassword( password ) );
        return userBuilder.newInstance();
    }

}

Now comes the assembly that reuse what’s described above and add both the password domain assembly plus your custom User entity and its factory:

@Override
public void assemble( ModuleAssembly module )
        throws AssemblyException
{
  [...snip...]

    new StandaloneShiroAssembler().
        withConfig( configModule, Visibility.layer ).
        assemble( module );
    new PasswordDomainAssembler().
        withConfig( configModule, Visibility.layer ).
        assemble( module );

    module.entities( User.class );
    module.services( UserFactory.class );

      [...snip...]

}

And finally here is how to create a new user and below how to perform a login:

User user = userFactory.createNewUser( "foo", "bar" );

  [...snip...]

Subject currentUser = SecurityUtils.getSubject();
currentUser.login( new UsernamePasswordToken( "foo", "bar" ) );

In this setup, password hashing use the Shiro’s default (Salted SHA-256 with 500.000 iterations). If you need to change this you can do it using PasswordRealmConfiguration properties:

/**
 * Sets the name of the MessageDigest algorithm that will be used to compute hashes.
 */
@Optional
Property<String> hashAlgorithmName();

/**
 * Sets the number of hash iterations that will be performed during hash computation.
 */
@Optional
Property<Integer> hashIterationsCount();
Permissions & Roles

In the same vein, this library provide a domain state skeleton with support in PasswordRealmService. It allows you to easily store roles, permissions and assignment to your accounts.

Let’s look at the previous example with permissions added.

First you need to add the RoleAssignee type to your account:

public interface User
        extends PasswordSecurable, RoleAssignee
{
}

Assembly is straight forward:

new StandaloneShiroAssembler().
    withConfig( configModule, Visibility.layer ).
    assemble( module );
new PasswordDomainAssembler().
    withConfig( configModule, Visibility.layer ).
    assemble( module );
new PermissionsDomainAssembler().
    assemble( module );

module.entities( User.class );
module.services( UserFactory.class );

And here is how to use all this:

UnitOfWork uow = module.newUnitOfWork();

User user = userFactory.createNewUser( "foo", "bar" );
Role role = roleFactory.create( "role-one", "permission-one", "permission-two" );
role.assignTo( user );

uow.complete();

  [...snip...]

uow = module.newUnitOfWork();

Subject currentUser = SecurityUtils.getSubject();
currentUser.login( new UsernamePasswordToken( "foo", "bar" ) );

if ( !currentUser.hasRole( "role-one" ) ) {
    fail( "User 'foo' must have 'role-one' role." );
}

if ( !currentUser.isPermitted( "permission-one" ) ) {
    fail( "User 'foo' must have 'permission-one' permission." );
}

  [...snip...]

uow.discard();

Other authentication mecanisms

For other authentication mecanisms you can leverage Shiro extensions available in the Shiro distribution or as external libraries. There’s support for text files, simple JDBC, LDAP, CAS SSO, OAuth, OpenID, X.509 Certificates etc…

Take the PasswordRealmService as a start and extend/rewrite it to suit your needs.

If you happen to come with a Zest™ integration that could be valuable in this very library, don’t hesitate to contribute.

Logging

All code from this library use the org.qi4j.library.shiro logger.