PDA

View Full Version : Is J2eePreAuthenticatedProcessingFilter incompatible?



aharris
May 18th, 2009, 09:15 AM
I'm working on a Flex / Spring application that uses Flex's HttpService to post credentials to Spring Security's form authentication. I also have the J2eePreAuthenticationProcessingFilter in place and working. None of this has been using Spring-BlazeDS Integration.

Now that Spring-BlazeDS is coming along so nicely, I'm trying to integrate it into my project, and have switched from the Flex HttpService to the ChannelSet login on the form. However, there seems to be an incompatibility. Here is the use case:

1. Hit the app for the first time
2. The user is automatically logged in via pre-authentication
3. Sign out (the HttpSession and FlexSession are invalidated)
4. Attempt to login as a different user

This results in "Cannot re-authenticate in the same session." The gist of the problem is that the call to ChannelSet login is pre-authenticating again before reaching the SpringSecurityLoginCommand. Is there a way around this problem, or is pre-authentication + form-login incompatible with BlazeDS ChannelSet login?

Thanks for any advice!

Adam

jeremyg484
May 18th, 2009, 10:37 AM
Some quick questions to figure out if it's something we already fixed:

1. Are you using RC1? If not, that's the first thing to do.
2. Have you tried configuring Spring Security with its session fixation protection turned off? (There is a bug in RC1 that causes it not to work in all possible configurations that we have since fixed.) If not, try something along these lines in your security config:


<http session-fixation-protection="none" >
...
</http>

aharris
May 18th, 2009, 11:02 AM
Hi Jeremy,

Yes, I am using RC1, and yes I do have session-fixation-protection="none" (in fact the Spring context initialization fails if session fixation is not turned off - org.springframework.beans.factory.NoSuchBeanDefini tionException: No bean named '_filterChainList' is defined).

Adam

jeremyg484
May 18th, 2009, 12:33 PM
Ok, then I want to understand your use case a little better. I'm unclear as to why you would want to use both the J2eePreAuthenticatedProcessingFilter and ChannelSet.login() in combination. Is it because you have non-flex portions of the application as well? Maybe I'm missing something. Can you provide more detail about the end result you would like to have?

aharris
May 18th, 2009, 02:33 PM
The requirement I have is to use pre-authentication to auto-login users. In addition, the user must be able to login as a different user (i.e. the auto-logged-in user should be able to sign off, be presented a login form, and submit credentials for a different user). This sort of requirement is easily fulfilled with plain Spring Security in a traditional web app. In fact, I have it currently working in a Flex RIA using Flex+HttpService and Spring Security although it's a bit kludgy (it doesn't use BlazeDS security at all).

I am now trying to follow the "Flex & Spring Integration" refcard as a pattern. So, my Flex login form is now attempting to use ChannelSet.login for processing the credentials instead of submitting them through Flex's HttpService. Maybe I'm attempting something unorthodox here...I thought that ChannelSet.login was the Blaze-sanctioned way of authentication instead of an HTTP POST to /j_spring_security_check.

I don't have any non-Flex portions of the application.

jeremyg484
May 18th, 2009, 04:24 PM
ChannelSet.login() is indeed the "Blaze-sanctioned" way to authenticate. I wouldn't say that what you're trying to do is unorthodox...it actually sounds fairly typical. But what I'm still trying to understand about your particular scenario is how are you obtaining the user's credentials for the auto-login? And are trying to use ChannelSet.login() to authenticate at that time?

aharris
May 18th, 2009, 08:53 PM
how are you obtaining the user's credentials for the auto-login?

Technically speaking, I'm not obtaining the credentials for auto-login.

I have IIS configured with the ISAPI redirector sitting in front of Apache Tomcat. When IE is the browser, the Spring Security J2eePreAuthenticatedProcessingFilter will grab the AUTHORIZATION header put there by IIS, and populate the SpringSecurityContext with the user's authentication token. Here is some of my spring xml.



<security:http auto-config="false" session-fixation-protection="none" >
<security:intercept-url pattern="/messagebroker/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:form-login />
<security:logout logout-url="/j_spring_security_logout"/>
</security:http>

<!-- Configuration for pre-authentication (e.g. User logged into Windows > IE > IIS > Tomcat) -->
<bean id="jeePreAuthenticatedFilter"
class="org.springframework.security.ui.preauth.j2ee.J2eeP reAuthenticatedProcessingFilter">
<security:custom-filter position="PRE_AUTH_FILTER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preAuthAuthenticationProvider"
class="org.springframework.security.providers.preauth.Pre AuthenticatedAuthenticationProvider">
<security:custom-authentication-provider/>
<property name="preAuthenticatedUserDetailsService" ref="userDetailsService"/>
</bean>




And are trying to use ChannelSet.login() to authenticate at that time?

For pre-auth, I am not calling ChannelSet.login(). It is when I want to logout of my pre-authenticated session and login manually as another user that things fall apart. Because the ChannelSet.login() is passing through the Spring Security filters, the pre-auth filter populates the UserPrincipal into the HttpServletRequest again before SpringSecurityLoginCommand.doAuthentication() has a chance to run. In fact, the code in BlazeDS LoginManager.login() circumvents the LoginCommand altogether throwing the "cannot re-auth" error. Here's some of LoginManager code:



