The following is a reprint from http://meri-stuff.blogspot.ru
Apache Shiro, originally called JSecurity, is Java security framework. It was accepted and became Apache top level project in 2010. It aims to be powerful and easy to be used.
The project is in active development with active both users and developers mailing lists. Most important areas are documented on its web page. However, it has lot of gaps in documentation. It is not possible to learn to use most Shiro features from documentation alone. Luckily, the code is well commented and where I tried it was also easily readable.
Main Shiro features are:
- session management.
In this article article we try and demonstrate various Shiro features. We start with simple unsecured web application, then we add security features into it. All code code is available in SimpleShiroSecuredApplication project on Github.
Unsecured application code is located in unsecured_application branch. Application represents an internal system for a fictional company. The company has four departments:
Each department has its own page. Each page contains buttons that are used by users to do their work. When user presses button, the work is done. For example, any repairmen can go to repairmen page and press button “Repair Refrigerator”. The button repairs refrigerator and shows success message.
Each user have his own account page. Account page contains user’s private data. Since unsecured application has no users yet, account page does nothing. Additionally, there is a page which contains all application functions. Everything anybody can do is possible to be done on this page.
Anybody can do any work and see all the pages.Sample application is run in test class RunWaitTest. It is not the best practice to use unit test this way, but it is not important now. If you run the class the application will be available at http://localhost:9180/simpleshirosecuredapplication/ url.
First, we have to verify user’s identity. The easiest and most standard authentication is done through user name and password. User fills in his user name and password and system verifies whether supplied values match with some user account.
For simplest applications, it is sufficient to store user names and passwords in plain text files. In more realistic scenario, user name and password are stored in persistent storage or the verification is done through other system such as ldap or active directory. Shiro supports all mentioned authentication methods. If out of the box authentication features are not sufficient, it is possible to extend the framework with own verification implementation.
In this chapter, we add user name and password based authentication into the application. User name and password are stored in static plain-text Shiro ini file.
New requirements: It is possible to log in and log out users. Application is to be accessible only for logged users. Successful log in redirects user into his own account page. All application functions and pages are still accessible to any logged user.
- add Apache Shiro,
- create log in page,
- configure users and password,
- create log out page.
Add Apache Shiro
Shiro is integrated into web application through servlet filters. A filter intercepts requests and responses before servlet and performs all necessary tasks (such as identifying currently logged user, attaching logged user to current thread, … ). Default Shiro filters provide basic security features such as:
- enforcing user log in,
- enforcing ssl,
- checking of page access rights.
If you want to learn more about default Shiro filters, good place to start is DefaultFilter enumeration. It lists all Shiro filters available by default. If those are not sufficient for your needs, you may create custom one.
We will use highly configurable IniShiroFilter. It reads Shiro configuration from ini file and initializes security framework. It does not perform any security checks. Permission checks, user login, protocol checking etc. are all delegated to either default or custom filters. IniShiroFilter only initialize them.
- Section [main] contains Shiro initialization. Filters and custom objects are configured here.
- Section [users] defines users, passwords and roles.
- Section [roles] associates roles with permissions.
- Section [urls] specifies access rights to application pages (urls). It is done by binding either default or custom filters to urls.
Add Apache Shiro dependency to pom.xml:
Create Shiro.ini file and put it on classpath. Configure web.xml to call IniShiroFilter before each request:
Create Login Page
Login page is simple html page with submit button, user name and password fields. Login functionality is handled by default Shiro authc filter. Authc filter allows url access only to logged in users. If the user is not logged in, filter will redirect him to login page.
Form on login page must have name ‘loginform’ and its submit method must be ‘post’. Create login.jsp page:
Enable authc filter for all application pages:
Update: Shiro automatically performs context-relative path matching. As SimpleShiroSecuredApplication does not have context path set, full paths in Shiro.ini are necessary. However, if application context path would be /simpleshirosecuredapplication, then paths could be relative: e.g. simple /**=authc or /account/personalaccountpage.jsp.
As it is not safe to send non-encrypted user name and password through network, we should force ssl logins. Ssl filter does exactly that. It has an optional parameter: ssl port number. If the port parameter is omitted, it uses default ssl port 443.
Before configuring ssl in Shiro, we have to enable it on web server. How to do that depends on web server. We show how to enable it in Jetty. First, create keystore with self signed certificate:
Answer all questions and in the end press enter so the keystore password and key passwords are the same.
Second, add the keystore to the project and configure Jetty to use ssl. Java code is available in AbstractContainerTest class.
Now, it is possible to configure ssl filter in Shiro.ini:
Configure Users and Password
SimpleShiroSecuredApplication is now available only for logged users. We now need to add some users so people can log in. Configuration is done in [users] section of Shiro.ini file. Format of section entries is:
username = password, roleName1, roleName2, ..., roleNameN
Following section creates seven users, all have the same password ‘heslo’:
If user makes an error while logging in, Shiro redirects him back to the login page. The page looks exactly the same as before, which may confuse the user.
New requirement: show error message after each unsuccessful log in attempt.
Any time authentication error occurs, an exception is thrown. By default, form authentication filter catches the exception and stores its class name in request parameter. As we wish to customize data send to page, we have to extend FormAuthenticationFilter and override setFailureAttribute method:
Replace form authorization filter with VerboseFormAuthenticationFilter and configure it to use ‘simpleShiroApplicationLoginFailure’ request attribute to hold error information:
Show error in login.jsp page:
Beware: real application should not show too much login error information. Message ‘Login attempt was unsuccessful.’ with no further info is usually enough.
Current application version has all passwords stored in plain text. It is better to store and compare only password hashes.
Objects responsible for authentication are called realms. By default, Shiro uses IniRealm with pluggable password matcher to compare passwords. We will replace passwords in ini by their SHA-256 hashes and configure IniRealm to use SHA-256 hashing matcher.
Generate SHA-256 hash of password:
Configure Shiro to compare password hashes instead of password itself:
Replace users passwords by password hashes:
Note: it is not possible to specify salt in ini configuration.
Create Logout Page
Any application that have login feature should have also logout feature. Logging out current user with Shiro is easy, use command:
Logout page then looks like this:
We conclude first part by adding authorization to the application. We begin by limiting users access to pages. No user should be able to see pages of other departments. This provides only partial security to the project, since the user is still able to use ‘all application functions’ page or edit URL in the browser to do any action. We will call it page level authorization.
Then, we limit users ability to perform actions themselves. Even if user opens ‘all application functions’ page or edits url in the browser, he will be allowed to perform only functions specific to his department. We will call it function level authorization.
New requirements: user is not able to see pages of departments he does not belong to. User is able to perform only his departmental functions. Only exception to previous rules is administrator, who can perform both administrative and repair functions.
Page level authorization is done with roles filter. Parameter part of the filter may contain any number of roles. Logged user can access page only if he has all supplied roles.
As usual, roles filter is configured in Shiro.ini file:
Test whether security works: log in as any sales user, click home, click ‘repairmen page’ link. You will see an ugly error.
We finish page authorization and replace error with redirect to an error page. Default Shiro filters have property unauthorizedUrl. In case of unauthorized access, the filter will redirect user to specified url.
All departmental pages are secured now. However, any user can still perform any function on ‘all application functions’ page. Moreover, any logged user can edit url and thus do any action. For example, if you log in as sales and put https://localhost:8443/simpleshirosecuredapplication/masterservlet?action=MANAGE_REPAIRMEN into url, the application will perform manage repairmen function too (then it will throw null pointer exception, but security breach was already done).
We assign unique permission to each function. They are in split into groups:
- all permissions are in “functions” group,
- all administrative permissions are in “manage” group,
- all repair permissions are in “repair” group,
- all sale permissions are in “sale” group,
- all science permissions are in “science” group.
Shiro supports multi-level permissions represented as strings. Levels are separated with symbol ‘:’. E.g. “functions:manage:repairmen” has three levels: “functions”, “manage” and “repairman”. Multi-level permissions allow for easy permissions grouping. For example, science group belongs to functions group and contains three permissions:
Actions verify logged user permissions before doing their job:
NOTE: Another way how to achieve the same goal is through annotations.
The PerformFunctionAndGoBackServlet servlet catches authorization exception and converts it into error message:
Finally, we need to configure permissions to roles in Shiro.ini file. Shiro supports wildcards for multi-level permissions. Thus, we do not have to specify each departmental permission separately:
You can now try functions on ‘all application functions’ page. If logged user does not have required permission, an error message appears on top of the page. Moreover, if you log in as sales and try hacking https://localhost:8443/simpleshirosecuredapplication/masterservlet?action=MANAGE_REPAIRMEN, you will see an error message in the console (instead of success message).
Final application is available in ‘static_authentication_and_authorization’ branch on Github.