You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@shiro.apache.org by ryannelsonaz <mw...@hotmail.com> on 2011/08/10 22:21:40 UTC

Handling exceptions thrown by @Requires* annotations

I need a little help understanding how to intercept Exceptions thrown when a
@Requires* annotation fails on a given business method.  Jared made the
suggestion of adding a filter that catches the Exception and does the
redirect.  I implemented it like so (using Guice).

In subclassed ServletModule:

@Override
protected void configureServlets() {
	filter( "/*" ).through( AuthorizationFailureFilter.class );
	filter( "/*" ).through( GuiceShiroFilter.class );
	...
}

In new auth filter (AuthorizationFailureFilter):

@Override
public void doFilter( ServletRequest request, ServletResponse response,
FilterChain chain ) throws IOException, ServletException {
	try {
		chain.doFilter( request, response );
	} catch ( ServletException e ) {
		if ( e.getCause() instanceof UnauthenticatedException ) {
			// what to do here?  redirect to login.jsp?
		} else {
			throw e;
		}
	}
}

The problem I'm running into here is I get none of the automatic redirection
provided by the FormAuthenticationFilter.  I'd like my program to function
exactly as if I'd secured the URL with a path-based filter instead of an
annotation.  I.e., redirect to the login page, and after authentication,
redirect back to the SavedRequest.

Any suggestions on this?

--
View this message in context: http://shiro-user.582556.n2.nabble.com/Handling-exceptions-thrown-by-Requires-annotations-tp6673997p6673997.html
Sent from the Shiro User mailing list archive at Nabble.com.

Re: Handling exceptions thrown by @Requires* annotations

Posted by ryannelsonaz <mw...@hotmail.com>.
The reason I caught UnauthenticatedException explicitly is because that's
what gets thrown when a method is protected with @RequiresAuthetication. 
Ideally, I'd like to be able to handle all exceptions thrown by any of the
@Requires* annotations.

I've tried a few permutations based on your suggestions, and I'm afraid I'm
still not understanding enough to make this work.

As an experiment, I first tried implementing my custom filter like so:

public class AuthorizationFailureFilter extends FormAuthenticationFilter {
    @Override
    public void afterCompletion( ServletRequest request, ServletResponse
response, Exception exception ) throws Exception {
        if ( exception instanceof UnauthenticatedException || ( exception
instanceof ServletException && exception.getCause() instanceof
UnauthenticatedException ) ) {
            super.onAccessDenied( request, response );
        }
    }
}

I kept my servlet configuration the same as in my original post.  My
expectation based on my understanding of FormAuthenticationFilter is that
this should at least bounce me to the login page on an
UnauthenticatedException.  However, it didn't work, and it also broke my
existing FormAuthenticationFilter (which uses path-based filtering to secure
an unrelated URL called "/secure").  In both cases, I end up with errors
related to a missing SecurityManager.  The path-based filter throws this
explicitly, but the annotation-based filter swallows it in the AdviceFilter
to allow the original exeception to be propagated:

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.

I assume what's happening is that I have essentially 2 instances of
FormAuthenticationFilter floating around: the one assigned to the static
ShiroWebModule.AUTHC variable which I've added to my filter chain, and the
subclassed one--AuthorizationFailureFilter--that I assign explictly in my
Guice ServletModule.  Somehow the latter instance is not getting a
SecurityManager, and that's affecting the former.  I'm still trying to
understand how via the debugger.

My 2nd thought was that perhaps I should limit it to just one instance, so I
changed my ShiroWebModule like so:

public class BigBankShiroWebModule extends ShiroWebModule {
    protected void configureShiroWeb() {
		...
        Key<AuthorizationFailureFilter> myAUTHC = Key.get(
AuthorizationFailureFilter.class );

        addFilterChain( "/login.jsp", myAUTHC );
        addFilterChain( "/secure/**", myAUTHC );
	}
}

I then removed the explicit filter binding from my Guice ServletModule.  My
thought was that as long as the filter was in the chain, it should handle
both the regular path-based failures, plus call my custom afterCompletion()
method on failures.

This worked ... sort of.  It did resolve my problem with the regular
path-based authentication handling, in that hitting "/secure" again brought
me to my login page as it did with the normal FormAuthenticationFilter. 
However, through debugging I noticed that the FormAuthenticationFilter code
was all being hit twice.  It hits onAccessDenied(),
saveRequestAndRedirectToLogin(), and my afterCompletion() method all twice. 
I'm still trying to figure out why.

Hitting the @RequiresAuthentication-annotated method never even uses the
filter.  I guess even though it's in the filter chain, since the chain only
adds it for a specific path, it won't catch the exception.

I'm sure I'm going about this all the wrong way, but I still keep hitting
dead-ends.


--
View this message in context: http://shiro-user.582556.n2.nabble.com/Handling-exceptions-thrown-by-Requires-annotations-tp6673997p6681761.html
Sent from the Shiro User mailing list archive at Nabble.com.

Re: Handling exceptions thrown by @Requires* annotations

