Thursday 29 January 2009

NTLM Authentication and the IE Post Problem

We are using NTLM Windows Authentication for a Single Sign On (SSO) project.

We are using the Spring security Filter NtlmProcessingFilter which for most of the time is absolutely fine.

However the are atleast two scenarios where this fails.

1) When the session is timed out and a form.submit() request is made.
Under this situation a windows logon box is presented. This is obviously not desirable in a SSO project.

2) If the page makes heavy use of dwr/javascript.
In this case the page makes repeated NTLM authentication requests and stack traces are observed with the message 'This is not a Type 3 Message'.

There is a solution described in the jcifs documentation. Search for registry key. This solution works but is not suitable for us as our client would not let us change the registry on all the client PCs. Quite understandably I think.

The fix described here applies to Spring-Security 2.0.4 and jcifs 1.2.25 but is also required for jcifs to atleast 1.3.3

Both Spring-Security and jcifs have an NTLMFilter and it is to this that the fix is required.

Here is the Spring solution to org.springframework.security.ui.ntlm.NtlmProcessingFilter:


protected void doFilterHttp(final HttpServletRequest request,
final HttpServletResponse response, final FilterChain chain)
throws IOException, ServletException {
final HttpSession session = request.getSession();
Integer ntlmState = (Integer) session.getAttribute(STATE_ATTR);

final String authMessage = request.getHeader("Authorization");

// Check the special IE POST request with Authorization header containing
// type-1 message (see method javadoc)
if (this.reAuthOnIEPost(request)) {
if ((authMessage != null) && (authMessage.startsWith("NTLM "))) {
logger.debug("POST Request with NTLM Authorization detected.");
// decode the NTLM response from the client
byte[] src = Base64.decode(authMessage.substring(5));
// see if a type 1 message was sent by the client
if (src[8] == 1) {
logger
.debug("NTLM Authorization header contains type-1 message. Sending fake response just to pass this stage...");
Type1Message type1 = new Type1Message(src);
// respond with a type 2 message, where the challenge is null since we
// don't
// care about the server response (type-3 message) since we're already
// authenticated
// (This is just a by-pass - see method javadoc)
Type2Message type2 = new Type2Message(type1, new byte[8], null);
String msg = Base64.encode(type2.toByteArray());
response.setHeader("WWW-Authenticate", "NTLM " + msg);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
}
}
} else {
..... existing filter code
}
chain.doFilter(request, response);
}

The fix for jcifs appears to be very similar and thanks to Asaf Mesika off the jcifs forum for his help. NB: I have not tried this jcifs solution. For jcifs the fix is to jcifs.http.NtlmHttpFilter:


protected NtlmPasswordAuthentication negotiate( HttpServletRequest req,
HttpServletResponse resp,
boolean skipAuthentication ) throws IOException, ServletException {
UniAddress dc;
String msg;
NtlmPasswordAuthentication ntlm = null;
msg = req.getHeader( "Authorization" );
boolean offerBasic = enableBasic && (insecureBasic || req.isSecure());

// Check the special POST request with Authorization header containing type-1 message (see method javadoc)
if (request.getMethod().equalsIgnoreCase("POST")) {
String authorization = request.getHeader( "Authorization" );
if ( (authorization != null) && (authorization.startsWith("NTLM ")) ) {
logger.debug("POST Request with NTLM Authorization detected.");
// decode the NTLM response from the client
byte[] src = Base64.decode(authorization.substring(5));
// see if a type 1 message was sent by the client
if (src[8] == 1) {
logger.debug("NTLM Authorization header contains type-1 message. Sending fake response just to pass this stage...");
Type1Message type1 = new Type1Message(src);
// respond with a type 2 message, where the challenge is null since we don't
// care about the server response (type-3 message) since we're already authenticated
// (This is just a by-pass - see method javadoc)
Type2Message type2 = new Type2Message(type1, new byte[8], null);
String msg = Base64.encode(type2.toByteArray());
response.setHeader("WWW-Authenticate", "NTLM " + msg);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
return false;
}
}
}

... existing filter code ....
}

I have raised a spring-security bug if you want to see if it is fixed in the version you have.

2 comments:

Unknown said...

have you try form-fallback with ie, in my test, the form-fallback work in firefox, but not ie, in IE, i cannot getPrincipal(), it's empty

Bill Comer said...

afraid not Cometta, we have given up on NTLM as it is no longer supporteed and moved onto kerberos.