Sunday, March 27, 2011

Apache Shiro Part 1 - Basics

Apache Shiro, originally called JSecurity, is Java security framework. It was accepted and became Apache top level project in 2010. It aims to be powerful and easy to be used.

The project is in active development with active both users and developers mailing lists. Most important areas are documented on its web page. However, it has lot of gaps in documentation. It is not possible to learn to use most Shiro features from documentation alone. Luckily, the code is well commented and where I tried it was also easily readable.

Main Shiro features are:
  • authentication,
  • authorization,
  • cryptography,
  • session management.

In this article article we try and demonstrate various Shiro features. We start with simple unsecured web application, then we add security features into it.  All code code is available in SimpleShiroSecuredApplication project on Github.

Unsecured Application

Unsecured application code is located in unsecured_application branch. Application represents an internal system for a fictional company. The company has four departments:
  • administrators,
  • repairmen,
  • scientists,
  • sales.

Each department has its own page. Each page contains buttons that are used by users to do their work. When user presses button, the work is done. For example, any repairmen can go to repairmen page and press button "Repair Refrigerator". The button repairs refrigerator and shows success message.

Each user have his own account page. Account page contains user's private data. Since unsecured application has no users yet, account page does nothing. Additionally, there is a page which contains all application functions. Everything anybody can do is possible to be done on this page.

Anybody can do any work and see all the pages.Sample application is run in test class RunWaitTest. It is not the best practice to use unit test this way, but it is not important now. If you run the class the application will be available at http://localhost:9180/simpleshirosecuredapplication/ url.

Adding Authentication

First, we have to verify user's identity. The easiest and most standard authentication is done through user name and password. User fills in his user name and password and system verifies whether supplied values match with some user account.

For simplest applications, it is sufficient to store user names and passwords in plain text files. In more realistic scenario, user name and password are stored in persistent storage or the verification is done through other system such as ldap or active directory. Shiro supports all mentioned authentication methods. If out of the box authentication features are not sufficient, it is possible to extend the framework with own verification implementation.

In this chapter, we add user name and password based authentication into the application. User name and password are stored in static plain-text Shiro ini file.

New requirements: It is possible to log in and log out users. Application is to be accessible only for logged users. Successful log in redirects user into his own account page. All application functions and pages are still accessible to any logged user.

Needed steps:
  • add Apache Shiro,
  • create log in page,
  • configure users and password,
  • create log out page.
Add Apache Shiro
Shiro is integrated into web application through servlet filters. A filter intercepts requests and responses before servlet and performs all necessary tasks (such as identifying currently logged user, attaching logged user to current thread, ... ). Default Shiro filters provide basic security features such as:
  • enforcing user log in,
  • enforcing ssl,
  • checking of page access rights.

If you want to learn more about default Shiro filters, good place to start is DefaultFilter enumeration. It lists all Shiro filters available by default. If those are not sufficient for your needs, you may create custom one.

We will use highly configurable IniShiroFilter. It reads Shiro configuration from ini file and initializes security framework. It does not perform any security checks. Permission checks, user login, protocol checking etc. are all delegated to either default or custom filters. IniShiroFilter only initialize them.

Ini configuration is described in both documentation and javadoc. Ini file configuration has four sections:
  • Section [main] contains Shiro initialization. Filters and custom objects are configured here.
  • Section [users] defines users, passwords and roles.
  • Section [roles] associates roles with permissions.
  • Section [urls] specifies access rights to application pages (urls). It is done by binding either default or custom filters to urls. 

Add Apache Shiro dependency to pom.xml:
<properties>
    <shiro.version>1.1.0</shiro.version>
</properties>
<dependencies>
    <dependency>
        <groupid>org.apache.shiro</groupid>
        <artifactid>shiro-core</artifactid>
        <version>${shiro.version}</version>
    </dependency>
    <dependency>
        <groupid>org.apache.shiro</groupid>
        <artifactid>shiro-web</artifactid>
        <version>${shiro.version}</version>
    </dependency>