Posted by Jared Bunting <ja...@peachjean.com>.
I believe the reason (it's been awhile) that I overrode 'cleanup' is
because I didn't want the exception to be rethrown.  Off the top of my
head, I'm not sure if that will suppress the redirect or not, but I
wanted the distinct behavior of my filter "handling" the exception.

Basically, the way that 'afterCompletion' is invoked feels more like a
'finally' and what I wanted was a 'catch'.  However, like I said, it's
been awhile, and there's a chance that I completely misjudged how that
would play out.

-Jared

On 08/10/2011 04:06 PM, Les Hazlewood wrote:
> Jared's solution is definitely the right way to think of this, but I'd
> personally override the 'afterCompletion' method in the AdviceFilter
> superclass instead of 'cleanup'.
>
> 'cleanup' calls 'afterCompletion' in all cases, and will additionally
> handle any extraneous/unexpected exceptions that might be thrown from
> 'afterCompletion'.  And there is no need to call super.foo.  In this
> way I think it is a bit safer/more convenient to override
> afterCompletion rather than cleanup.  This is just my preference
> however.
>
> Cheers,
>
> Les


Re: Handling exceptions thrown by @Requires* annotations

Posted by Les Hazlewood <lh...@apache.org>.
Jared's solution is definitely the right way to think of this, but I'd
personally override the 'afterCompletion' method in the AdviceFilter
superclass instead of 'cleanup'.

'cleanup' calls 'afterCompletion' in all cases, and will additionally
handle any extraneous/unexpected exceptions that might be thrown from
'afterCompletion'.  And there is no need to call super.foo.  In this
way I think it is a bit safer/more convenient to override
afterCompletion rather than cleanup.  This is just my preference
however.

Cheers,

Les

Re: Handling exceptions thrown by @Requires* annotations

Posted by Jared Bunting <ja...@peachjean.com>.
Well, you could look at FormAuthenticationFilter and mimic its
behavior.  The AccessControlFilter (a superclass of
FormAuthenticationFilter) has some fairly straightforward
"saveRequestAndRedirect" and "redirectToLogin" methods.  Also the
"issueSuccessRedirect" in AuthenticationFilter.

However, it sounds to me like what you want is a
FormAuthenticationFilter that only redirects you to the login page if an
UnauthenticatedException is thrown, rather than anytime you're not
logged in?

if that's the case, here's some code where I added the same sort of
behavior to the BasicHttpAuthenticationFilter.  You should probably be
able to do something similar with FormAuthenticationFilter.  Note that
the behavior here is attempting to retain the behavior of
BasicHttpAuthenticationFilter if permissive = false.

public class BasicHttpPermissiveAuthenticationFilter extends
BasicHttpAuthenticationFilter
{

    private boolean permissive = true;

    public void setPermissive(boolean permissive)
    {
        this.permissive = permissive;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue)
    {
        return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginAttempt(request, response) && permissive);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception
    {
        return executeLogin(request, response) || sendChallenge(request,
response);
    }

    @Override
    protected void cleanup(ServletRequest request, ServletResponse
response, Exception existing) throws ServletException, IOException
    {
        if (existing instanceof UnauthenticatedException || (existing
instanceof ServletException && existing.getCause() instanceof
UnauthenticatedException))
        {
            sendChallenge(request, response);
            existing = null;
        }
        super.cleanup(request, response, existing);
    }
}

-Jared

On 08/10/2011 03:21 PM, ryannelsonaz wrote:
> I need a little help understanding how to intercept Exceptions thrown when a
> @Requires* annotation fails on a given business method.  Jared made the
> suggestion of adding a filter that catches the Exception and does the
> redirect.  I implemented it like so (using Guice).
>
> In subclassed ServletModule:
>
> @Override
> protected void configureServlets() {
> 	filter( "/*" ).through( AuthorizationFailureFilter.class );
> 	filter( "/*" ).through( GuiceShiroFilter.class );
> 	...
> }
>
> In new auth filter (AuthorizationFailureFilter):
>
> @Override
> public void doFilter( ServletRequest request, ServletResponse response,
> FilterChain chain ) throws IOException, ServletException {
> 	try {
> 		chain.doFilter( request, response );
> 	} catch ( ServletException e ) {
> 		if ( e.getCause() instanceof UnauthenticatedException ) {
> 			// what to do here?  redirect to login.jsp?
> 		} else {
> 			throw e;
> 		}
> 	}
> }
>
> The problem I'm running into here is I get none of the automatic redirection
> provided by the FormAuthenticationFilter.  I'd like my program to function
> exactly as if I'd secured the URL with a path-based filter instead of an
> annotation.  I.e., redirect to the login page, and after authentication,
> redirect back to the SavedRequest.
>
> Any suggestions on this?
>
> --
> View this message in context: http://shiro-user.582556.n2.nabble.com/Handling-exceptions-thrown-by-Requires-annotations-tp6673997p6673997.html
> Sent from the Shiro User mailing list archive at Nabble.com.