What's New in Apache Shiro 1.2
Apache Shiro 1.2.0 was released on Tuesday, January 24 2012 with a lot of new features and improvements that most of the community will find useful. Thanks to everyone who contributed to this release; it was a significant undertaking and reflects a big step forward for the project.
In this article, we’ll break the improvements up into four categories - Tools, Core, Web, and Support Modules - and cover them below.
Tools
Previously Apache Shiro has been exclusively an embedded application framework. The 1.2 retains this design goal, but it adds a very small convenience command-line cryptographic hashing program. Many will find this program useful when configuring Shiro-enabled applications or when needing general purpose hashing behavior on any JVM-enabled machine.
Command-Line Hasher
The new shiro-tools-hasher-1.2.0-cli.jar
file has been provided which is an executable jar program. You can run it by executing the following:
> java –jar shiro-tools-hasher-1.2.0-cli.jar
This command will print out a full list of instructions and options. The program can be used to hash almost anything: standard input, passwords, text strings, byte data, and any file or URL-based resource. While you can run the command above to see the full list of configuration options, here are some examples:
Password Hashing (the –p/--password flag):
> java –jar shiro-tools-hasher-1.2.0-cli.jar -p Password to hash: Password to hash (confirm):
By default, this will generate a SHA-256 hash using 500,000 hash iterations and a 128-bit securely randomly generated salt, printed in a string safe to store in a file or database. The Hash algorithm, number of iterations and salt size or salt source are configurable of course.
File checksums (the –r/--resource flag):
> java –jar shiro-tools-hasher-1.2.0-cli.jar –r RESOURCE_PATH
Where RESOURCE_PATH
is a file, url, or classpath location of a document for which to compute the checksum. By default an MD5 hash (checksum) is calculated, but you can specify another JVM-recognized hash algorithm name with the –a/--algorithm option.
We’ll soon see how to use this program to securely represent passwords in a shiro.ini
file or other text-based configuration file.
Core
A few nice features were added to Shiro’s core API as well.
PasswordService
Before Shiro 1.2 it was easy enough to generate password hashes. For example:
String digestString = new Sha256Hash(password, salt, numIterations).toBase64();
While this is incredibly easy compared to practically any other alternatives for the JVM, it is still a general-purpose hashing solution.
It became clear that it would be even nicer to abstract Hash logic and corresponding configuration (secure random salt generation, iterations, hash algorithm name, etc.) to a dedicated component. This way, the application developer only needed to worry about a single user-submitted value: the password and only the password. All hashing and configuration concerns could be internalized in a component and reused easily in the application.
The new PasswordService was created to satisfy this need. For example:
import org.apache.shiro.authc.credential.*; … String rawPassword = “test”; PasswordService svc = new DefaultPasswordService(); //e.g. during account signup or password reset: String encrypted = svc.encryptPassword(rawPassword); //e.g. during login: assert svc.passwordsMatch(rawPassword, encrypted);
You can configure the PasswordService
instance with hashing strategies and re-use that instance where necessary.
PasswordMatcher
To authenticate users, Shiro already supports credentials comparison for Realms with its CredentialsMatcher
concept. But for comparing passwords, a new component, the PasswordMatcher
(view source) has been introduced. The PasswordMatcher
is an implementation of the CredentialsMatcher
interface that uses a PasswordService
to perform password comparisons during authentication.
Realm configuration
To configure a Realm
for password matching, you would configure a password matcher, and ensure the matcher can use a password service. For example, if using shiro.ini
configuration:
[main] … passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher passwordService = org.apache.shiro.authc.credential.DefaultPasswordService #config the passwordService w/ hashing strategies as necessary passwordMatcher.passwordService = $passwordService … myRealm.credentialsMatcher = $passwordMatcher
Ensure the AuthenticationInfo
instance supplied by your Realm returns the encrypted password string from its getCredentials()
implementation. That is:
authenticationInfo.getCredentials() === encrypted_password
Text-configured User Accounts
If using text configuration to define a static list of user accounts, you can use the new PasswordMatcher and the aforementioned Command-Line Hasher to securely store user passwords in text configuration directly.
For example, let’s say we want to create a new user account with username ‘jsmith’. Using Shiro’s INI user account configuration as an example, we can enable the account in 3 steps:
- Enable the PasswordMatcher on the implicit iniRealm
[main] … passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher passwordService = org.apache.shiro.authc.credential.DefaultPasswordService passwordMatcher.passwordService = $passwordService iniRealm.credentialsMatcher = $passwordMatcher …
- Hash the user password
Use the Command-Line Hasher (covered above):
> java –jar shiro-tools-hasher-1.2.0-cli.jar –p Password to hash: test Password to hash (confirm): test $shiro1$...(cut for brevity)
- Add the user
Copy and paste the Command-Line Hasher’s output into the [users] section as a new user definition line:
[users] … jsmith = $shiro1$...(cut for brevity)
And that’s it! When the ‘jsmith’ user enters ‘test’ in a login form password field (for example), their authentication will succeed.
Note that this is just an example for INI-based configuration. The Command-Line Hasher’s password output is safe for any text-based location (e.g. spring.xml, database column, etc).
Authentication Caching
Authentication Caching is a new feature that was added to support applications that perform repeated authentication of the same accounts in a relatively short period of time. This is often done in stateless architectures such as REST or SOAP-based applications that authenticate every request.
When authenticating every request, it might be too computationally expensive to query a data store for account information every time the application services a request. Authentication caching was added to allow certain Realms to cache AuthenticationInfo (account data) lookups for future authentication attempts to alleviate strain on the underlying account data store.
To enable authentication caching in Apache Shiro 1.2, you must do 3 things:
- Your Realm implementation must subclass the
AuthenticatingRealm
class (or one of its subclasses such as the more commonly usedAuthorizingRealm
). - You must configure the Shiro SecurityManager with a general-purpose
CacheManager
or configure the Realm with a specificCacheManager
. For example:[main] cacheManager = com.foo.some.CacheManager … # configure a CacheManager for only a particular realm: # myRealm.cacheManager = $cacheManager # or, configure a CacheManager for all of Shiro’s needs: securityManager.cacheManager = $cacheManager
- Enable authentication caching for the realm.
[main] … myRealm.authenicationCachingEnabled = true …
As explained in the AuthenticatingRealm
JavaDoc, enable authenticationCaching
ONLY if you know it is safe to do so.
Web
Prior to Shiro 1.2, Shiro’s SecurityManager
initialization was performed in a Servlet Filter. This meant that any application that needed Shiro’s APIs at startup before the Shiro Filter initialized could not function effectively. Also, any application that might have wanted access to configuration objects other than just the SecurityManager
could not do so, because the Shiro Filter only exposed just the SecurityManager object to the application. These two concerns have been addressed in Shiro 1.2 by initializing Shiro with a Servlet Context Listener and the introduction of the new Environment
and WebEnvironment
API.
Initialization and Environment
To allow applications to have access to all Shiro-related objects at startup (and not just the SecurityManager), new Environment
(and web-specific WebEnvironment
) interfaces were introduced. An Environment
instance allows an application developer to obtain anything Shiro-related if necessary, for example, configured objects like a PasswordService
instance. The Shiro Filter still exists to filter and secure requests, but now the Filter relies on objects in the WebEnvironment
instead of being responsible for constructing them itself.
The following web.xml
snippet is how you enable Apache Shiro 1.2 and later in a web application:
… org.apache.shiro.web.env.EnvironmentLoaderListener ShiroFilter org.apache.shiro.web.servlet.ShiroFilter ShiroFilter /*
By default this configuration assumes Shiro is configured with a shiro.ini
file located either at /WEB-INF/shiro.ini
or at the root of the classpath. You can also control where the file resides, use another configuration format entirely, or even how to specify custom WebEnvironment
implementations. Please see the Shiro web documentation for details.
Accessing the WebEnvironment
When the Shiro Servlet Context Listener initializes, it will place the WebEnvironment
instance in the ServletContext
so it may be accessed by the application at any time. You can access the WebEnvironment and the objects it contains by using Shiro’s WebUtils class:
import org.apache.shiro.web.util.WebUtils; … ServletContext servletContext = //get your apps’s servlet context WebEnvironment webEnv = WebUtils.getRequiredWebEnvironment(servletContext);
Easily Enable or Disable Filters
Typically most developers disable a filter by simply removing it from a filter chain. But for filter chains enforcing security, this can be a pain – you often want to leave the chain alone and only turn on or off filters as necessary.
For example, perhaps you are using Shiro’s ssl
filter to ensure that web requests for a filter chain are only allowed if the channel is secure (HTTPS). You probably always want to enforce this in production, but in development, setting up a private SSL certificate and configuring it in your dev web server can be a real chore. So, while you would like to always ensure the SSL filter is enabled in production, you’d like to disable it in development.
Any servlet filter that extends the OncePerRequestFilter
can be enabled or disabled via the enabled
property. All of Shiro’s out-of-the-box filters extend this class and can therefore be enabled or disabled accordingly. You can subclass this filter for your own filters to support the same behavior. In future Shiro versions, this behavior will likely be supported for any filter, even those that don’t subclass OncePerRequestFilter
.
The following is a shiro.ini
example of how you might support the ssl use case described above:
[main] … # uncomment the following line to disable the ‘ssl’ filter: # ssl.enabled = false [urls] /securedEndpoint/** = ssl, someFilter, anotherFilter, ...
By uncommenting the ssl.enabled = false
line in development, the /securedEndpoint/**
filter chain will not enforce SSL communication. Commenting the line will enforce SSL again.
LogoutFilter
The LogoutFilter
was added to support a very simple but common use case: when a user logs out, typically most apps will simply want to call subject.logout()
and redirect the user to a default ‘after logout’ page (e.g. a thank you page, the login page, etc). Shiro 1.2 now supports this common flow with the LogoutFilter
.
Example shiro.ini
configuration that redirects to the login page with a status message:
[main] ... logout.redirectUrl = /login.jsp?status=loggedOut [urls] # requests to /logout will be handled by the ‘logout’ filter # configured above: /logout = logout
Enforcing Statelessness: The NoSessionCreationFilter
This is a simple but extremely useful filter, especially for enforcing stateless application architectures like those with REST and/or SOAP endpoints. The NoSessionCreationFilter
, available as the default noSessionCreation
filter, will prevent a new session from being created at any point further down in the filter chain.
If the noSessionCreation filter is the very first filter in your filter chains, you don’t have to search through code to figure out if someone or something might be creating sessions unexpectedly – the noSessionCreation
filter will explicitly prevent it.
The noSessionCreation
filter will enforce that a new session cannot be created at a later point during request execution. It does not have any effect if a session has been created prior to a Subject visiting a filter chain. For example, in shiro.ini:
[urls] /rest/** = noSessionCreation, someFilter, anotherFilter, ...
This guarantees that if anything in the /rest/**
filter chain (another filter, an MVC framework, etc) tries to create a new session, an exception will be thrown. More concretely, if a session does not exist, any call to the following four methods downstream will throw a DisabledSessionException
:
subject.getSession()
subject.getSession(true)
httpServletRequest.getSession()
httpServletRequest.getSession(true)
If a session already exists before the filter chain is executed, the above methods are allowed.
Finally, calls to the following two methods are allowed in all cases:
subject.getSession(false)
httpServletRequest.getSession(false)
The NoSessionCreationFilter
can be leveraged with the aforementioned Authentication Caching support to great effect to efficiently secure modern stateless REST-enabled applications.
New Support Modules
Apache Shiro has a number of support modules representing integration with 3rd-party APIs, such as the Spring Framework and AspectJ. Shiro 1.2 introduces three new support modules. While a detailed description of each module is out of scope for this overview, please see the linked documentation to learn more:
- Shiro support for Google Guice
- Shiro support for Jasig CAS
- Apache Karaf
features.xml
1 Response:
Great job. Thanks for the new release. The password service is great. One thing I am not sure of is that there is no longer need to supply a salt or number of interations? It seems the service will automatically handle those for me. Is my assumption correct?
Leave a comment: