You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@shiro.apache.org by Allan Ditzel <al...@gmail.com> on 2011/06/16 04:08:02 UTC

Extending AuthenticatingFilter and AuthorizingRealm

Hi all,

I am in a situation where I need to extend AuthenticatingFilter in order to
provide some customized behavior for the web application I'm building. This
application is system-to-system and will not have any visible UI. All
operations are going to be done via HTTP POST's. All URL's are going to be
protected by shiro except for login and logout (as expected), however if a
user is not authenticated then there will be no HTTP 302 redirect to a login
form. Instead an HTTP 200 will be returned with an xml payload that contains
our application specific error codes.

Therefore I am extending the AuthenticatingFilter and
implementing createToken() and onAccessDenied(). However, I'm a bit fuzzy of
the lifecycle of the framework and when each of these methods will be called
and what their interaction is (whether direct or indirect).

Also, I am implementing a realm that integrates with our existing data layer
that provides authentication functionality, therefore I'm extending
AuthorizingRealm to interact with our existing data layer. My question there
lies in what happens if my application doesn't perform authorization, and
only performs authentications? How should the doGetAuthorizationInfo()
method behave? Should have any interaction with doGetAuthenticationInfo() or
vice versa?

Any input would be most welcome!

Best Regards,

Allan

Re: Extending AuthenticatingFilter and AuthorizingRealm