</dependencies>

Create Shiro.ini file and put it on classpath. Configure web.xml to call IniShiroFilter before each request:
<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
    <init-param>
        <param-name>configPath</param-name>
        <param-value>classpath:Shiro.ini</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Create Login Page
Login page is simple html page with submit button, user name and password fields. Login functionality is handled by default Shiro authc filter. Authc filter allows url access only to logged in users. If the user is not logged in, filter will redirect him to login page.

Form on login page must have name 'loginform' and its submit method must be 'post'. Create login.jsp page:
<form name="loginform" action="" method="post">
<table align="left" border="0" cellspacing="0" cellpadding="3">
    <tr>
        <td>Username:</td>
        <td><input type="text" name="user" maxlength="30"></td>
    </tr>
    <tr>
        <td>Password:</td>
        <td><input type="password" name="pass" maxlength="30"></td>
    </tr>
    <tr>
        <td colspan="2" align="left"><input type="checkbox" name="remember"><font size="2">Remember Me</font></td>
    </tr>
    <tr>
        <td colspan="2" align="right"><input type="submit" name="submit" value="Login"></td>
    </tr>
</table> 
</form>

Enable authc filter for all application pages:
[main] 
# specify login page
authc.loginUrl = /simpleshirosecuredapplication/account/login.jsp

# name of request parameter with username; if not present filter assumes 'username'
authc.usernameParam = user
# name of request parameter with password; if not present filter assumes 'password'
authc.passwordParam = pass
# does the user wish to be remembered?; if not present filter assumes 'rememberMe'
authc.rememberMeParam = remember

# redirect after successful login
authc.successUrl  = /simpleshirosecuredapplication/account/personalaccountpage.jsp

