Fallback to spring security's BasicAuthenticationFilter

Developer
Aug 23, 2010 at 7:35 PM

I'm using spring security.  I'd like to use waffle's negotiate sso filter.  And I'd also like to allow basic authentication.

You have described how to do this in your documentation.

This works great.

 

Now, bear with me here....  

 

Why do I need to use waffle's implementation of basic authentication (BasicSecurityFilterProvider), rather than just fall back to spring security's BasicAuthenticationFilter (as the next filter in the chain)?  Is there some technical reason to do so?

 

It might help to understand where I'm coming from to understand this question.  My team develops a pre-packaged server application that can be deployed at customer sites.  Our customers don't really know that java/app server/springsecurity/waffle is running under the hood.  This application can be deployed on unix or windows.  I would like to configure spring's BasicAuthenticationFilter on both unix and windows.  And then configure a negotiate filter above the BasicAuthenticationFilter in the chain only on windows.  I do not want to configure waffle's BasicSecurityFilterProvider if I don't have to (i.e. I don't want to duplicate configuration info between spring's BasicAuthenticationFilter and waffle's BasicSecurityFilterProvider).    Does that make sense?

 

So, back to the original question.  Is there a technical reason to require the usage of BasicSecurityFilterProvider?  If not, I'll probably write:

  • my own servlet filter similar to waffle.spring.NegotiateSecurityFilter that doesn't barf if a basic authentication request comes in and a BasicSecurityFilterProvider is not configured.  This would allow fallback to spring security's BasicAuthenticationFilter.
  • my own entrypoint that is capable of returning negotiate/ntlm/basic on windows and only basic on unix.

 

Coordinator
Aug 23, 2010 at 9:03 PM

You need the Waffle implementation of BASIC to enable actual Windows logon and get a consistent set of roles whether you authenticate via NTLM, Kerberos or Basic. I may be wrong, but all the authentication providers that I have seen don't do anything about user Active Directory groups. You can't know, for example, that domain\user is a member of domain\group, much less nested groups.

The next smaller problem is that something needs to return a 401 Access Denied and challenge correctly the client in the right order (Negotiate, NTLM, Basic) and properly set connection=close/keep-alive. I couldn't achieve that without terminating the connection in the filter and hence adding Basic auth externally was not an option. I might have been doing something wrong though.

All that aside, do consider submitting patches to Waffle instead of reimplementing a very similar filter. I am totally open to new options that make the behavior (eg. passthrough) configurable.

PS: some guys at LikeWise were promising to extend waffle to use their unix-based SSP to do NTLM on *nix ;)

 

Developer
Aug 23, 2010 at 9:27 PM
dblock wrote:

You need the Waffle implementation of BASIC to enable actual Windows logon and get a consistent set of roles whether you authenticate via NTLM, Kerberos or Basic. I may be wrong, but all the authentication providers that I have seen don't do anything about user Active Directory groups. You can't know, for example, that domain\user is a member of domain\group, much less nested groups.

Right, I forgot to mention that I'll also configure waffle's WindowsAuthenticationProvider in spring's AuthenticationManager.  So, if Negotiate/NTLM are used, the NegotiateFilter will short-ciruit to perform the auth, but if basic auth is used, it will fall through spring's BasicAuthenticationFilter... and then down to AuthenticationManager (which will then use the WindowsAuthenticationProvider)

dblock wrote:

The next smaller problem is that something needs to return a 401 Access Denied and challenge correctly the client in the right order (Negotiate, NTLM, Basic) and properly set connection=close/keep-alive. I couldn't achieve that without terminating the connection in the filter and hence adding Basic auth externally was not an option. I might have been doing something wrong though.

Right, that's where spring's AuthenticationEntryPoint comes into play.  I would have to write my own that would handle what I want.  Waffle's NegotiateSecurityFilterEntryPoint delegates to the configured SecurityFilterProviders to provide the available security mechanisms in the 401 response.  I couldn't rely on that, since the BasicSecurityFilterProvider would not be configured.  So mine would have to have knowledge of the protocols handled by waffle's SecurityFilterProviders AND by basic auth.  Right now, in my prototype, I've essentially hardcoded Negotate/NTML/Basic on windows and only Basic on unix.  Off the top of my head, I can't think of an elegant way to not hardcode it.

dblock wrote:

All that aside, do consider submitting patches to Waffle instead of reimplementing a very similar filter. I am totally open to new options that make the behavior (eg. passthrough) configurable.

I'd be happy to submit patches.  The really tough part is what you have mentioned regarding the 401 response with the correct headers.  I'm hoping to get a prototype working with hardcoded values, then I'll try to find an elegant way of determining the correct response.   If I can do that, I'll submit a patch.  Otherwise, I doubt you'd want waffle to have to rely on the user hardcoding values.  We could discuss this after I get it working though.

dblock wrote:

PS: some guys at LikeWise were promising to extend waffle to use their unix-based SSP to do NTLM on *nix ;)

Oh, that would be sweet. :)

Thanks for the prompt response!

Coordinator
Aug 23, 2010 at 11:17 PM

Still, does the Spring BasicAuthenticationFilter generate user roles for groups? Don't you want that?

Getting things working first is always good. Generic patches that make Waffle better are even ... better :) I have rather little Spring-security experience, so I don't claim to have done everything well.

 

Developer
Aug 24, 2010 at 3:02 PM

Spring Security's <tt>BasicAuthenticationFilter</tt> delegates to the <tt>AuthenticationManager</tt> to perform the authentication. And since I'm using a <tt>ProviderManager</tt> for my <tt>AuthenticationManager</tt>, and I've configured waffle's <tt>WindowsAuthenticationProvider</tt>, I get the roles/groups as discovered by waffle.

 

i.e. Both the <tt>NegotiateSecurityFilter/BasicSecurityFilterProvider</tt> combo and the <tt>BasicAuthenticationFilter/AuthenticationManager/WindowsAuthenticationProvider</tt> combo end up calling <tt>IWindowsAuthProvider.logonUser</tt> and constructing a <tt>WindowsAuthenticationToken</tt> with fully populated roles from the <tt>IWindowsIdentity</tt>.

 

For someone with little spring-security experience, you've done a great job with Waffle.  It's very well designed and easy to configure.  These few things I'm running into are very minor when compared to the benefit it provides.

Coordinator
Aug 24, 2010 at 3:04 PM

This makes total sense. Thanks for your kind words too.

Developer
Aug 24, 2010 at 7:11 PM

Ok, I uploaded patch# 6551 for half of the solution.

That patch will allow the waffle.spring.NegotiateSecurityFilter to fall-through to the rest of the filter chain if it doesn't support the requested security package (e.g. Basic). The patch changes the default behavior.  You might want to introduce a  configurable option to enable/disable fallthrough in this case.  However, I feel that the fall-through behavior should be the default, so waffle's filter plays nicely with other filters.

With this patch, I will be able to use waffle's NegotiateSecurityFilter (which is half of what I need) instead of implementing my own.

The other half pertains to NegotiateSecurityFilterEntryPoint.  I cannot use this as-is, because it delegates to the SecurityFilterProviderCollection (which, in my case, will not have the BasicSecurityFilterProvider configured).  As mentioned before, I don't see a nice way of implementing a new AuthenticationEntryPoint in waffle to provide the behavior I need.   I think it would be ok if you leave that as an exercise for the waffle user (e.g. me).

Coordinator
Aug 25, 2010 at 1:41 PM

Thank you for the patch. Commited @ rev. 56750 . I also wrote a unit test for this scenario (next time you'll have to do that before you send a patch, no more excuses ;)).

Coordinator
Aug 25, 2010 at 2:16 PM
philsttr wrote:

The other half pertains to NegotiateSecurityFilterEntryPoint.  I cannot use this as-is, because it delegates to the SecurityFilterProviderCollection (which, in my case, will not have the BasicSecurityFilterProvider configured).  As mentioned before, I don't see a nice way of implementing a new AuthenticationEntryPoint in waffle to provide the behavior I need.   I think it would be ok if you leave that as an exercise for the waffle user (e.g. me).

 How does Spring-security tell us do it "out-of-the-box"? I mean you have to implement an AuthenticationEntryPoint that sends challenges in "commence". But if you don't know your providers upfront, you can't know what to send. I wouldn't be suprised if the answer is always "roll out your own", but maybe there's a better way?

Developer
Aug 25, 2010 at 3:52 PM
dblock wrote:

I also wrote a unit test for this scenario (next time you'll have to do that before you send a patch, no more excuses ;)).

You got me.  next time....

dblock wrote:
 How does Spring-security tell us do it "out-of-the-box"? I mean you have to implement an AuthenticationEntryPoint that sends challenges in "commence". But if you don't know your providers upfront, you can't know what to send. I wouldn't be suprised if the answer is always "roll out your own", but maybe there's a better way?

The spring-security docs are pretty vague on the topic, but hint at writing your own if you use a custom authentication mechanism.

What I've done is write a really generic AuthenticationEntryPoint (GenericUnauthorizedEntryPoint) that has a list of authentication header values and a keepAlive boolean.  The entrypoint returns a 401 with all the auth headers and keep alive header as configured.  I just configure this one differently based on unix/windows. I almost think this really belongs in spring-security proper, but I could submit it to waffle for the time being.

Coordinator
Aug 26, 2010 at 12:17 PM

What I've done is write a really generic AuthenticationEntryPoint (GenericUnauthorizedEntryPoint) that has a list of authentication header values and a keepAlive boolean.  The entrypoint returns a 401 with all the auth headers and keep alive header as configured.  I just configure this one differently based on unix/windows. I almost think this really belongs in spring-security proper, but I could submit it to waffle for the time being.

 I think it's too generic for Waffle and is, indeed, more suitable for sprint-security proper. But you should do a blog post / article somewhere and link it from here, with source code.

Coordinator
Sep 9, 2010 at 8:27 PM

Mr phillstr - you now have write access to waffle source, this is for your other patch. See comments in http://waffle.codeplex.com/workitem/9353 before you commit. Your CodePlex contact preferences are off, so maybe you'll get this e-mail :) Thanks for contributing!