WARNING - OUTDATED CONTENT!
Currently Shiro has fine Jakarta Faces integration.
Introduction
After having used Java EE container managed authentication and even having homegrown JSF based authentication for a good amount of years and getting a bit tired of it, I wanted to review how well the current 3rd party Java EE authentication frameworks integrate in Java EE 6 with JSF 2, CDI and EJB 3. Apache Shiro (formerly known as JSecurity) is one of them. I also briefly looked at Spring Security, but it's not usable in JSF/CDI/EJB beans, but only in Spring beans. You'd almost be forced to migrate from Java EE to Spring altogether which just doesn't make sense anymore these non-J2EE days.
Back to top
What container managed authentication can (not)
First we need to understand how container managed authentication is insufficient for a bit more than just a public website with an "admin backend".
- No builtin "Remember Me" functionality. Servlet 3.0 made this however easy to homegrow.
- No straightforward way to redisplay login page with login errors in case you've specified the same page as both login page and error page. There are however tricks.
- No remembering of POST request data when a form is submitted while the session is expired. There is no way to retain this data for resubmission other than homebrewing an authentication filter or going for a 3rd party one.
- No permission based restriction. A "permission" basically checks a specific task/action depending on currently logged-in user which may be shared across multiple roles. Role based restriction is sometimes too rough, requiring you to create ridiculous meta-roles like "SUPER_USER", "POWER_USER", "SUPER_ADMIN", "ALMOST_ADMIN", "GOD" and so on.
- No container-independent way of configuring the datasource containing users/roles. Not all containers offer the same granularity of configuring the datasource, making the datasource or even the datamodel potentially unportable across containers. JASPIC is intented to solve that, but it has as of now still container-specific problems.
Noted should be that the "Remember Me" functionality has become a breeze since the new Servlet 3.0 (Java EE 6) programmatic login facility in flavor of HttpServletRequest#login()
. Before that, it was not possible without hacking around with container specific classes or fiddling with JASPIC. Also noted should be that since Java EE 5 the container managed security also offers annotation based restriction like @RolesAllowed
in EJB methods which is very useful.
As to JSF, you can get the login username of the currently logged-in user in the view by #{request.remoteUser}
. You can perform role based checks by #{request.isUserInRole('SOME')}
in e.g. the rendered
attribute of a JSF component. In the backing bean, the same methods are also available via ExternalContext
. However, usually it are the EJBs who do the business actions and should thus do the security checks. But they are not allowed to grab the FacesContext
, let alone the ExternalContext
. For the role checks, the aforementioned annotations should be used instead.
Well, let's look if Shiro can do it better/easier.
Back to top
Preparing
The below article/tutorial assumes in Eclipse terms that you've created a "Dynamic Web Project" with JSF, CDI and JPA facets and that you've configured a Java EE 6 web profile compatible container like Glassfish or JBoss AS as target container. It also assumes that you know how to create a Hello World JSF application. So you know where/how to put the necessary Facelets code (I don't like repeating all the <html>
, <h:body>
, etc boilerplate in all Facelets examples).
Back to top
Getting Shiro to work
We need to have at least the shiro-core
and shiro-web
JARs in our webapp's /WEB-INF/lib
. Then, the documentation for configuring Shiro on web applications is available here. Okay, we thus need a servlet context listener so that Shiro can do its global initialization thing and a servlet filter for the actual authentication/authorization works whereby Shiro also wraps the request/response by Shiro-controlled ones (so that e.g. #{request.remoteUser}
and so on still keep its functionality). Add them to the webapp's /WEB-INF/web.xml
as follows:
<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>
Noted should be that FORWARD
and INCLUDE
dispatchers are never internally used by JSF on Facelets. They are in any way kept there in web.xml
for the sake of completeness (perhaps you want to ever use JSP in the same webapp? you never know). If you intend to navigate to a JSF resource which possibly requires re-checking authentication, then you'd better make it a fullworthy GET request by either a plain GET link/button or a POST-Redirect-GET action. See also when should I use <h:outputLink>
instead of <h:commandLink>
?
As to configuring Shiro, it uses the INI file syntax. There are several default filters available. If you need BASIC authentication, use the authcBasic
filter. If you need FORM authentication, use authc
filter.
Let's start with the simplest possible configuration, a BASIC authentication on everything inside the /app/
subfolder of the webapp and a single admin user. First create a /WEB-INF/shiro.ini
file with the following contents (yes, the example username is "admin" and the example password is just like that, "password"):
[users]
admin = password
[urls]
/app/** = authcBasic
Let's test it ... Hey, that worked quite good!
Back to top
Form based authentication
Turning it into form based authentication is just a matter of changing authBasic
to authc
(again, see the list of default filters for the filter name). Only, the login page path defaults to /login.jsp
. As JSP is a deprecated view technology since JSF 2.0 at December 2009, we obviously don't want to keep this setting as such. We want to change it to Facelets as /login.xhtml
. This can be done by setting the authc.loginUrl
entry. Note that you also need to include it in the [urls]
list. Also note that I of course assume that you've mapped the FacesServlet
on an URL pattern of *.xhtml
. If you're using a different URL pattern, then you should also change it as such in the INI file.
[main]
authc.loginUrl = /login.xhtml
[users]
admin = password
[urls]
/login.xhtml = authc
/app/** = authc
The shiro.ini
file syntax follows Javabean/EL look-a-like configuration of properties. If you look closer at the javadoc of FormAuthenticationFilter
, then you'll see that loginUrl
is actually a property of FormAuthenticationFilter
! The shiro.ini
basically interprets the entry key as a setter operation and uses the entry value as the set value. This configuration style however requires Apache Commons BeanUtils in the webapp. So, drop its JAR in /WEB-INF/lib
as well.
The HTML form syntax of /login.xhtml
as shown below is also rather straightforward. Remember, you can without problems use "plain HTML" in a JSF/Facelets page. Note that Shiro sets the login failure error message as a request attribute with the default name shiroLoginFailure
. This was nowhere mentioned in the Shiro web documentation! I figured it by looking at the Javadoc of FormAuthenticationFilter
.
<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>
Let's test it ... Yes, that works also quite good.
Noted should be that the failure message just represents the fully qualified classname of the thrown exception. E.g. org.apache.shiro.authc.UnknownAccountException
. This is intented to be further used as key of some i18n resource bundle like so #{bundle[shiroLoginFailure]}
or perhaps a Map
property.
Back to top
Remember Me
The form based authentication example as shown in the previous chapter has also a "Remember Me" checkbox. It indeed sets a rememberMe
cookie on the response with some long and encrypted value. However, after I deleted the JSESSIONID
cookie in the browser, it still brought me back to the login page as if the "Remember Me" was never ticked.
A little research with help of Google brought me to Matt Raible's blog from May 2011 (almost 2 years ago) wherein he initially also complained that the Shiro Remember Me didn't work. It turns out that you've to use the UserFilter
instead of the FormAuthenticationFilter
! As per the default filters listing, it has the name user
. So, fix the shiro.ini
accordingly.
[main]
authc.loginUrl = /login.xhtml
user.loginUrl = /login.xhtml
[users]
admin = password
[urls]
/login.xhtml = authc
/app/** = user
Finally, it works.
In hindsight, this approach of Shiro makes sense. You would at times of course also like to distinguish "remembered" users from really authenticated users, so that you can if necessary re-ask authentication on really sensitive submits. I am however surprised that this was nowhere mentioned in the Remember Me section of the Shiro web documentation, let alone in the rest of the Shiro web documentation, even though I initially expected that this was a classic RTFM case. I wonder, have the Shiro guys ever reviewed the documentation on that point after the complaint of Matt Raible?
Note that I had to duplicate authc.loginUrl
and user.loginUrl
to prevent authentication failures on authc
from being still redirected to the default login URL /index.jsp
. It would make more sense if the UserFilter
basically extends from FormAuthenticationFilter
so that both cases are covered. Maybe Shiro has its own reasons for this separation of the both filters, but I don't see it for now.
By the way, the cookie value turns out to be a Base64 encoded representation of an AES encrypted representation of a serialized representation of a collection of principals. Basically, it contains the necessary username information which is decryptable with the right key (which a hacker can in case of a default key easily figure by just looking at Shiro's source code). Not my favorite approach, but the AES encryption is really strong and you can (must) specify a custom AES cipher key or even a complete custom manager which can deal with the cookie value format you need. The custom AES chiper key can if necessary be specified as follows in shiro.ini
according to this example in the Shiro INI documentation:
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
The value is obviously fully to your choice. You can generate your own as follows using Shiro's own cryptographic API:
String key = Hex.encodeToString(new AesCipherService().generateNewKey().getEncoded());
System.out.println("0x" + key);
Back to top
Behavior on session expiration
The behavior on session expiration is rather straightforward on GET requests. When not remembered, it shows the login page and on successful login, it redirects you to the initially requested page, complete with the original query string. Exactly as expected.
The behavior is however not consistent on POST requests. I've observed the behavior on session expiration in 4 different cases:
- Synchronous POST without Remember Me
This discarded all the POST data and the user was after the login redirected to the application root instead of the initial page. You'd have to navigate to the initial page and re-enter all the POST data yourself. As to the wrong redirect to the root, this turns out to be the default fallback URL for the case there's no saved request. However, there's definitely a saved request, so I peeked around in the source code and it turns out that the saved request is only valid on a GET request and thus for POST the fallback URL is instead been used.
Well, this makes perhaps sense in some cases, but this should really have been better documented. In case of JSF, it doesn't harm if you use the POST URL for a GET request as JSF by default submits the
<h:form>
to exactly the same URL as the page is been requested with by GET (also known as "postback"). A concrete solution to the problem of being redirected to the wrong URL is discussed later in chapter Programmatic login.Further, it would have been be nice if Shiro remembered the POST request request body as well and upon a successful login via POST, replace the current request body with it and perform a 307 redirect. Or, perhaps, at least offer a way to obtain the saved POST request parameters by programmatic means, so that the developer can choose for setting the initial POST request URL as login form URL and all POST request parameters as hidden input fields of the login form. When the login is successful, then Shiro should not perform a redirect, but just let the request continue to the application. Theoretically, this is possible with a custom Shiro filter.
- Synchronous POST with Remember Me
On a default JSF setup, this failed with a
ViewExpiredException
. This is not Shiro's fault. The actual login was successfully performed. However, as the session is expired, the JSF view state is also expired. You can solve this by either setting thejavax.faces.STATE_SAVING_METHOD
toclient
, or by using OmniFaces<o:enableRestorableView>
. Once fixed that, the login went smoothly. All the POST data was successfully submitted. Of course, the login happens within the very same request already and effectively no redirect has taken place. - Asynchronous POST without Remember Me
This failed without any feedback. Shiro forced a synchronous redirect to the login URL which resulted in an ajax response which is effectively empty, leaving the enduser with no form of feedback. The enduser is facing the same page as if the form submit did nothing. In JSF ajax, redirects are not instructed by a HTTP 302 response, but by a special XML response. See also among others this stackoverflow.com answer.
It'd be nice if Shiro performed a
if ("partial/ajax".equals(request.getHeader("Faces-Request")))
check and returned the appropriate XML response. Fortunately, it's possible to extend Shiro's authentication filter to take this into account. A concrete solution is discussed later in chapter Make Shiro JSF ajax aware. - Asynchronous POST with Remember Me
This behaved exactly the same as the synchronous one described at point 2. The principle is also not much different though. You're however dependent on having a decent ajax exception handler if you would get feedback about the
ViewExpiredException
or not.
Back to top
Using a JSF form
Instead of a plain HTML form, you can of course also use a JSF form so that you can benefit of JSF builtin required="true"
validation and/or to get look'n'feel in line with the rest of the site (e.g. by PrimeFaces). You, as a JSF developer, should probably already know for long that JSF prepends the ID of the parent form in the ID (and also name) attribute of the input components. However, Shiro checks by default the request parameters with the exact name username
, password
and rememberMe
only. Fortunately, this is configurable in shiro.ini
. Look in the javadoc of FormAuthenticationFilter
, there are setters for the properties usernameParam
, passwordParam
and rememberMeParam
.
So, given the following JSF form in /login.xhtml
,
<h2>Login</h2>
<h:form id="login">
<h:panelGrid columns="3">
<h:outputLabel for="username" value="Username:" />
<h:inputText id="username" required="true" />
<h:message for="username" />
<h:outputLabel for="password" value="Password" />
<h:inputSecret id="password" required="true" />
<h:message for="password" />
<h:outputLabel for="rememberMe" value="Remember Me" />
<h:selectBooleanCheckbox id="rememberMe" />
<h:panelGroup />
<h:panelGroup />
<h:commandButton value="Login" />
<h:panelGroup styleClass="error" rendered="#{not facesContext.validationFailed}">
#{shiroLoginFailure}
</h:panelGroup>
</h:panelGrid>
</h:form>
and the following shiro.ini
,
[main]
authc.loginUrl = /login.xhtml
authc.usernameParam = login:username
authc.passwordParam = login:password
authc.rememberMeParam = login:rememberMe
user.loginUrl = /login.xhtml
[users]
admin = password
[urls]
/login.xhtml = authc
/app/** = user
you're already set. It works fine. Note that there's no means of a backing bean. That's also not necessary given that Shiro is performing the business logic by itself based on the request parameters.
Note that it's not possible to login by ajax this way. The submit itself would work fine and you would be logged in, but the navigation does not work. You won't be navigated to the initially requested page at all. So, if you're using JSF component libraries with builtin ajax facilities like PrimeFaces, then you'd need to set ajax="false"
on the command button. Also, input validation should not be done via ajax. It will work, but those requests will trigger Shiro's "remember the last accessed restricted page" mechanism and cause Shiro to redirect to the wrong URL after successful login, namely the one on which the ajax validation request is fired.
If you really need to login or validate via ajax, then you can always consider programmatic login.
Back to top
Programmatic login
Shiro also offers a programmatic login possibility. This is more useful if you want to be able to utilize for example ajax based validation on the required input fields and/or want to be able to perform the login by ajax. The programmatic login API is rather simple, as documented in Shiro web documentation:
SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password, remember));
However, the documentation doesn't seem to explain in any way how to obtain the saved request URL in order to perform a redirect to that URL. Some Googling brought me at this Shiro mailing list discussion which shows that the actual redirect can be done as follows:
WebUtils.redirectToSavedRequest(request, response, fallbackURL);
This would however not work when the current request concerns a JSF ajax request. As explained before, it has to return a special XML response instructing the JSF ajax engine to perform a redirect by itself. This functionality is provided by JSF's own ExternalContext#redirect()
method which transparently distinghuishes ajax from non-ajax requests. So, we really need to have just the saved request URL so that we could perform the redirect ourselves. After peeking around in the Shiro source code how it deals with the saved request URL, I figured that the saved request URL is available as follows:
String savedRequestURL = WebUtils.getAndClearSavedRequest(request).getRequestUrl();
Okay, let's put the pieces together. First create a backing bean (for practical reasons we're using CDI managed bean annotations instead of JSF managed bean annotations, further in the article annotation based restriction will be discussed and that works only in managed beans when using CDI; feel however free to use JSF managed bean annotations instead for programmatic login):
package com.example.controller;
import java.io.IOException;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.omnifaces.util.Faces;
import org.omnifaces.util.Messages;
@Named
@RequestScoped
public class Login {
public static final String HOME_URL = "app/index.xhtml";
private String username;
private String password;
private boolean remember;
public void submit() throws IOException {
try {
SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password, remember));
SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(Faces.getRequest());
Faces.redirect(savedRequest != null ? savedRequest.getRequestUrl() : HOME_URL);
}
catch (AuthenticationException e) {
Messages.addGlobalError("Unknown user, please try again");
e.printStackTrace(); // TODO: logger.
}
}
// Add/generate getters+setters.
}
Note that, as you're reading this blog, I'll for simplicity also assume that you're familiar with OmniFaces which minimizes some FacesContext
boilerplate. The Faces
and Messages
utility classes are from OmniFaces.
Now change the login form in /login.xhtml
accordingly to submit to that and perform the necessary ajax magic:
<h2>Login</h2>
<h:form id="login">
<h:panelGrid columns="3">
<h:outputLabel for="username" value="Username:" />
<h:inputText id="username" value="#{login.username}" required="true">
<f:ajax event="blur" render="m_username" />
</h:inputText>
<h:message id="m_username" for="username" />
<h:outputLabel for="password" value="Password:" />
<h:inputSecret id="password" value="#{login.password}" required="true">
<f:ajax event="blur" render="m_password" />
</h:inputSecret>
<h:message id="m_password" for="password" />
<h:outputLabel for="rememberMe" value="Remember Me:" />
<h:selectBooleanCheckbox id="rememberMe" value="#{login.remember}" />
<h:panelGroup />
<h:panelGroup />
<h:commandButton value="Login" action="#{login.submit}" >
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:messages globalOnly="true" layout="table" />
</h:panelGrid>
</h:form>
Now edit the shiro.ini
accordingly to get rid of authc
filter:
[main]
user.loginUrl = /login.xhtml
[users]
admin = password
[urls]
/login.xhtml = user
/app/** = user
Let's test it ... Yes, it works again quite good. Additional bonus is that this approach also fixes the problem that Shiro by default redirects to a fallback URL when the saved request concerns a POST request. See also point 1 of chapter Behavior on session expiration.
Noted should be that when the page with the POST form is been requested by GET with a request parameter like so /customers/edit.xhtml?id=42
in order to set the Customer
via <f:viewParam>
and so on, then you would after successful login be redirected to /customers/edit.xhtml
. This is not exactly Shiro's fault, it's JSF itself who is by default submitting to an URL without the query string in the <h:form>
. If you have those parameters definied as <f:viewParam>
, then you can just replace the form by the OmniFaces <o:form>
as follows to include the view parameters in the form action URL:
<o:form includeViewParams="true">
...
</o:form>
This way JSF will submit to the URL with the view parameters in the query string and thus give you the opportunity to redirect to exactly that URL after successful login. See also this stackoverflow.com question and answer.
Back to top
Programmatic logout
The programmic logout API is also simple, but it is nowhere mentioned in the Shiro web documentation. It was however easily found with common sense and IDE autocomplete on the Subject
instance. So, here is the oneliner:
SecurityUtils.getSubject().logout();
You can also just invalidate the HTTP session, however that doesn't trash the "Remember Me" cookie in case you're using it and the user would be auto-logged in again on the subsequent request when "Remember Me" was ticked. So, invalidating the session should merely be done to cleanup any other user-related state in the session, not to perform the actual logout.
Here's how the logout backing bean could look like:
package com.example.controller;
import java.io.IOException;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import org.apache.shiro.SecurityUtils;
import org.omnifaces.util.Faces;
@Named
@RequestScoped
public class Logout {
public static final String HOME_URL = "login.xhtml";
public void submit() throws IOException {
SecurityUtils.getSubject().logout();
Faces.invalidateSession();
Faces.redirect(HOME_URL);
}
}
Note that the redirect is really mandatory as the invalidated session is still available in the response of the current request. It's only not available anymore in the subsequent request. Also note that this issue is not specific to Shiro/JSF, just to HTTP in general.
In the view, just provide a command link/button which invokes #{logout.submit}
.
<h:form>
<h:commandButton value="logout" action="#{logout.submit}" />
</h:form>
Back to top
Make Shiro JSF ajax aware
As per point 3 of chapter Behavior on session expiration, the redirect to login page in case of session expiration wasn't properly dealt with in case of JSF ajax requests. The enduser basically ends up with no single form of feedback.
Fortunately, the Shiro API is designed in such way that this is fairly easy overridable with the following filter:
package com.example.filter;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.web.filter.authc.UserFilter;
public class FacesAjaxAwareUserFilter extends UserFilter {
private static final String FACES_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
@Override
protected void redirectToLogin(ServletRequest req, ServletResponse res) throws IOException {
HttpServletRequest request = (HttpServletRequest) req;
if ("partial/ajax".equals(request.getHeader("Faces-Request"))) {
res.setContentType("text/xml");
res.setCharacterEncoding("UTF-8");
res.getWriter().printf(FACES_REDIRECT_XML, request.getContextPath() + getLoginUrl());
}
else {
super.redirectToLogin(req, res);
}
}
}
To get it to run, just set it as user
filter in shiro.ini
(no, do not use the @WebFilter
annotation nor the web.xml
!):
[main]
user = com.example.filter.FacesAjaxAwareUserFilter
user.loginUrl = /login.xhtml
[users]
admin = password
[urls]
/login.xhtml = user
/app/** = user
Let's test it ... Yes, session expiration on ajax requests is now also properly handled.
Back to top
Configuring JDBC realm
In a bit sane Java EE web application wherein the container managed authentication is insufficient, the users are more than often not stored in some text file, but instead in a SQL database, along with their roles. In Shiro, you can use a Realm to configure it to obtain the users and roles (and permissions) from a SQL database. One of ready-to-use realms is the JdbcRealm
which is relatively easy to setup via shiro.ini
. Note also that this way the Realm is fully portable across different containers, which is definitely a big plus.
In this article we'll setup a test database with help of the embedded database engine H2 (formerly known as Hypersonic) and create a JPA model and an EJB service. Noted should be that the H2/JPA/EJB part is not necessary for functioning of Shiro. You're free in the choice of database vendor and the way how you model it and how you interact with it. In any way, the JPA/EJB examples are concretely used in the Register user and Hashing the password cases later on.
For your information only (you don't need to create them yourself at this point), the (Hibernate-generated) DDLs of the tables look basically like this:
create table User (
id bigint generated by default as identity (start with 1),
password varchar(255) not null,
username varchar(255) not null,
primary key (id),
unique (username)
)
create table UserRoles (
userId bigint not null,
role varchar(255)
)
Noted should be that the role
column could better have been an enumerated type (enum
, set
, etc) depending on DB make/version. This is however beyond the scope of this article. Let's try to keep it simple for now.
First download the H2 JAR file (no, not the Windows installer nor the zip file, just the JAR file from Maven or Sourceforge!) and drop it in /WEB-INF/lib
folder. Then edit shiro.ini
accordingly to get rid of the [users]
entry and utilize the users database via a JdbcRealm
:
[main]
# Create and setup user filter.
user = com.example.filter.FacesAjaxAwareUserFilter
user.loginUrl = /login.xhtml
# Create JDBC realm.
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
# Configure JDBC realm datasource.
dataSource = org.h2.jdbcx.JdbcDataSource
dataSource.URL = jdbc:h2:~/test
dataSource.user = sa
dataSource.password = sa
jdbcRealm.dataSource = $dataSource
# Configure JDBC realm SQL queries.
jdbcRealm.authenticationQuery = SELECT password FROM User WHERE username = ?
jdbcRealm.userRolesQuery = SELECT role FROM UserRoles WHERE userId = (SELECT id FROM User WHERE username = ?)
[urls]
/login.xhtml = user
/app/** = user
Note that the Javabean/EL-style properties of the data source in shiro.ini
should actually match the properties of the real data source instance.
At this point, it isn't possible to test the login thing as the database is basically empty :) Continue to the next chapters to create the model and the service, so that we can create users.
Back to top
JPA model and EJB service
Now the model and service. First create the following user role enum, com.example.model.Role
:
package com.example.model;
public enum Role {
EMPLOYEE, MANAGER, ADMIN;
}
Then create the following user entity, com.example.model.User
:
package com.example.model;
import java.util.List;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.validation.constraints.NotNull;
@Entity
@NamedQueries({
@NamedQuery(
name = "User.find",
query = "SELECT u FROM User u WHERE u.username = :username AND u.password = :password"),
@NamedQuery(
name = "User.list",
query = "SELECT u FROM User u")
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(unique = true)
private String username;
@NotNull
private String password;
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
@CollectionTable(name = "UserRoles", joinColumns = { @JoinColumn(name = "userId") })
@Column(name = "role")
private List<Role> roles;
// Add/generate getters+setters and hashCode+equals.
}
Then create the following service class, com.example.service.UserService
:
package com.example.service;
import java.util.List;
import javax.ejb.Stateless;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.omnifaces.cdi.ViewScoped;
import com.example.model.User;
@Stateless
public class UserService {
@PersistenceContext
private EntityManager em;
public User find(Long id) {
return em.find(User.class, id);
}
public User find(String username, String password) {
List<User> found = em.createNamedQuery("User.find", User.class)
.setParameter("username", username)
.setParameter("password", password)
.getResultList();
return found.isEmpty() ? null : found.get(0);
}
@Produces
@Named("users")
@RequestScoped
public List<User> list() {
return em.createNamedQuery("User.list", User.class).getResultList();
}
public Long create(User user) {
em.persist(user);
return user.getId();
}
public void update(User user) {
em.merge(user);
}
public void delete(User user) {
em.remove(em.contains(user) ? user : em.merge(user));
}
}
Then create the following datasource in /WEB-INF/web.xml
:
<data-source>
<name>java:app/H2/test</name>
<class-name>org.h2.jdbcx.JdbcDataSource</class-name>
<url>jdbc:h2:~/test</url>
<user>sa</user>
<password>sa</password>
<transactional>false</transactional> <!-- https://community.jboss.org/message/730085 -->
<max-pool-size>10</max-pool-size>
<min-pool-size>5</min-pool-size>
<max-statements>0</max-statements>
</data-source>
Finally create the following persistence unit in /META-INF/persistence.xml
(please note that this configuration indeed drops the DB tables on every server restart, again, it's just for testing purposes):
<persistence-unit name="test-jsf-shiro">
<jta-data-source>java:app/H2/test</jta-data-source>
<class>com.example.model.User</class>
<properties>
<!-- Hibernate specific properties. -->
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true" />
<!-- EclipseLink specific properties. -->
<property name="eclipselink.ddl-generation" value="create-tables" />
<property name="eclipselink.ddl-generation.output-mode" value="database" />
</properties>
</persistence-unit>
Again, noted should be that all of the above boilerplate is not mandatory for the functioning of Shiro. It's merely to create and find users as demonstrated in the following chapter.
Back to top
Register user
In order to create users via JSF, we need the following backing bean:
package com.example.controller;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import org.omnifaces.util.Messages;
import com.example.model.User;
import com.example.service.UserService;
@Named
@RequestScoped
public class Register {
private User user;
@EJB
private UserService service;
@PostConstruct
public void init() {
user = new User();
}
public void submit() {
try {
service.create(user);
Messages.addGlobalInfo("Registration suceed, new user ID is: {0}", user.getId());
}
catch (RuntimeException e) {
Messages.addGlobalError("Registration failed: {0}", e.getMessage());
e.printStackTrace(); // TODO: logger.
}
}
public User getUser() {
return user;
}
}
And this view, /register.xhtml
, using among others OmniFaces <o:importConstants>
to ease importing enums into <f:selectItems>
and OmniFaces omnifaces.GenericEnumConverter
in order to convert the selected roles to a proper List<Role>
instead of a List<String>
:
<o:importConstants type="com.example.model.Role" />
<h2>Register</h2>
<h:form id="register">
<h:panelGrid columns="3">
<h:outputLabel for="username" value="Username:" />
<h:inputText id="username" value="#{register.user.username}" required="true">
<f:ajax event="blur" render="m_username" />
</h:inputText>
<h:message id="m_username" for="username" />
<h:outputLabel for="password" value="Password:" />
<h:inputSecret id="password" value="#{register.user.password}" required="true">
<f:ajax event="blur" render="m_password" />
</h:inputSecret>
<h:message id="m_password" for="password" />
<h:outputLabel for="roles" value="Roles:" />
<h:selectManyCheckbox id="roles" value="#{register.user.roles}" required="true"
layout="pageDirection" converter="omnifaces.GenericEnumConverter">
<f:selectItems value="#{Role}" />
</h:selectManyCheckbox>
<h:message id="m_roles" for="roles" />
<h:panelGroup />
<h:commandButton value="Register" action="#{register.submit}" >
<f:ajax execute="@form" render="@form" />
</h:commandButton>
<h:messages globalOnly="true" layout="table" />
</h:panelGrid>
</h:form>
Finally, at this point we should be able to create users via database and login them programmatically via a JDBC realm!
If you want to have an overview of all users, just start off with this table (note that this effectively retrieves the list via @Produces
annotation of UserService#list()
):
<h2>Users</h2>
<h:dataTable value="#{users}" var="user">
<h:column>#{user.id}</h:column>
<h:column>#{user.username}</h:column>
<h:column>#{user.password}</h:column>
<h:column>#{user.roles}</h:column>
</h:dataTable>
Don't forget to ajax-update it on register, if necessary.
Back to top
Hashing the password
Storing password plaintext in the DB is not exactly secure. We'd of course like to hash them, if necessary along with a salt. Shiro offers several helper classes for hashing which allows you to do the job with a minimum of effort. Let's pick the most strongest hash algorithm: SHA256.
First edit Register#submit()
method accordingly to hash like that (note: don't use redisplay="true"
in the JSF password field! otherwise the hashed value would be reflected in the UI):
user.setPassword(new Sha256Hash(user.getPassword()).toHex());
service.create(user);
// ...
Then tell Shiro's JDBC realm to hash like that as well. Add the following lines to the end of the [main]
section of shiro.ini
:
# Configure JDBC realm password hashing.
credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName = SHA-256
jdbcRealm.credentialsMatcher = $credentialsMatcher
That's it!
Noted should be that the HashedCredentialsMatcher
javadoc strongly recommends salting passwords. Here's a cite of relevance:
ALWAYS, ALWAYS, ALWAYS SALT USER PASSWORDS!
That makes completely sense. However, configuring a salted password hash on a JDBC realm is in the current Shiro 1.2.1 version surprisingly not as easy as configuring the password hash. You basically need to homebrew a custom salt aware realm as described in this blog. In other words, in spite of their own strong recommendation, Shiro does not offer any ready-to-use realms for this. They seem to be working on that for version 1.3.
In any case, we're now at the point that all the essential stuff is properly configured and working smoothly: register, login, remember me, logout, users database and password hashing. Now, let's go a step further with user and role based restriction in Facelets rendering, HTTP requests and even bean methods.
Back to top
Restriction in Facelets rendering
With Shiro you can just continue using #{request.remoteUser}
and #{request.isUserInRole('SOME')}
the usual way as if you were using container managed authentication.
<h:panelGroup rendered="#{empty request.remoteUser}">
Welcome! Well, it seems that you are not logged in!
Please <h:link value="login" outcome="login" /> to see more awesomeness on this site!
</h:panelGroup>
<h:panelGroup rendered="#{not empty request.remoteUser}">
Welcome! You're logged in as #{request.remoteUser}. Enjoy the site!
<h:panelGroup rendered="#{request.isUserInRole('ADMIN')}">
You're an ADMIN user! Wow, we'll render some more cool buttons for you soon.
</h:panelGroup>
<h:panelGroup rendered="#{not request.isUserInRole('ADMIN')}">
You're not an ADMIN user. You probably will never become one.
</h:panelGroup>
</h:panelGroup>
This is absolutely a big plus. Not only makes this the views fully portable across container managed authentication and Shiro, but there are no other usable Shiro-offered ways, even no Facelets tags!
Shiro has a JSP/GSP based tag library, but JSP tags are unfortunately unusable in Facelets. It surprises me somewhat that they don't have a Facelets tag library even though it was born in the end of 2006 and has become the default view technology since Java EE 6 in the end of 2009. Even more, JSP is since then officially deprecated as the default view technology of JSF. If you're using the old Facelets 1.x (for JSF 1.x, which is over 6 years old already), then you can go for this Shiro Facelets taglib which was created by a power user of Shiro. Unfortunately, Facelets 1.x tags are unusable in Facelets 2.x (although it can easily be converted with a relative minimum of effort). All with all, it gives me somewhat the impression that Shiro is still hanging in the ancient J2EE/Spring era instead of moving forward along with the new Java EE 5/6 technologies.
Back to top
Restriction in HTTP requests
You can use the [urls]
section of shiro.ini
for this, as explained in Shiro web documentation. You can use the RolesAuthorizationFilter
to restrict access on a per-role basis. This filter is available by the name roles
which requires a commaseparated string of required roles as value. Here's an example:
[urls]
/login.xhtml = user
/app/admin/** = user, roles[ADMIN]
/app/manager/** = user, roles[MANAGER]
/app/employee/** = user, roles[EMPLOYEE]
/app/hr/** = user, roles[MANAGER,EMPLOYEE]
/app/** = user
/public/** = anon
Note that multiple roles are treated as an AND
condition, so the resource /app/hr/**
requires the user to have both the roles MANAGER
and EMPLOYEE
. Also note that there's an anon
filter allowing access by anonymous users (guests, non-logged-in users).
Also note that when you intend to restrict users applicationwide by /**
, that you should not forget to explicitly allow access to JSF resources (CSS/JS/image files), otherwise your login page would appear without any styles/scripts/images. You can achieve that by mapping the JSF resource URL pattern to the anon
filter:
[urls]
/login.xhtml = user
/javax.faces.resource/** = anon
/** = user
If a rule is violated, then Shiro returns a HTTP 401 error, which is customizable by the following error page entry in web.xml
as follows, which can be a fullworthy JSF page:
<error-page>
<error-code>401</error-code>
<location>/WEB-INF/errorpages/unauthorized.xhtml</location>
</error-page>
Everything with regard to HTTP request restriction seems to work fine. The configuraiton is quite simple, too.
Back to top
Programmatic restriction in bean methods
In JSF/CDI/EJB beans, the currently logged-in user is available by SecurityUtils.getSubject()
which returns a Subject
which has in turn several checkXxx()
, hasXxx()
and isXxx()
methods to check the roles and permissions. The checkXxx()
ones will throw an exception in case of a mismatch.
package com.example.controller;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import org.apache.shiro.SecurityUtils;
@Named
@RequestScoped
public class SomeBean {
public void doSomethingWhichIsOnlyAllowedByADMIN() {
SecurityUtils.getSubject().checkRole("ADMIN");
// ...
}
}
This throws a org.apache.shiro.authz.AuthorizationException
, which is customizable by the following error page entry in web.xml
as follows, which can be a fullworthy JSF page:
<error-page>
<exception-type>org.apache.shiro.authz.AuthorizationException</exception-type>
<location>/WEB-INF/errorpages/unauthorized.xhtml</location>
</error-page>
Note that this exception may be wrapped in a FacesException
if it's been caught by JSF and delegated to the container. In that case, you'd like to use OmniFaces FacesExceptionFilter
to unwrap it, so that the container properly receives a org.apache.shiro.authz.AuthorizationException
instead of a FacesException
.
Back to top
Declarative restriction in bean methods
Shiro has several annotations like @RequiresRoles
. Its documentation mentions that it only requires AOP like AspectJ or Spring. Well, as we're running Java EE 6, we'd like to utilize a Java EE interceptor to check the invoked CDI/EJB class/method for Shiro annotations. No need for AspectJ/Spring. Java EE 5 has already standardized it for long.
While looking around if someone else didn't already invent such an interceptor, I stumbled via list of Shiro Articles on among others this blog. The code at that blog was however not really useable as it didn't check for Shiro-specific annotations, but instead a homebrewed one. It also performed a permission check on certain method name patterns which is perhaps smart (convention over configuration), but not really declarative. There's another blog which has basically the same idea as I'm looking for, but the overall code quality is pretty poor (e.g. catching NPE...), it was clearly written by a starter. It must be doable with less than half of the provided code (and even with full coverage of annotations instead of only three).
Well, let's write it myself. First create an annotation which the interceptor has to intercept on:
package com.example.interceptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
@Inherited
@InterceptorBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ShiroSecured {
//
}
Then create the interceptor itself:
package com.example.interceptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;
@Interceptor
@ShiroSecured
public class ShiroSecuredInterceptor implements Serializable {
private static final long serialVersionUID = 1L;
@AroundInvoke
public Object interceptShiroSecurity(InvocationContext context) throws Exception {
Subject subject = SecurityUtils.getSubject();
Class<?> c = context.getTarget().getClass();
Method m = context.getMethod();
if (!subject.isAuthenticated() && hasAnnotation(c, m, RequiresAuthentication.class)) {
throw new UnauthenticatedException("Authentication required");
}
if (subject.getPrincipal() != null && hasAnnotation(c, m, RequiresGuest.class)) {
throw new UnauthenticatedException("Guest required");
}
if (subject.getPrincipal() == null && hasAnnotation(c, m, RequiresUser.class)) {
throw new UnauthenticatedException("User required");
}
RequiresRoles roles = getAnnotation(c, m, RequiresRoles.class);
if (roles != null) {
subject.checkRoles(Arrays.asList(roles.value()));
}
RequiresPermissions permissions = getAnnotation(c, m, RequiresPermissions.class);
if (permissions != null) {
subject.checkPermissions(permissions.value());
}
return context.proceed();
}
private static boolean hasAnnotation(Class<?> c, Method m, Class<? extends Annotation> a) {
return m.isAnnotationPresent(a)
|| c.isAnnotationPresent(a)
|| c.getSuperclass().isAnnotationPresent(a);
}
private static <A extends Annotation> A getAnnotation(Class<?> c, Method m, Class<A> a) {
return m.isAnnotationPresent(a) ? m.getAnnotation(a)
: c.isAnnotationPresent(a) ? c.getAnnotation(a)
: c.getSuperclass().getAnnotation(a);
}
}
Note: the abbreviated c
, m
and a
variablenames are not my style, it's just to get the code to fit in 100 char max length of this blog — I have my editor set at 120 chars. Also note that the annotations are checked on the target class' superclass as well as the target class may in case of CDI actually be a proxy and Shiro's annotations don't have @Inherited
set.
In order to get it to work on CDI managed beans, first register the interceptor in /WEB-INF/beans.xml
as follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://docs.jboss.org/cdi/beans_1_0.xsd"
>
<interceptors>
<class>com.example.interceptor.ShiroSecuredInterceptor</class>
</interceptors>
</beans>
Similarly, in order to get it to work on EJBs, first register the interceptor in /WEB-INF/ejb-jar.xml
as follows (or in /META-INF/ejb-jar.xml
if you've a separate EJB project in EAR):
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
version="3.1"
>
<interceptors>
<interceptor>
<interceptor-class>com.example.interceptor.ShiroSecuredInterceptor</interceptor-class>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>com.example.interceptor.ShiroSecuredInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>
On a CDI managed bean, you need to set the custom @ShiroSecured
annotation in order to get the interceptor to run.
package com.example.controller;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import org.apache.shiro.authz.annotation.RequiresRoles;
import com.example.interceptor.ShiroSecured;
@Named
@RequestScoped
@ShiroSecured
public class SomeBean {
@RequiresRoles("ADMIN")
public void doSomethingWhichIsOnlyAllowedByADMIN() {
// ...
}
}
This is not necessary on an EJB, the ejb-jar.xml
has already registered it on all EJBs.
Noted should be that this interceptor feature is in no way supported on JSF managed beans. That's exactly the reason why this article is using CDI managed beans from the beginning on. If your business requirements however allow to have those security restriction annotations on EJBs only, then you can also just keep them over there and get away with JSF managed beans.
As an alternative, if you don't want to spend some XML code for some reason, then you can also explicitly set the desired interceptor on the CDI managed bean or EJB using the @Interceptors
annotation. So, instead of the custom @ShiroSecured
annotation and all the XML configuration, you could also use this annotation on both CDI managed beans and EJBs:
@Interceptors(ShiroSecuredInterceptor.class)
This is however considered tight coupling and thus poor design.
In any way, reagardless of how the interceptor is registered, the annotation based restriction works really nice! In case of ajax requests, you may want to register the OmniFaces FullAjaxExceptionHandler
to prevent the no-feedback problem on exceptions in ajax requests.
Back to top
Summary
No, Shiro is not ready for Java EE 6. It didn't work out the box. It lacks the following essential Java EE 6 features:
- A JSF ajax aware filter which sends a proper redirect
- A JSF2 Facelets taglib (although not strictly necessary if you only need user/role checks)
- A CDI/EJB interceptor for security annotations
Another point of concern is the state and documentation of the project. I have the impression that Shiro is somewhat hanging in the ancient J2EE/Spring era and also that documentation needs some more attention and love. Further, it would be really cool if Shiro also remembers the POST request which failed authentication, so that it can immediately be re-executed after successful login. Also, having a salt aware JDBC realm out the box of Shiro would be very useful.
Other than that, the configuration is a breeze and fully portable across containers. Also, the security API is awesome, it has quite handy cryptographic helper classes and furthermore it even supports impersonating out the box. I would forgive its current Java EE 6 shortcomings, which are fortunately relatively easy to fix, but they should really work on that soon :)
75 comments:
quite good analysis, thank you BalusC !
I can deal with not retaining the post after a failed authentication... the documentation is where I think the most effort needs to be put now. I am still excited about giving this a shot for one of my smaller side projects to see how it goes.
What other projects are available that will offer what Shiro offers to the small development team right now?
BalusC, I would like to request you to comment on Seam 3 security module. Seam2 has great security module but cannot be used as CDI extension (you should either use seam2 in full or not) but in seam3 they moved their secuirty implementation to picketlink and before seam 3 stablizes seam 3 team joined deltaspike. So untill deltaspike is ready for porduction can seam 3 be an alternative? After going through documentation it doesnot seem to be complete for resource based authorization. Is it ready for Java EE6? Since Seam 3 has been obsolete after jboss has moved it forces for deltaspike and deltaspike is not ready for production. Also I dont like using spring security with JEE6. I was evaluating between apache shiro and Seam secuirty. And this post has helped me alot. Thank you for the post and also like to welcome you to comment on seam 3 security.
@Ross: Alternatives are Spring Security and Seam Security.
@Dev: I've also reviewed Spring Security, but it's not much better than Shiro. It's not usable in CDI/EJB beans, but only in Spring beans. You'd almost be forced to migrate from Java EE to Spring altogether which IMO just doesn't make sense these non-J2EE days. Seam Security is on the review list. On paper it looks more awesome. It has already a ready-to-use interceptor and it has even builtin OpenID support.
BaluC, Nice insight...My question is, How would the shiro.ini settings be done when using Spring beans in applicationContext.xml file?
@James: Sorry, I don't do Spring. Just standard Java EE CDI/EJB. If you're using legacy Spring anyway, why don't you use Spring Security? Or does it have its shortcomings?
@BalusC...I just use Spring for transaction management, Not used Spring Security but from what I have gathered it looks a little more complex than Shiro, I needed to have my Shiro.ini in beans in my beans definitions so that I don't have multiple database config locations....Thanks anyway.
@James: for that the standard Java EE stack offers EJB. Shiro has specifically for legacy Spring a separate document: http://shiro.apache.org/spring.html You may find it useful.
@BalusC, Even though you have tried to cover as much as possible. It would have been gr8 if you have included declarative restriction for instance level access control. Do you think its possible to do instance level access control using declarative access control.
What I am trying to achieve is
@Restricted(MyEnum.Project)
public void editProject(Project p){}
and use permission interface to check whether user has permission on any of the project in our system, he will then be allowed to edit the projects he owns.
Thanks BalusC for this great article.
In my usecase, I have a problem FacesAjaxAwareUserFilter in combination with navigation via GET. I posted my question in detail at http://stackoverflow.com/questions/14726767/apache-shiro-user-authenticates-within-ajax-how-to-restore-get-variables-after
the library at https://github.com/deluan/shiro-faces seems to have been updated for JSF2, i've done a quick test and it works fine.
great article, i've been testing out Shiro for a while, has some nice features but i agree that the documentation is a bit poor.
Regarding the JSF2 taglib, it is available in Shiro trunk: https://svn.apache.org/repos/asf/shiro/trunk/support/faces/
I've built and tested it locally, it also works with Shiro 1.2.1.
I asked on the Shiro user list for a possible release of this component but never got a reply.
http://grokbase.com/t/shiro/user/12bp9mxer1/shiro-faces-release
Really Cool stuff BalusC. A treat to read. Thanks
Could you please share a download link to download the complete source code of the examples discussed in this article... am a beginner in jsf/shiro... so it would be a great help if you can share the complete code.... Thanks - Thomas
sthomaslinson@gmail.com
@thomaslinson: Just follow the article step by step. It's complete already.
Just wanted to say thank you for this excellent post. Very informative.
Not working for me with a custom realm which is managed bean in jsf
---------------------
shiro.ini
[main]
# Create and setup user filter.
#user = com.example.filter.FacesAjaxAwareUserFilter
user.loginUrl = /login.xhtml
realmA = au.com.std.shiroweb.web.mbean.LoginController
credentialsMatcher = org.apache.shiro.authc.credential.SimpleCredentialsMatcher
realmA.credentialsMatcher = $credentialsMatcher
securityManager.realms = $realmA
Custom realm
----------------
@ManagedBean
@SessionScoped
public class LoginController extends AuthorizingRealm {
public LoginController() {
setName("realmA"); //This name must match the name in the User class's getPrincipals() method
setCredentialsMatcher(new SimpleCredentialsMatcher());
}
public String authenticate() {
try {
// Example using most common scenario of username/password pair:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
currentUser.login(token);
} catch (UnknownAccountException uae) {
xxxxxxxxxxxxx
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
AuthenticationInfo authinfo = null;
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
Account account = new Account();
account.setLoginId(token.getUsername());
char[] password = token.getPassword();
account.setPasswrd(password.toString());
account = services.findAccountByUser(account);
if (account != null) {
System.out.println("password:" + account.getPasswrd());
checkNotNull(account.getPasswrd(), "No account found for user [" + token.getUsername() + "]");
if (account.getPasswrd().equals(new String(password))) {
System.out.println("User password and Instore password Matched");
authinfo = new SimpleAuthenticationInfo(account.getLoginId(), account.getPasswrd().toCharArray(), getName());
}
}
return authinfo;
}
error:
Login Failed: Authentication failed for token submission [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=false]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).
questions:
1.pls check if the code and config are correct
2. doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException is this function loaded automatically or we need to call this ?
First off, great tutorial. This left me with mixed feelings tho. If I am just starting to implement security (basically blank slate), would this be something you would recommend, or should I continue to look? Your conclusion yourself seems to be of mixed feelings. Have you migrated your projects to this?
@J-P: no, we haven't migrated to Shiro yet. We wanted an "out the box" working solution for JSF. My fellow Arjan Tijms started off OmniSecurity some time ago which is built around JASPIC, Java EE's own relatively unknown authentication API. When the time allows it, I'll dive into it covering all missing aspects.
Thanks! Very helpful. I will keep an eye on OmniSecurity. What is the best way to contribute to see this project become reality at this point?
@BalusC I wanted to call my EJB method in my Realm as I already have a method to get the hashed password in my EJB not I dont like the idea of using native queries and datasource at web, but @EJB fails and i get null object reference, my EJB is local and I am using jboss 7.1
@BalusC I am using custom Realm and wanted to call ejb method to get Hashed Password in doGetAuthenticationInfo but I am geting null reference for @EJB in my realm, my ejb is local and I am using jboss 7.1, can you help ?
Thanks for really good post as always BalusC. I am using Glassfish 3.1.2.2 and HTML5-pages. I just followed your example and created app-folder and html-page under it. Yes Basic-auth is working, but after that I got a WARNING: StandardWrapperValve[default]: PWC1406: Servlet.service() for servlet default threw exception
java.lang.RuntimeException: WEB5001: Exception during processing of event of type AFTER_FILTER_EVENT for web module StandardEngine[glassfish-web].StandardHost[server].StandardContext[/Sesame] After that any page under the context is not working.
Hello BalusC
can we use apache shiro without maven , i'm developping a simple javaee webapp on eclipse with tomcat 7 and i m very confused about the security layer
@Riadh: This blog already descibes the procedure without Maven.
Thank you Balusc
because all the exemples that i saw were in maven , i was lost
if i may ask , if there is a tutorial about a jpa toplink crud application (jsf or primefaces) communicates with
an android application
i still have 20 days and i'll present my graduation project , i wont you forget your help
OK i have checked out a number of examples and am getting confused now, Tell me is the any code that i have to include on the login button to refer to SHIRO .ini file or what?
Awesome. Thanks Balus C. Great Job!
Have you tried PicketBox / PicketLink?
Thank you for your article. Just tried out Shiro according to your tutorial today and I am pretty sure it should work better than my self developed Glassfish-Realm.
I already started implementing my own Shiro-Realm based on my existing user database and with support for BCrypt. Thank you so far.
Very informative. Explaination is superb. Thanks for the post BalusC.
Balusc thanks for the great post. I followed your way to build my project but I bumped into a serialization problem when I needed to use @ViewScoped or @SessionScoped beans. Then I realized that you have used only @RequestScoped beans. Have you ever faced the same problem? Do you have a suggestion?
@BalusC First off, thank you for your article.
However, i have some trouble when i activate shiro by adding the filter section in the web.xml.
Indeed, all the CSS files are not loaded.
I use :
- Glassfish 3
- shiro 1.2.2
- primeface 3.5
Have you ever faced the same problem?
Do you have a suggestion?
Apparently you've configured Shiro to block CSS requests as well. Perhaps you used /** instead of e.g. /app/**.
In that case, you need to map all JSF resource requests to "anon" user as follows:
/javax.faces.resource/** = anon
Thank you BalusC,
Indeed it was just a matter of right on the resources directory.
Hi Balus,
Your code works great! I am using for one of my apps right now.
Actually, I'm facing an issue you may have solved it already.
When injecting an EJB that resides in the Web container, the SecurityUtils.getSubject() works ok.
The problem is when I try to do it on an injected EJB from another container (even an ejb jar in the same EAR).
The error I get is: Caused by: org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
Do you have any clues on how to solve this? Thanks in advance!!
Hi BalusC
When I create a new JSF project using Programmatic login why I get this error the first time I press the Login button:
Unable to find matching navigation case with from-view-id '/login.xhtml' for action login.submit}' with outcome '/Languages/javax.faces.resource/theme.css.xhtml?ln=primefaces-aristo'
Thanks in advance
@John: apparently you've configured Shiro to block resource requests as well, maybe by an overly restrictive URL pattern like /** ?
In that case, head to this answer.
Thanks Bauke. Really you are the greatest one
JSF and CDI support have been contributed to Apache Shiro by the user community, but have not been included in a release so far.
To fill this gap, these components are now available as an independent project Pax Shiro, compatible with Apache Shiro 1.2.2.
Hello BalusC,
I created a dynamic web project in Eclipse and ran the examples. But I am not able to get one thing..
Assume I have set the <welcome-page/> as index.xhtml. Now if I run the app by right-clicking and using Run on Server on the page, the ShiroFilter intercepts the request. However, if I do the same by right-clicking on the project instead, the filter gets bypassed. Is there any change required in the filter-mapping to prevent this behavior?
I am using JBoss 7.0.2.
Thanks,
Vrushank
P.S. The documentation for the logout() method is indeed mentioned, however, not in the web documentation but in the Authentication Guide :)
Restriction in HTTP requests
Is there a way to enable access to a specific path to Role X OR Role Y.
For example
/PageForAdminOrSuperUser.jsf = user, roles[ADMIN]
/PageForAdminOrSuperUser.jsf = user, roles[SUPER]
According to the comment one line with multiple roles requires the user to be in both roles.
I tried multiple ini lines with different roles but it looks like the access is provided as defined in the last line
You should give the desired SUPER user the ADMIN role as well.
Hi Bauke,
The Super & Admin example is probably not best demonstrating the scenario that I was talking about.
The following shiro user thread covers this topic -
'AND or OR roles filters ?' - http://shiro-user.582556.n2.nabble.com/AND-or-OR-roles-filters-td2741505.html#a2745765
The solution that we applied was simply adding some more roles to achieve the 'OR' functionality.
Overall we are content with the Shiro solution so thanks for this great post...
good article, thanks for this sharing
good article, thanks for sharing
This article really made my day, thanks!
great post, thanks
When I log out the logout message doesn't appear:
Thanks Baluc. whatever we tried in my application you have been posted in you blog or stack overflow thank you jiiii...
Thanx BalusC . Indeed this was really helpful in understanding about how to configure shiro . I did this for my ADF Project. Thanx again.
Thanks BalusC! Very good article. You helped me a lot!
Thanks,
Awesome analysis, I was struggling to select a Security framework for my JEE6 project.. Just one question, have you tried Picketlink aka Seam Security ??
Thanks again,
Cristhian.
I got a job by saying this answer in my last interview. thanks for awesome help.
I got more idea about Java from Besant Technologies. If anyone wants to get Java Training in Chennai visit Besant Technologies.
http://www.besanttechnologies.com/training-courses/java-training
Great Post BalusC.Hope it saved two or three of my days.
Thanks
Hi ,
I have implemented Apache Shiro as per your article. The authentication/authorizations were successful, however I couldn't get Remember Me feature working. The browser gets the remember me cookie. When I access the URL second time, it is still pointing to Login page only. I have used java program to authenticate user. I am not sure how remember me works without code to redirect the user to home page since authentication happens through java class not through shiro.ini file. Appreciate your help
Thanks
Rao
@RSisto
Thanks for your comment. Had the same problem and it got me thinking. I'm using JBoss and configuring the jboss-deployment-structure.xml so that the .jar points to the .war fixed it.
@Jonathan Rosenblitt:
Could you please tell me which entry you added to jboss-deployment structure in order to make it work?
Thanks men you save me hours of work =)
The best article regarding this subject ...Thank you for the great work BalusC
Hey BalusC, thanks a lot for this comprehensive tutorial. Regarding the issue of defining /login.xhml in multiple places, apparently you can just use: shiro.loginUrl=/login.xhtml
---
This is a special configuration directive that tells Shiro “For any of Shiro’s default filters that have a loginUrl property, I want that property value to be set to /login.jsp.” Source
I have a warning using Interceptor and "PostConstruct" annotation (in netbeans), the code runs ok. What's this means? Thanks for share your knowledge, your posts and your explanations are very nice.
@Named
@ViewScoped
@ShiroSecured
public class MyBeanController....
@PostConstruct
public void init(){...}
message error:
an interceptor for lifecycle callbacks may only declare interceptor binding types that are defined as @Target(TYPE). Interceptor com....ShiroSecuredInterceptor declares mismatch interceptor binding com...ShiroSecured
Hi Baluc,
I am getting the following exception
Caused by: org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
at org.apache.shiro.SecurityUtils.getSecurityManager(SecurityUtils.java:123)
at org.apache.shiro.subject.Subject$Builder.(Subject.java:626)
at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)
at com.eplm.travel.security.ShiroSecuredInterceptor.interceptShiroSecurity(ShiroSecuredInterceptor.java:36)
The error I get when I use interceptor that you mentioned in the blog.
I have the POM Project with EAR having separate ejb,web and JPA Module
Could you please give some hint so that I can resolve this
Best Regards
Prajeesh Kumar
Hi BalusC,
Great tutorial, but I've got a problem I can't find a solution to. I have a shiro aware web application using a jdbcRealm, currently installed on a glassfish 3.1.x.x server. Works perfectly well. I tried to set up a glassfish 4.0 server using the same database. When I start my web application on the glassfish 4.0 server, I cannot get the authentication to work.
I don't even know what question to ask as to why this isn't working. I can still access it through the 3.1 server without any problems.
I realize this is a little off-topic, but if you could provide some insight, I'd greatly appreciate it.
Very nice post thanks you
How to implement salted MD5 hashing for transporting user password from Browser to the Server where the Shrio Module resides?
Thanks you Great post
Off-topic! "Make Shiro JSF ajax aware" explains how to work with ajax timeout. We are using CA's siteminder for authentication. This tool is not ajax-aware and does not have facility to make it so. When it redirects - the application never sees it. The user keeps clicking on the screen with no response. Any suggestion on how to redirect in such a situation?
Awsome, thanks.
You are awesome, thank you very much. JSF would not be the same without you, I've learned sooo much with you for the past year.
A great Job indeed, keep it up!
Cheers!
Very useful. Thanks for posting.
Bauke Scholtz great post, I know this post is a bit old, but i want to ask you if you tried PAC4J? (http://www.pac4j.org/) I want to know your opinion. I think is a good java security library. Thanks!
Hey Balusc, I just wanted to say thanks, this line:
user.loginUrl = /login.xhtml
was a life saver for a problem I was having regarding shiro. If I could buy you a beer or coffee I would!
I used this article to integrate Shiro with Jakarta 8
I took this article and (after 7+ years of development) created a library that simplifies and implements all of this (and much more) here:
https://github.com/flowlogix/flowlogix/tree/main/jakarta-ee/shiro-ee
Post a Comment