Posted by Allan Ditzel <al...@gmail.com>.
Thank you, Les! The definitely clears things up a lot. One more question:
I'm wiring Shiro in Spring, so would the filter bean definition look
something like this? (currently depending on Shiro 1.1.0)

    <bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="customFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /login = anon
                /logout = anon
                /** = authc
            </value>
        </property>
    </bean>

Systems integrating with us via our web based application will explicitly
hit a /login url and a /logout url.

Thanks again!

Allan

On Wed, Jun 15, 2011 at 11:32 PM, Les Hazlewood <lh...@apache.org>wrote:

> P.P.S.
>
> I think this wins as my longest email on this list ever! Which, if you
> know me, says a lot, since I can be pretty long winded ;)
>

Re: Extending AuthenticatingFilter and AuthorizingRealm

Posted by Les Hazlewood <lh...@apache.org>.
P.P.S.

I think this wins as my longest email on this list ever! Which, if you
know me, says a lot, since I can be pretty long winded ;)

Re: Extending AuthenticatingFilter and AuthorizingRealm

Posted by Les Hazlewood <lh...@apache.org>.
P.S.

A shiro.ini setup for your custom filter would probably look like this:

[main]
# override the default 'authc' filter with the custom one:
authc = com.foo.my.AuthenticationFilter

[urls]
# logout filter available in trunk only - will be in 1.2:
/logout = logout
# everything else requires authentication:
/** = authc

Cheers,

-- 
Les Hazlewood
CTO, Katasoft | http://www.katasoft.com | 888.391.5282
twitter: http://twitter.com/lhazlewood
katasoft blog: http://www.katasoft.com/blogs/lhazlewood
personal blog: http://leshazlewood.com

Re: Extending AuthenticatingFilter and AuthorizingRealm

Posted by Les Hazlewood <lh...@apache.org>.
Hi Allan,

When you configure any filter that subclasses AuthenticationFilter,
the Subject must be authenticated in order for the request to be
allowed through the filter chain on to its final destination.  If not
authenticated, the behavior is up to you - AuthenticatingFilter
subclasses will often perform a login attempt if the information can
be found to do so and all others will typically block the request.

I'll cover flow, but let me first show you the relevant parts of
Shiro's Filter hierarchy that helps you automate this:

AdviceFilter( allows AOP-style pre/post/finally 'advice' during filter
chain execution)
|-- PathMatchingFilter (allows filter to react to path-specific config
during a request)
    |-- AccessControlFilter (allows or denies a request based on some
Subject state)
        |-- AuthenticationFilter (specifically allows or denies a
request based on authentication state)
            |-- AuthenticatingFilter (can automatically perform a
login if not authenticated)

So, for AuthenticatingFilter instances, the flow works as followed:

1.  The AdviceFilter's onPreHandle method is invoked.  This is the
AOP-style method that is invoked to determine if the chain execution
should occur at all.
2.  The AccessControlFilter implementation of onPreHandle performs a check:
    if (isAccessAllowed(...)) {
        return true; //allow the request to continue
    }
    //otherwise access isn't allowed,
    //allow the filter to react to the denial:
    return onAccessDenied(...);

3.  For AuthenticationFilter instances, the isAccessAllowed method
implementation returns true if Subject.isAuthenticated(), false
otherwise.
4.  For AuthenticatingFilter instances, the onAccessDenied method
returns false (as expected, because the user isn't authenticated),
_unless_ it wishes to perform an authentication attempt based on the
current request.

It is often convenient to automatically perform an authentication
attempt during onAccessDenied, probably because you've determined that
although they're not yet authenticated, they're currently accessing
the authentication url and have provided proper principals/credentials
to authenticate.  If the url being accessed *does not* represent a
current authentication attempt however, the onAccessDenied method
should return false as expected to prevent the filter chain from
continuing.  You can also set headers and do other things in this case
too to indicate that the caller isn't authenticated and shouldn't be
accessing the URL until they log in (e.g. set HTTP 401 response code).

The HttpBasicAuthenticationFilter is a good example of this 'if
they're trying to login, then login, if not, deny access and challenge
for authentication' workflow.  For example, its onAccessDeniedMethod
looks like the following:

protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
    boolean loggedIn = false; //false by default or we wouldn't be in
this method
    if (isLoginAttempt(request, response)) {
        loggedIn = executeLogin(request, response);
    }
    if (!loggedIn) {
        sendChallenge(request, response);
    }
    return loggedIn;
}

The isLoginAttempt method and sendChallenge methods are specific to
that class, but I'm assuming you'll want to do similar things based on
your custom XML mechanisms.  The executeLogin method however is
already written and available from the AuthenticatingFilter
superclass, so you won't have to write that (executeLogin calls
createToken, so make sure your createToken method returns a valid
AuthenticationToken based on the inbound request).

That should explain the flow.  It sounds like all you'll need to do is
implement createToken and onAccessDenied to perform the 'auto login or
else deny' convenience workflow.

As for Realm authentication vs authorization, they two operations are
totally orthogonal.  Authentication can occur completely independent
of authorization and vice versa.

If you subclass AuthorizingRealm, most of the authc/authz logic is
written for you, you just need to provide the raw authc/authz data so
the superclass implementations can execute that logic.

The doGetAuthenticationInfo method returns information from your
Realm's data source that is only specific to authentication -
typically a username/password pair or some other principal/credential
pair (biometric, etc).  That's it.  Either the submitted
AuthenticationToken's credentials match the AuthenticationInfo
returned from the datasource (in which case the authentication is
considered successful), or they don't match, in which case the attempt
is considered failed.  The matching is performed separately by a
pluggable CredentialsMatcher component that is used by all
AuthenticatingRealm subclasses.

The doGetAuthorizationInfo method functions in the exact same way, but
instead of providing principals/credentials for authc matching, it
returns any roles and/or permissions attributed to the subject.  The
parent class can check the returned AuthorizationInfo to perform the
authorization logic.

The reason why these two are completely separate makes sense when you
see that they can happen independently at runtime.  Authorization only
needs a Subject identity to perform authz checks.  For example, when a
user is remembered from RememberMe services, they're not considered
authenticated (because they haven't proven their identity), but Shiro
can still use that remembered identity to perform authorization
checks.

If your Realm's doGetAuthorizationInfo always returns null, then that
effectively disables authorization for that realm and any role or
permission check delegated to that realm will always return 'false' by
the parent AuthorizingRealm subclass logic.  See the
AuthorizingRealm's getAuthorizationInfo JavaDoc for detailed
explanation on how this works.

Note that authz caching is recommended for big performance benefits
and authc caching will work in 1.2 (in trunk only at the moment).  If
you configure Shiro with a CacheManager, you'll receive these
benefits.  When a CacheManager is configured, authz caching is enabled
automatically by default, but authc caching must still be turned on
explicitly.

HTH!

-- 
Les Hazlewood
CTO, Katasoft | http://www.katasoft.com | 888.391.5282
twitter: http://twitter.com/lhazlewood
katasoft blog: http://www.katasoft.com/blogs/lhazlewood
personal blog: http://leshazlewood.com