Apache Shiro part 2 – securing a JSF Java EE 7 application
In the first part I described why I chose Apache Shiro as an Authentication framework. In this part I will describe the simplest working solution to secure a Java EE7 application with JSF/Primefaces frontend. To be honest I wont use much of Primefaces in this sample but the application for which I did this research uses it so I added the dependency here and added an Primefaces component to this demo project.
Bootstrapping
I created an empty Java EE project via maven and added the needed Shiro dependencies as well as the Primefaces dependency to
the pom file.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.are_you_ready</groupId> <artifactId>shirotest</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> <apache-shiro.version>1.3.2</apache-shiro.version> <primefaces.version>6.0</primefaces.version> <junit.version>4.12</junit.version> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <!-- Security --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${apache-shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${apache-shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${apache-shiro.version}</version> </dependency> <!-- PrimeFaces --> <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>${primefaces.version}</version> </dependency> <!-- Testing --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- Logging --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <finalName>shirotest</finalName> </build> </project>
The first two shiro dependencies should be self explaining while the third one is needed to make the “remember me” function work because it adds shiros session management. Because I chose Java EE as backend technology this would be all what is needed for a full fledged web application with database transactions, dependency injection etc. At next I had to configure the shiro framework itself. This is possible directly from the java code or via a separate shiro.ini file. For the sake of simplicity I chose the shiro.ini file. This has the positive effect that program code and security configuration are perfectly separated and that all configurations concerning the application security are at one place. Because shiro follows a convention over configuration principle the file has to be placed in the “webapp/WEB-INF” folder and has to be named “shiro.ini”.
My minimal configuration looks like this:
[main] # set login url user.loginUrl = /login.xhtml authc.loginUrl = /login.xhtml # redirect url called after successful login authc.successUrl = /index.xhtml # user and password parameter names at the website authc.usernameParam = username authc.passwordParam = password # name of the component that triggers remember me functionality authc.rememberMeParam = rememberMe # login error messages are returned to element with name shiroLoginFailure authc.failureKeyAttribute = shiroLoginFailure # Preconfigured password matcher that uses SHA-256 with 500000 hash iterations and a Salt passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher iniRealm.credentialsMatcher = $passwordMatcher [users] #password test123 admin = $shiro1$SHA-256$500000$GCgWy0vaMQDhVbFGT6jBEw==$GXGb/BduuS8goR2zoxOEeeIhzpKbTBo6Z/Fp0iZYtgs=, root, user = $shiro1$SHA-256$500000$GCgWy0vaMQDhVbFGT6jBEw==$GXGb/BduuS8goR2zoxOEeeIhzpKbTBo6Z/Fp0iZYtgs=, Users [urls] # filter setup for secured pages /javax.faces.resource/** = anon /login.xhtml = authc # when /shirotest/logout is invoked default redirect url / is used see LogoutFIlter.java /logout = logout #user filter is needed for remember-me to work # to visit any page you must be known as a user (through remember me i.e.). If not you have to login /** = user # make page only available for role ;/admin/** = authc, roles[root]
The file is divided into the three sections “main”, “users” and “urls” which are found by shiro through the square brackets. As you can see there are different prefix keywords used throughout the config file. The keywords “authc” and “user” are filters which are used and predefined by shiro. “authc” stands for authentication filter which requires the user to be authenticated to proceed. The user filter goes one step further and requires the accessor to be a known user to get access granted. This is needed later for the “Remember me” functionality to work. If you don’t need that you can stick with authc. There are many more filters predefined by shiro which can be examined at the Shiro website.
Most of the parameters are self explaining. Additionally I commented on them to make it even more clear. The passwords for the users are already hashed, I will cover later how I’ve done that. For the moment you just have to know that the two hashes are the same which means the password is in both cases test123. After the password you can see that there are roles attached to the users. The admin has the role root while the user has the role Users.
In the urls section the filter setup is essential. Here I’m telling shiro that even an anon user can access the page and get some response but that you will need an authentication at the login page.
The logout url defines a url at which shiros build in logout mechanism is activated. This has some problems with JSF but we will come later to that and as a first iteration it works.
At last we need to tell JSF that it has to let shiro do the authentication. For this to work I simply added a filter to the web.xml
<!--Apache SHIRO config--> <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> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping>
It just says that the shiro filter should be used at any url no matter if it is a request, forward, include or error. With this the prerequisites are done and we can implement the views.
Creating the main view and login form
At first I created a simple JSF view which should only be reachable after logging in through shiro.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:p="http://primefaces.org/ui"> <f:view> <h:outputLabel value="Hello, world"/> <br /> <form action="/shirotest/logout" method="get"> <button>Logout via automatic shiro.ini logout filter</button> </form> </f:view> </html>
The page just shows a static label and provides a logout button. If you take a second look at the shiro.ini you will see that we mapped the /logout url to the shiro logout filter (/logout = logout). This means that the definition of our logout button is all we need to proceed the logout. This works fine for the moment but can cause trouble because it doesn’t invalidate the HTTP session and can leave some state in it. I show the solution to this later.
To log in I created a simple login form:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://xmlns.jcp.org/jsf/core"> <f:view> <h2>Login</h2> <form method="post"> <label for="username">Username:</label> <input type="text" id="username" name="username" /> <br/> <label for="password">Password:</label> <input type="password" id="password" name="password" /> <br/> <label for="rememberMe">Remember me:</label> <input type="checkbox" id="rememberMe" name="rememberMe" value="true" /> <br/> <input type="submit" value="Login" /> <span class="error">#{shiroLoginFailure}</span> </form> </f:view> </html>
The names and ids for password and username should match the ones used in the shiro.ini at “authc.usernameParam = username“ and “authc.passwordParam = password” to wire shiro to the form parameters. The trailing span containing #{shiroLoginFailure} is also defined in the shiro.ini as “authc.failureKeyAttribute = shiroLoginFailure”. This can be seen as a marker where shiro renders error messages if some error happens during the login action. This is basically all there is to do. The application can now be started and you should be able to log in. There is just one improvement left. As I wrote before there can be problems with the shiro logout filter and the not properly cleaned up HTTP Session. So I had to work around that and build a custom logout. For this I extended the protected page with a new command button which is wired to a “submit” method in a class called “logout”
<h:form> <p:commandButton value="Logout via JSF action" id="logout" actionListener="#{logout.submit}" ajax="false" /> </h:form>
The logout class contains just this one method in which it programmatically calls shiros logout routine (as the logout filter does), additionally invalidates the session and redirects to the login page.
@Named @RequestScoped //automatic CDI injection when requested public class Logout { /** * Shiro logout for the current user */ public void submit() throws IOException { if (SecurityUtils.getSubject().hasRole("root")) { final ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); SecurityUtils.getSubject().logout(); externalContext.invalidateSession(); // cleanup user related session state externalContext.redirect("login.xhtml?faces-redirect=true"); } } }
The @Named annotation makes this a container managed CDI bean which means it can be injected while the application server manages its life-cycle. The @RequestScoped annotation says that a new instance is created per request. When the request is finished the instance is marked for garbage collection. In short the two annotations do the instantiation for us when needed and make sure no state is shared between requests.
Experimenting with roles
As a short addition I want to show the easiest way to use roles. As you might have seen I already have given roles to the two users in the shiro.ini by simply adding them after the hashed passwords
admin = $shiro1$SHA-256$500000$GCgWy0vaMQDhVbFGT6jBEw==$GXGb/BduuS8goR2zoxOEeeIhzpKbTBo6Z/Fp0iZYtgs=, root, user = $shiro1$SHA-256$500000$GCgWy0vaMQDhVbFGT6jBEw==$GXGb/BduuS8goR2zoxOEeeIhzpKbTBo6Z/Fp0iZYtgs=, Users
As you can see the admin has the role root and the user has the role Users. With this it is possible to show role specific messages in the secured website by adding the following JSF labels.
<h:outputLabel rendered="#{jsfSecurityTools.subject.hasRole('root')}" value="foo" /> <h:outputLabel rendered="#{jsfSecurityTools.subject.hasRole('Users')}" value="bar" />
To properly fill this labels depending on the role I wrote the JsfSecurityTools class which simply provide the subject from shiros SecurityTools from which the role can be requested.
@Named @RequestScoped public class JsfSecurityTools { public Subject getSubject() { return SecurityUtils.getSubject(); } }
This is an inconvenience when using JSF with shiro as there are shorthand methods to acquire the SecurityTools directly from the view code with other view technologies like JSPs for example. On the other hand it is not much effort to provide the SecurityTools to JSF with something like the here shown JsfSecurityTools class.
Generating secure encrypted passwords
As explained earlier I used shiro to create the hashed passwords inserted in the shiro.ini. Shiro provides for this a good preconfigured convenient way. For demonstration purposes I wrote a java command line application:
class shiroGen { public static void main (String[] args) { Console console = System.console(); final char[] password = console.readPassword("Please insert new password: "); PasswordMatcher auth = new PasswordMatcher(); final String encryptedPW = auth.getPasswordService().encryptPassword(password); System.out.println("PW: " + encryptedPW); } }
It asks for the desired plaintext password and returns the hashed password which can safely be inserted in the shiro.ini or added to the users database entry. Shiro is preconfigured to use a salt and a SHA256 hash with 500000 iterations.
This should be enough for any basic application and is portable via different application servers with minimum configuration effort. For more information about shiro and advanced topics visit the shiro project page.
The complete sample code is provided on my GitHub account.
2 thoughts on “Apache Shiro part 2 – securing a JSF Java EE 7 application”
Hi Sebastian,
Thank you for this tutorial. It is helping me allot.
However I think there is a small issue, where you update the page to call the submite method to do the session invalidation I think it needs to be changed to use a commandButton. If I am correct you can fix and delete my message if I’m wrong you can just ignore my message.
Thanks,
Henk
Hi Henk,
thx for your comment. You are absolutely right and as you can see in the GitHub repository I already did that but I simply pasted the wrong snippet (actually I pasted the same snippet which you could already see in the code block before) in the article. I replaced it now with the one which uses the commandButton to call the logout method.
Thanks for you help,
Sebastian