[urls]
# enable authc filter for all application pages
/simpleshirosecuredapplication/**=authc
Update: Shiro automatically performs context-relative path matching. As SimpleShiroSecuredApplication does not have context path set, full paths in Shiro.ini are necessary. However, if application context path would be /simpleshirosecuredapplication, then paths could be relative: e.g. simple /**=authc or /account/personalaccountpage.jsp.

As it is not safe to send non-encrypted user name and password through network, we should force ssl logins. Ssl filter does exactly that. It has an optional parameter: ssl port number. If the port parameter is omitted, it uses default ssl port 443.

Before configuring ssl in Shiro, we have to enable it on web server. How to do that depends on web server. We show how to enable it in Jetty. First, create keystore with self signed certificate:
keytool -genkey -keyalg RSA -alias jetty -keystore keystore -storepass secret -validity 360 -keysize 2048

Answer all questions and in the end press enter so the keystore password and key passwords are the same.

Second, add the keystore to the project and configure Jetty to use ssl. Java code is available in AbstractContainerTest class.

Now, it is possible to configure ssl filter in Shiro.ini:
[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc
# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc

Configure Users and Password
SimpleShiroSecuredApplication is now available only for logged users. We now need to add some users so people can log in. Configuration is done in [users] section of Shiro.ini file. Format of section entries is:
username = password, roleName1, roleName2, ..., roleNameN
Following section creates seven users, all have the same password 'heslo':
[users]
administrator=heslo,Administrator
friendlyrepairmen=heslo,repairmen
unfriendlyrepairmen=heslo,repairmen
mathematician=heslo,scientist
physicien=heslo,scientist
productsales=heslo,sales
servicessales=heslo,sales

It is now possible to log in into the application. However, no reasonable error message is shown if the user makes a mistake. Moreover, passwords are stored in plain text file.

Error Handling
If user makes an error while logging in, Shiro redirects him back to the login page. The page looks exactly the same as before, which may confuse the user.

New requirement: show error message after each unsuccessful log in attempt.

Any time authentication error occurs, an exception is thrown. By default, form authentication filter catches the exception and stores its class name in request parameter. As we wish to customize data send to page, we have to extend FormAuthenticationFilter and override setFailureAttribute method:
@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
  String message = ae.getMessage();
  request.setAttribute(getFailureKeyAttribute(), message);
}

Replace form authorization filter with VerboseFormAuthenticationFilter and configure it to use 'simpleShiroApplicationLoginFailure' request attribute to hold error information:
[main]
# replace form authentication filter with verbose filter 
authc = org.meri.simpleshirosecuredapplication.servlet.VerboseFormAuthenticationFilter
# request parameter with login error information; if not present filter assumes 'shiroLoginFailure'
authc.failureKeyAttribute=simpleShiroApplicationLoginFailure

Show error in login.jsp page:
<% 
  String errorDescription = (String) request.getAttribute("simpleShiroApplicationLoginFailure");
  if (errorDescription!=null) {
%>
Login attempt was unsuccessful: <%=errorDescription%>
<% 
  }
%>

Beware: real application should not show too much login error information. Message 'Login attempt was unsuccessful.' with no further info is usually enough.
Hashing Passwords
Current application version has all passwords stored in plain text. It is better to store and compare only password hashes.

Objects responsible for authentication are called realms. By default, Shiro uses IniRealm with pluggable password matcher to compare passwords. We will replace passwords in ini by their SHA-256 hashes and configure IniRealm to use SHA-256 hashing matcher.

Generate SHA-256 hash of password:
import org.apache.shiro.crypto.hash.Sha256Hash;

public static void main(String[] args) {
    Sha256Hash sha256Hash = new Sha256Hash("heslo");
    System.out.println(sha256Hash.toHex());
}

Configure Shiro to compare password hashes instead of password itself:
[main] 
# define matcher matching hashes instead of passwords
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName=SHA-256

# enable matcher in iniRealm (object responsible for authentication)
iniRealm.credentialsMatcher = $sha256Matcher

Replace users passwords by password hashes:
[users]
administrator=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, Administrator
friendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
unfriendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
mathematician=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, scientist
physicien=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,  scientist
productsales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,        sales
servicessales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,  sales

Note: it is not possible to specify salt in ini configuration.
Create Logout Page
Any application that have login feature should have also logout feature. Logging out current user with Shiro is easy, use command:
//acquire currently logged user and log him out
SecurityUtils.getSubject().logout();

Logout page then looks like this:
<%@ page import="org.apache.shiro.SecurityUtils" %>
<% SecurityUtils.getSubject().logout();%>
You have succesfully logged out. 

Adding Authorization

We conclude first part by adding authorization to the application. We begin by limiting users access to pages. No user should be able to see pages of other departments. This provides only partial security to the project, since the user is still able to use 'all application functions' page or edit URL in the browser to do any action. We will call it page level authorization.

Then, we limit users ability to perform actions themselves. Even if user opens 'all application functions' page or edits url in the browser, he will be allowed to perform only functions specific to his department. We will call it function level authorization.

New requirements: user is not able to see pages of departments he does not belong to. User is able to perform only his departmental functions. Only exception to previous rules is administrator, who can perform both administrative and repair functions.
Page Authorization
Page level authorization is done with roles filter. Parameter part of the filter may contain any number of roles. Logged user can access page only if he has all supplied roles.

As usual, roles filter is configured in Shiro.ini file:
[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc

# only users with some roles are allowed to use role-specific pages 
/simpleshirosecuredapplication/repairmen/**=authc, roles[repairman]
/simpleshirosecuredapplication/sales/**=authc, roles[sales]
/simpleshirosecuredapplication/scientists/**=authc, roles[scientist]
/simpleshirosecuredapplication/adminarea/**=authc, roles[Administrator]

# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc

Test whether security works: log in as any sales user, click home, click 'repairmen page' link. You will see an ugly error. 

We finish page authorization and replace error with redirect to an error page. Default Shiro filters have property unauthorizedUrl. In case of unauthorized access, the filter will redirect user to specified url.
[main]
# redirect to an error page if user does not have access rights
roles.unauthorizedUrl = /simpleshirosecuredapplication/account/accessdenied.jsp

accessdenied.jsp:
<body>
Sorry, you do not have access rights to that area.
</body>

Functions Authorization
All departmental pages are secured now. However, any user can still perform any function on 'all application functions' page. Moreover, any logged user can edit url and thus do any action. For example, if you log in as sales and put https://localhost:8443/simpleshirosecuredapplication/masterservlet?action=MANAGE_REPAIRMEN into url, the application will perform manage repairmen function too (then it will throw null pointer exception, but security breach was already done).

We assign unique permission to each function. They are in split into groups:
  • all permissions are in "functions" group,
  • all administrative permissions are in "manage" group,
  • all repair permissions are in "repair" group,
  • all sale permissions are in "sale" group,
  • all science permissions are in "science" group.

Shiro supports multi-level permissions represented as strings. Levels are separated with symbol ':'. E.g. "functions:manage:repairmen" has three levels: "functions", "manage" and "repairman". Multi-level permissions allow for easy permissions grouping. For example, science group belongs to functions group and contains three permissions:
  • functions:science:research,
  • functions:science:writearticle,
  • functions:science:preparetalk.

Actions verify logged user permissions before doing their job:
public String doIt() {
    String neededPermission = getNeededPermission();
    // acquire logged user and check permission
    if (SecurityUtils.getSubject().isPermitted(neededPermission))
        return "Function " + getName() + " run succesfully.";

    throw new UnauthorizedException("Logged user does not have " + neededPermission + " permission");
}

NOTE: Another way how to achieve the same goal is through annotations.

The PerformFunctionAndGoBackServlet servlet catches authorization exception and converts it into error message:
private String performAction(String actionName) {
    try {
        Actions action = findAction(actionName);
        String result = action == null ? null : action.doIt();
        log.debug("Performed function with result: " + result);
        return result;
    } catch (ShiroException ex) {
        log.debug("Function failed with " + ex.getMessage() + " message.");
        return "Error: " + ex.getMessage();
    }
}

Finally, we need to configure permissions to roles in Shiro.ini file. Shiro supports wildcards for multi-level permissions. Thus, we do not have to specify each departmental permission separately:
[roles]
# members of departments should be able to perform all departmental functions
sales=functions:sale:*
scientist=functions:science:*
repairman=functions:repair:*

# administrators are able to do all management functions and repair functions
Administrator=functions:manage:*,functions:repair:*

You can now try functions on 'all application functions' page. If logged user does not have required permission, an error message appears on top of the page. Moreover, if you log in as sales and try hacking https://localhost:8443/simpleshirosecuredapplication/masterservlet?action=MANAGE_REPAIRMEN, you will see an error message in the console (instead of success message).

End

Final application is available in 'static_authentication_and_authorization' branch on Github.

In the second part we will create custom realm and move users, passwords, roles and permissions from ini file to database. The third part is dedicated to Apache Shiro cryptography package.

40 comments:

Les Hazlewood said...

Hi there,

Great writeup! Thanks so much for putting in the work and sharing with everyone.

I have one question though: why do you prefix all of your [urls] with '/simpleshirosecuredapplication/'?

If your application contect path is /simpleshirosecuredapplication, then shiro will automatically perform context-relative path matching. So your chains can just be:

[urls]
/repairmen/** = authc,...
/sales/** = authc,...
...

Cheers,

Les

Meri said...

Hi Les,

thank you for encouraging feedback :).

Thank you also for the context path information, it was new to me. I already updated the article with it.

As my application does not have context path set, I left examples as they was.

Meri

Les Hazlewood said...

Good stuff. I can't wait to see part 2! :)

Best,

Les

gilbertoca said...

Yes, excellent!
I've just translated it to Brazilian Portuguese[1]!

Regards,

Gilberto
[1]http://blog.gilbertoca.com/?p=626

Meri said...

Hi Gilberto,

thank you very much, it made my day :).

Meri

gilbertoca said...

Just some observations (you say: It is now possible to log in into the application, before password hashes):

- when you show us login.jsp page you forgot to put the form tags:
form name="loginform" action="" method="post" and /form
-when I setup the shiro filter in the web.xml file I've put it after the PerformFunctionAndGoBackServlet servlet and so it not worked.
-it would be good to say that, at this stage, we can login using any user (in the [users]section) with the password 'heslo', but the application won't show anything nor the log, when you enter an no existent user or a different password.

Regards,

Giberto

Meri said...

Hi Giberto,

thank you for the feedback, I updated the post. The biggest change is new chapter 'Error Handling', so the application will show something if you enter wrong username or password.

With Regards,
Meri

Anonymous said...

I have been searching for some online help on Shiro, finally found it here. Very nice article for a newbie. Helped to get a clear idea on Authentication. Simple and good presentation too.

Raja SMA said...

can disable user account after 5 login attempts failure in shiro api? How to do this?

Meri said...

Hi Raja SMA,

Shiro is an authentication framework and is unable to disable user account. You need an 'intrusion detection' framework. You have two options:

1.) Use AppSensor as an intrusion detection framework. I wrote two posts about it, you can read them to get you started:
* AppSensor - Intrusion Detection
* AppSensor - Integration with Shiro.

If you decide to use AppSensor, you have do three things:
* integrate AppSensor with Shiro,
* create custom Shiro realm and put 'login failed' detection point into it,
* configure AppSensor to lock account after repeated login failures.

I would suggest also to:
* write custom intrusion store to keep all intrusions in a persistent storage,
* write custom account disabler to keep list of disabled accounts in a persistent storage.

All those thing are described in the above articles. Hopefully, it should not be difficult.

2.) Write your own intrusion detection framework. It does not have to be big, probably 2-3 small classes. Read AppSensor overview to get an idea on how you might do that - their design is solid and practical.

If you decide to go this way, you have to:
* create failed logins storage (the class would keep them in database),
* create custom Shiro realm which will add a new intry into your failed logins storage,
* override Shiro ModularRealmAuthenticator to consult failed logins storage after each login attempt (see Disable Account chapter in AppSensor - Integration with Shiro).

Whether you decide to use AppSensor or not, the work is almost the same. The advantages of AppSensor:
* flexible and configurable,
* once it is integrated, all kinds of intrusion detections are available for free.

The advantages of own solution:
* minimalist solution - simplicity.

Good luck.

Raja SMA said...

Thanks Meri...

blurblurNick said...

Hi Meri,

I had some issue over the "Remember Me".
I found out that my "Remember Me" is not working.
When user get back to the login page, the username and password field still empty.
In my login servlet class, i had execute the token.setRememberMe(true); whenever the remember me checkbox was ticked.
I wonder is there any configuration need to be done in the shiro.ini for this feature to be working?

Regards,
Nick

Meri said...

Hi blurblurNick,
"Remember Me" does not require special configuration.

However, "Remember Me" is not able to fill username and password. Instead, this feature will automatically log the user in next time he comes back (even if the session expired in the meantime).

When the user comes back, he will not see the login page. He will go directly to the requested page.

I think that you are using Shiro differently than I did. I neither used login servlet class nor manipulated the token directly.

Instead, I integrated Shiro into web application through servlet filters. Filters run before servlet classes and handle everything related to authentication.

The filter checks whether the user is logged or remembered and redirects him to the login page if needed.

The submit from the login page is handled in the filter too. The filter checks name and password and redirects user back to the originally requested page. If the user checked remember me, the filter puts a cookie with login info to his computer.

This approach has three advantages:
* You do not have to write own login class.
* You do not have to write own filters, Shiro provides them.
* Your servlets do not have to be aware of the login functionality. The security layer runs before the application itself.

If you can use Shiro authc filter instead of your own login servlet class, remember me functionality will work.

If you have to use your own login servlet, you will have to simulate remember me feature by yourself. In this case, you will have to download Shiro source code and see how they do it.

blurblurNick said...

Hi Meri,

Thanks for your reply.
Now I understand the Remember Me functionality.
Btw, if I was logout. Do the cookie will be cleared as well?
Actually, I was doing tutorial that I found on the apache shiro website. I had downloaded the source code as well. All the authentication was done by the filters, but when its comes to the submitting the login page, the tutorial are using the servlet to passing the username and password to the token. I wonder how this can be done with the filters. If you mind to share or a reference link will do as well.

Many thanks,
Nick

blurblurNick said...
This comment has been removed by the author.
blurblurNick said...

Hi Meri,

Thanks for your reply.
I had try out your way to authenticate the login page without the login servlet class (similar to your guides on this post), but still my Remember Me not working. It still show me the login page when I access to secure pages. To reconfirm, my loginform was having those default field names which is username, password and rememberMe. Any idea about this? Currently I was using latest Shiro 1.2.0

Thanks and regards,
Nick

Meri said...

Hi blurblurNick,

it seems like you are right - the remember me feature does not work the simple way I expected.

I debugged it and the good news is that the cookie is set and read correctly. The bad news is that according Shiro, the remembered user is not the same thing as the authenticated user.

This logic is located in createSubject method of the DefaultWebSubjectFactory class. Put a breakpoint there:
* the principals collection contains subject that was stored in the cookie,
* the authenticated boolean resolves to false.

I'm not sure whether it is a bug or by design. I found two threads on the similar issue:
* Remember Me behavior change
* Callout for remember me

The best thing you can do is to ask on the Shiro user mailing list . The Shiro team reads this list and usually answers all questions in details and fast. Alternatively, if you think this is a bug, create a new jira issue .

That being said, I have one solution I would suggest. It is in next comment.

Meri said...

Hi blurblurNick,

the blogspot does not allow me to use the code and pre tags in my comment. The code formatting will be little off and the whole comment less readable than would be optimal. I will prefix each code line with * to make it more readable. Sorry for that.

If all you need is to allow user to access the pages and you are fine with them not being authenticated, then create your own authentication filter and override its isAccessAllowed method.

Step 1: Extend either FormAuthenticationFilter class or AuthenticatingFilter class.

Step 2: The AuthenticatingFilter defines following isAccessAllowed method:
* protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
* Subject subject = getSubject(request, response);
* return subject.isAuthenticated();
* }

override it to:
* protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
* Subject subject = getSubject(request, response);
* return subject.isAuthenticated() || subject.isRemembered();
* }

Step 3: Configure Shiro to use this filter. How to do it is described in the part 2 of this tutorial. See the sub-chapter Servlet Filter of the Alternative Login - Certificates chapter.

This solution has one disadvantage/advantage: the user will have access to pages, but he will not be authenticated. From the security point of view, this may be an advantage. The system should require an explicit authentication before more sensitive operations (password change, money operations and so on). Log in through remember me should be enough to let the user see ordinary data, but it should not be sufficient for more sensitive operations.

If this is not good enough for you, then override the method createSubject of the DefaultSubjectFactory class.

Step 1: Extend DefaultSubjectFactory class.

Step 2: Change the line
* boolean authenticated = wsc.resolveAuthenticated();

to something like this:
* boolean authenticated = wsc.resolveAuthenticated();
* authenticated |= principals != null && !principals.isEmpty();

Step 3: Configure Shiro.ini to use your custom subject factory:
* customSubjectFactory=org.blurblurNick.package.NickSubjectFactory
* securityManager.subjectFactory=$customSubjectFactory

The remembered subject will be automatically authenticated. The system will act exactly the same way as if the user went through the login screen.

Meri said...

> Btw, if I was logout. Do the cookie will be cleared as well?

Yes, logout should clean the cookie. But, I think it would be a good idea to test whether it really works :).

blurblurNick said...

Hi Meri,

Really appreciate your long reply here. :)
I had try out your 1st option and it worked. I will take note on the isAuthenticated parts.
And I had tested the logout as well which the cookie will be cleaned.

Once again thanks for your advices here.

Best Regards,
Nick

Chets said...

Hi Meri,
I am using Shiro for the following flow.

1)Welcome.jsf => my login page.e.g. URL is https://localhost/myappcontext/welcome.jsf
2) With successful login user is taken to Overview.jsf URL becomes https://localhost/myappcontext/overview.jsf
3)Now the user is logged in successfully.Now I edit the url & keep it like https://localhost/myappcontext/.Now I want my overview.jsf to be displayed & not welcome.jsf.The reason is obvious that user is already logged in so why to loging second time.

I am not able to implement this.Please assist me.I just hope I am not missing any obvious thing.I am using tomcat+shiro+primefaces for my application.

Meri said...

Hi Chets,

once you are logged in, the login page will not show. You can edit URL as you wish as long as the cookie with session id is intact.

Assuming that Shiro is configured properly and there is something special about your setup, your case should work correctly.

Chets said...

Hi Meri,
I think I was not clear.
Let me try explaining my problem in another way.
1) welcome.jsf is the welcome file in web.xml.This jsf page has standard username,pwd n login button.So when I hit url like https://localhost/myappcontext,the welcome.jsf is rendered.

2) Now when the user logs in with correct credentials then the overview.jsf is shown & now the url is https://localhost/mypappcontext/overview.jsf.

3)Now the user is logged in successfully.so far so good.Now if I edit the url like https://localhost/mypappcontext & if hit enter then I want overview.jsf to be displayed as the user is logged in & not welcome.jsf.But in my case I see welcome.jsf.This is what I want to fix.If the user is logged in then i have to show overview.jsf and not welcome.jsf.

Hope it clarifies.

Chets said...

I think the problem is that when I hit url https://localhost/myappcontext/ that time the request is not routed thtough the auth filter from shiro.

Meri said...

Hi Chets,

in you case, the welcome page is also a login page. Whenever user see the welcome page, he sees login button and fields.

Shiro filter works like this:
* catch the request,
* if user NOT authenticated:
** redirect to the login page,
** let user login,
** redirect back to originally requested page,
* else (user IS authenticated:)
** request continue as if there would be no filter.

So, the point 3. works "as designed", but does not match requirements. The user is logged in, so Shiro allows him to see the requested page. Incidentally, the requested page contains a login button and fields.

The point 2. is a puzzle to me. In that case user requests welcome page. Shiro should catch the request and redirect it to login page. After login, it should redirect him to the page he asked for (e.g. welcome with login button).

Is it possible that your jsf page is handling the login instead of Shiro filter?
All user login logic should be handled by Shiro authentication filter, your code should never read the content of username and password fields.

If my guess about your system is right:
1.) Configure overview.jsf as a welcome file.
2.) Configure shiro authc filter to catch all requests [the urls section]
3.) Configure the page with login button and fields as shiro login page [form-based login in docs]

This way, authenticated user will hit the overview page and unauthenticated will be redirected to the login page.

Hope that helps,
meri

Chets said...

Hay Meri,
Thanks a lot for a detailed reply.
I tried exactly the same steps as per your guess.
Most of the things are working fine except one case.

1)At the start (when user is not logged in)if I hit url like https://localhost/myappcontext/ then it does not show login page,rather it shows overview.jsf with no data in it.

I think the problem is that when I hit url https://localhost/myappcontext/ that time the request is not routed thtough the auth filter from shiro.

I have configured auth filter to catch all requests i.e. url mapping in web.xml is /* but still the problem :-(

Meri said...

Hi Chets,

url-pattern tag in web.xml configures general Shiro filter. If it contains /* then the main Shiro filter should catch everything.

However, the main Shiro filter delegates the work to smaller specialized filters. Authc is one of them.

The main Shiro filter is configured in Shiro.ini file. The problem is most likely in that file.

There should be section [urls] which configures concrete filters for individual urls. Following line means, that authc filter catches every request:

/**=authc

If this does not help, you will have to debug Shiro itself to see what happens. Maven should be able to download sources from the central repository. Alternatively, you can download them from svn.

Find class PathMatchingFilter - authc filter extends it. Put the breakpoint into preHandle method. This method compares configured urls with request urls.

Access the welcome page from the browser. The server will stop at the breakpoint and hopefully you will be able to find the real problem.

Most likely, the /**=authc line is all you need. But if there is some other problem, that debug should help you to find it.

Meri

Nuno Santos said...

Hi Meri,

I've been trying to get your tutorial to work but, I'm a new user to both Apache shiro and Apache Maven.

I don't know how to add the snapshots to the settings.xml, and I think that's why it won't work. :/

I've tried googling it, but haven't been successful.

If you can shed me some light, I would appreciate it.

Nuno :)

Nuno Santos said...

snapshots
shiro snapshots https://repository.apache.org/content/groups/snapshots-group/ default


I've tried adding this to the project pom.xml but it still gives errors..

Nuno Santos said...

The above comment didn't show everything I wrote. Please check this link to see what I've added to the pom.xml: http://shiro-user.582556.n2.nabble.com/Snapshot-repository-td7438372.html

Meri said...

Hi Nuno Santos,

the article is a year old, Shiro released new version in between. Use 1.2.0 to get released version from the central maven repository.

This way, you do not have to set up snapshots repository.

If you insist on snapshot (which I do not recommend), use 1.3.0-SNAPSHOT version number. How to configure alternative maven repository is described here.

Nuno Santos said...

Thank you Meri :)

I'm a new user to both apache maven and shiro, but I'm getting the hang of it.

Cheers!

Sean Liang said...

Thank you for these articles which give me a good start for Shiro.

For the "Remember Me" discussion in comments, the official solution is use "user" filter instead of "authc". For example:

/low_secure/** = user
/high_scure/** = authc

Hope this helps.

jetty question said...

Thanks for the great article. I'm trying to get a login error back to the user but by returning a JSON message instead of using a login.jsp. Do you know where the hook is to allow me to not return the original login page but to instead return a formatted JSON error message?

Meri said...

@jetty question: you will have to override either onAccessDenied or saveRequestAndRedirectToLogin method of the FormAuthenticationFilter.

Then you have to replace FormAuthenticationFilter (authc) with your custom version. How to do it is described in "Error Handling" chapter of this post.

jetty question said...

Thanks. I was using onLoginFailure but I was not getting every error

Ruzalin Galiev said...
This comment has been removed by the author.
alifa said...

Hi

Do you have any idea how to get shiro work with two-factor authentication mechanisms like RADIUS?

Thanks

Meri said...

@alifa I never tried that, so no. You may try Shiro mailing list, they used to be very helpful there.

Satish Saini said...

Hi,
I am new in apache-shiro. I have write a simple web application in angularjs and deploy it in apache-karaf as a osgi bundle. I want to secure my application with shiro. I have configure the shiro.ini in web.xml as you described in blog. I am building my application with apache-maven. My application is deploy in karaf successfully. And when we access it in browser, login page comes first.But can't able to login in the application. Its redirect me on login page again. Below message is appearing on server log :-
111 - org.apache.shiro.web - 1.2.1 | Null or empty configuration specified via 'config' init-param. Checking path-based configuration.

Please help me.

I have another question also, in login form action is not specify, How this form work. Please explain me
Thanks,
Satish

Post a Comment