code
docs
tests
This library provides integration with the Apache Shiro Java Security Framework.
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.
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.
This library provides the SecurityConcern
that should be used alongside the provided method annotations that mimic
Apache Shiro annotations:
@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.
@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.
@RequiresPermissions
annotation requires the current Subject be permitted one or more permissions in order to
execute the annotated method.
@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.
@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.
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.
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.
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();
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();
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.