/**
* Perform login with username and credentials.
*
* @param username Username to use to login.
* @param credentials Credentials to use to login.
*/
public void login(String username, Object credentials)
{
if (getCurrentPrincipal() == null)
{
if (loginCommand != null)
{
if (username != null && credentials != null)
{
Principal authenticated = loginCommand.doAuthentication(username, credentials);

if (authenticated == null)
{
// Invalid login.
SecurityException se = new SecurityException();
se.setMessage(INVALID_LOGIN);
se.setCode(SecurityException.CLIENT_AUTHENTICATION _CODE);
throw se;
}
setCurrentPrincipal(authenticated);
}
else
{
// Login is required but the client passed null principal and credentials.
SecurityException se = new SecurityException();
se.setMessage(LOGIN_REQ);
se.setCode(SecurityException.CLIENT_AUTHENTICATION _CODE);
throw se;
}
}
else
{
// Client needs to be externally authenticated via Basic Authentication or some other method.
SecurityException se = new SecurityException();
se.setMessage(NO_LOGIN_COMMAND);
se.setCode(SecurityException.SERVER_AUTHENTICATION _CODE);
throw se;
}
}
else
{
// It is possible that the username passed in from the client and that stored in the
// Principal on the session may be different. To facilitate this case a LoginCommand
// must implement LoginCommandExt and the user stored in the Principal is retrieved
// here for comparison
String comparisonUsername;
if(loginCommand instanceof LoginCommandExt)
{
comparisonUsername = ((LoginCommandExt)loginCommand).getPrincipalNameFr omCredentials(username, credentials);
}
else
{
comparisonUsername = username;
}

// If we have a username and a different existing principal then we
// must raise an exception as we don't allow re-authentication for
// a given session...
if (comparisonUsername != null && !comparisonUsername.equals(getCurrentPrincipal().g etName()))
{
// Cannot re-authenticate in the same session.
SecurityException se = new SecurityException();
se.setMessage(CANNOT_REAUTH);
se.setCode(SecurityException.CLIENT_AUTHENTICATION _CODE);
throw se;
}
}
}



FYI - I have a destination endpoint Flex calls when the client app is starting up: User getAuthenticatedUser().
If this call fails to return a User (e.g. the Windows user doesn't have an account in my application), the client app challenges the user with the login form; otherwise the main page of the application is presented.

I hope I'm getting closer to answering your questions. :)

jeremyg484
May 19th, 2009, 11:18 AM
Right, now we're definitely getting somewhere. :)

So the problem isn't entirely that the J2eePreAuthenticatedProcessingFilter is setting the Authentication earlier in the ChannelSet.login() request. The BlazeDS LoginManager fails because it ultimately gets the current user Principal from HttpServletRequest.getUserPrincipal() when it does its comparison against the new username/password.

In reality, the call to HttpServletRequest.getUserPrincipal() at that point in the request is getting handled by the SecurityContextHolderAwareRequestWrapper instead of actually calling through to the real underlying HttpServletRequest. Because of that, there is a way to make things work by providing a custom MessageInterceptor that resets the Authentication on the SecurityContext when a login command message is being processed.

Such an implementation might look like this:


package flex.spring.samples;

import org.springframework.flex.core.MessageInterceptor;
import org.springframework.flex.core.MessageProcessingCon text;
import org.springframework.security.context.SecurityConte xtHolder;

import flex.messaging.messages.CommandMessage;
import flex.messaging.messages.Message;

public class SecurityContextCleanupInterceptor implements MessageInterceptor {

public Message postProcess(MessageProcessingContext context,
Message inputMessage, Message outputMessage) {
return outputMessage;
}

public Message preProcess(MessageProcessingContext context,
Message inputMessage) {
if (inputMessage instanceof CommandMessage) {
CommandMessage command = (CommandMessage) inputMessage;
if (command.getOperation() == CommandMessage.LOGIN_OPERATION) {
SecurityContextHolder.getContext().setAuthenticati on(null);
}
}
return inputMessage;
}
}


You can then integrate this into the AMF message processing chain using the namespace config...this would look something like the following:



<flex:message-broker>
<flex:message-interceptor ref="securityCleanupInterceptor"/>
<flex:secured />
</flex:message-broker>

<bean id="securityCleanupInterceptor"
class="flex.spring.samples.SecurityContextCleanupIntercep tor"/>

aharris
May 19th, 2009, 11:36 AM
Fantastic! That works. I really appreciate your help.

Adam

pcw216
Mar 2nd, 2011, 01:20 PM
I'm having a hell of a time getting this to work. I'm actually allowing Flex to override the pre-authenticated user and manually login using a different user for my Flexunit tests. This is all for integration testing to test my method-level security layer on my remote object. We have Siteminder enabled as our pre-authentication provider, but in testing scenarios, siteminder is unavailable. Furthermore we want administrators to be able to override the current user.

I've installed the method interceptor as shown, and I've verified that it runs for every flex call to ChannelSet.login(). It properly clears the security context. I also installed a CustomLoginCommand so that I could debug and log those commands.

The problem I'm seeing is that there seems to be no order between the message interceptor and then the login command. Occasionally I've seen this work correctly, where the context is cleared and then the security system processes the login command. More often, the system ignores the login command because it is already pre-authenticated (the interceptor has not cleared the context yet), and then the clear is performed. This invalidates all of my subsequent calls to my secured remote service.

flex-servlet.xml


<flex:message-broker>
<flex:config-processor ref="customLoginCommand" />
<flex:message-interceptor ref="securityCleanupInterceptor" />
<flex:secured />
</flex:message-broker>


and a simple flexunit test.



channelSet.login("user","password");
remoteObject.getRequest(-1);


I receive a "org.springframework.security.AuthenticationCredent ialsNotFoundException" because the login command was ignored. I hope you guys can help me with this because I don't see any other examples out there.