Securing ZK Applications With Apache Shiro
Ashish Dasnurkar, Senior Engineer, Potix Corporation
March 06, 2012
ZK 5/ZK 6/ZK 7
Introduction
In this smalltalk I will introduce how you can secure your ZK applications using Apache Shiro, a Java security framework.
Apache Shiro
Apache shiro is an easy-to-use Java security framework that provides security features such as authentication, authorization, cryptography, session management and so on. It is a comprehensive application security framework and especially useful for securing your web applications based on simple URL pattern matching and filter chain definitions.
Configuration
You can add Apache Shiro framework support to your ZK applications by either downloading the binary files from Shiro project download section and placing the jar files in your WEB-INF/lib folder, or, if you are using Maven, just add dependencies to your project pom.xml file as shown below;
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Shiro uses SLF4J for logging. -->
<!-- ZK uses SLF4J for logging since 7.0.0, don't need this dependency if using ZK 7.0.0 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
<scope>runtime</scope>
</dependency>
You also need to enable Shiro security filter in web.xml for it to auto register security filter configurations. Make sure you have the filter mapping configured to filter all URL patterns.
web.xml settings for Shiro 1.2.3
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
web.xml settings for Shiro 1.1.0
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Application overview
The ZK application used to demonstrate security using Shiro in this article is a simple web application with few pages without any real functionality. It will have few registered users with specific roles and except application home page all other pages are restricted to be accessed by users with only specific role. There are three pages in the application for users with marketing, products and sales roles. Users with marketing role can only access marketing page in addition to home page and so on.
Login page
Let's create a simple login page first. Shiro will automatically redirect users to this login page whenever they are trying to access a restricted page. Once user enter credentials, Shiro will authenticate and based on the roles assigned to the user, it will authorize access to that restricted page. Login page simply contains textbox input components for entering username and password. In addition to that it also has a checkbox component to let users select if the application should remember user credentials for future access.
<?page id="testZul" title="CUSTOM ZK + Apache Shiro login"?>
<window id="loginwin" title="CUSTOM ZK + Apache shiro login" border="normal" width="350px">
<!-- this form-login-page form is also used as the
form-error-page to ask for a login again. -->
<html style="color:red" if="${not empty requestScope.loginFailure}">
<![CDATA[
Your login attempt was not successful, try again.<br/><br/>
]]>
</html>
<groupbox>
<caption>Login</caption>
<h:form id="f" name="loginform" action="" method="POST"
xmlns:h="native">
<grid>
<rows>
<row>User: <textbox id="u" name="user"/></row>
<row>Password: <textbox id="p" type="password" name="pass"/></row>
<row><checkbox id="r" name="remember"/>Remember</row>
<row spans="2">
<hbox>
<h:input type="submit" value="Login"/>
</hbox>
</row>
</rows>
</grid>
</h:form>
</groupbox>
</window>
Most important thing here to consider is that the login form must be named as loginform as per Shiro requirement. One more additional piece of code above is to display login errors. In case the login attempt was unsuccessful, Shiro redirects user back to the login page. Shiro FormAutheticationFilter
will set a failure attribute with the error message in such a scenario. I have extended FormAutheticationFilter
and override its setFailureAttribute(ServletRequest, AuthenticationException)
to set an attribute in request. I detect this attribute when the login page is displayed and if it is present, the error message is provided to the user indicating failed login attempt and the reason.
public class SampleFormAuthenticationFilter extends FormAuthenticationFilter {
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
String message = ae.getMessage();
request.setAttribute(getFailureKeyAttribute(), message);
}
}
Securing pages
As described in application overview we have different pages in our ZK applications and they are restricted to be accessed by users with only specific roles. Let's see how we can configure Shiro to detect users accessing to these pages and authenticate & authorize them based on roles assigned to them in the application. Shiro is designed to work in any environment, from simple command-line applications to the largest enterprise clustered applications and because of this diversity of environments, there are a number of configuration mechanisms that are suitable for configuration. However, for simplicity we will use the common-denominator text-based configuration i.e. INI format to setup Shiro configuration for our application.
INI configuration
INI is basically a text configuration consisting of key/value pairs organized by uniquely-named sections. Keys are unique per section only, not over the entire configuration. Shiro INI configurations has few section indicated by square brackets [SECTION_NAME]. Below is the INI configuration for our application
[main]
sampleauthc = shiro.sample.SampleFormAuthenticationFilter
sampleauthc.loginUrl = /login.zul
sampleauthc.usernameParam = user
sampleauthc.passwordParam = pass
sampleauthc.rememberMeParam = remember
sampleauthc.successUrl = /home.zul
sampleauthc.failureKeyAttribute=loginFailure
roles.unauthorizedUrl = /accessdenied.zul
[urls]
/login.zul = anon
/marketing/**=sampleauthc, roles[marketing]
/products/** = sampleauthc, roles[products]
/sales/** = sampleauthc, roles[sales]
/zkau/** = anon
/home.zul = anon
[users]
admin = a,administrator
marketingguy = a,marketing
productsguy = a,products
salesguy = a,sales
Keep this in a file called shiro.ini and add it on your classpath and ShiroFilter
as defined in web.xml will automatically discover it. You can also give it a different name and keep it in a different location but specify these details in web.xml as specified here. Below I will describe few important Shiro configuration concepts and formats.
[main] section above defines the Shiro filter definitions and their configurations. Since we have extended Shiro FormAuthenticationFilter
line 2 defines sampleauthc acronym for our own filter that Shiro will use for login page authentication. We also indicate the login page input component names for Shiro to retrieve their values correctly.
[users] section defines our application users. The format of each user definition is username = password, roles[role1,role2....]
Although the configuration here is showing passwords in plain text Shiro provides support for password hashes.
[urls] section defines which URL patterns are restricted and for what roles. It also defines which Shiro filters to be applied to authentication and authorize access to those URL patterns. The format is url pattern = filter1,filter2 .... , roles[role1,role2....]
. For our sample application, three pages in marketing,products and sales directory can be each accessed by users with marketing, product and sales role. Note that in line 16 I have set anonymous access to /zkau/** pattern which is the request pattern for ZK Ajax requests as defined in web.xml. Also, home.zul page is accessible to all users. Remember to order your URL pattern access rules from most specific to the least specific ones as Shiro will do a sequential match for URL patterns starting from the the top in [urls] section.
In addition to these sections, there is also the [roles] section that allow developers to define even more fine grained control per role basis; as in you can assign different permission per role basis.
As configured above the home.zul page is accessible to everyone. From application home users can choose to visit other pages by clicking on the hyperlinks.
<window title="ZK + Apache Shiro application home" border="normal" width="350px" height="220px" apply="shiro.sample.HomeComposer">
<label id="user" value=""></label>
<grid height="200px">
<rows>
<row>
<a label="Marketing" href="/marketing/marketing.zul"></a>
</row>
<row>
<a label="Products" href="/products/products.zul"></a>
</row>
<row>
<a label="Sales" href="/sales/sales.zul"></a>
</row>
</rows>
</grid>
</window>
In controller, we use Shiro SecurityUtils.getSubject()
API to get currently logged-in users. Here SecurityUtils.getSubject()
returns an instance of Subject
which in Shiro terminology means anything that interacts with the application. For web application it usually means the end user. If the user interacting with the application is already authenticated we can display user details as shown below.
if(SecurityUtils.getSubject().isAuthenticated()) {
user.setValue("Welcome: " + SecurityUtils.getSubject().getPrincipal());
} else {
user.setValue("");
}
Access denied page
In cases where user is trying to access a restricted page without proper authorization we can configure Shiro to redirect users to a common access denied page. This is done by specifying roles.unauthorizedUrl = /accessdenied.zul
in shiro.ini as shown above on line 9.
Implementing Logout functionality
Logging out an user is as simple as calling SecurityUtils.getSubject().logout()
API as shown below in line 2
public void onClick$logout() {
execution.sendRedirect("/home.zul");
SecurityUtils.getSubject().logout();
}
Note that Shiro does not yet support automatically redirecting users to login or a predefined page after logout so you need to do the redirect before calling the above logout() API as shown in line 2.
Summary
In this article I showed you how to secure your ZK applications using Apache Shiro. The sample discussed here adds a very simple page based security to a ZK application. You can refer to Shiro reference documentation to add more security features such ssl support, LDAP support, cryptography and so on.
Download
You can download the sample application code from its github repo here.
For the sample with ZK 7.0 and Apache Shiro 1.2.3, please refer to here.
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |