Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

WAS 6 NTLM TAI SSO module

69 views
Skip to first unread message

Scott Hasse

unread,
Nov 2, 2005, 11:21:30 AM11/2/05
to
All,

I recently ran into a situation where I wanted seamless SSO integration
between a windows Active Directory and a WebSphere Application Server 6
infrastructure. Although WAS can easily be configured to us AD as an
LDAP security provider, this still requires you to manually login.
Based on the excellent article on the WAS TAI mechanism found here:

http://www-128.ibm.com/developerworks/websphere/techjournal/0508_benantar/0508_benantar.html

and the great work done by the jcifs project, specifically the NTLM
servlet filter documented here:

http://jcifs.samba.org/src/docs/ntlmhttpauth.html

I have created a beta TAI module that makes use of NTLM credential
negotiation to seamlessly log users into WebSphere Application Server.

This module takes the same configuration options as the
jcifs.http.NtlmHttpFilter described in the article linked above, except
the settings are made as custom properties as described in the Benantar
IBM article linked above.

To use this code:
* configure your WebSphere infrastructure to use your AD as the global
security provider
* download the latest jcifs jar (currently jcifs-1.2.6.jar) from
http://jcifs.samba.org/src/
* compile the code pasted below (you will need to include wssec.jar from
your websphere installation and put it into a jar file
* copy both the jcifs jar file and your jar file to your
<WAS_HOME>/lib/ext directory
* Follow the instructions in the "TAI installation and configuration"
section of the IBM Benantar document linked above. In step 5,
substitute the TAI class name where appropriate.
* In step 6 of the "TAI installation and configuration" section of the
IBM Benantar document, add your custom properties as described in the
http://jcifs.samba.org/src/docs/ntlmhttpauth.html document. For
instance, you could specify (per the jcifs example):

jcifs.smb.client.domain NYC-USERS
jcifs.netbios.wins 10.169.10.77,10.169.10.66

After you restart your application server, you should then be able to
authenticate using NTLM. The web administrative console is a good
application for testing this functionality. By default, the jcifs code
logs to the System.err stream, so if you configure the
jcifs.util.loglevel property, output will go there.

If your browser does not trust the application server, a BASIC-like
login window will pop up. This is actually (unless you have configured
the module to allow BASIC auth) an NTLM authentication prompt. To get
rid of this, configure your browser to trust the server (both http and
https).

Some disclaimers: This is beta code, and has not been fully vetted
through a security process. It was basically pulled directly from the
Ntlm servlet filter from the jcifs folks, and IMO while it is basically
functional, it is somewhat hard to follow. There are still many bugs
likely to be discovered. It obviously comes with no warranty, so use at
your own risk. If you do end up using it or having problems, please let
me know.

One strange side effect of this module is that usually NTLM
authentication requires re-authentication on subsequent HTTP POST
operations. Based on my simplistic testing, this does not seem to be
happening with this module. One theory I have is that, due to
connection keep-alive or HTTP return codes, the browser does not believe
that NTLM authentication has been completed.

If people find that this module is useful, there might be some
possibilities to implement a SPNEGO version of this TAI based on the
servlet filter found in the jcifs-ext project
(http://sourceforge.net/projects/jcifs-ext).

Scott Hasse
Isthmus Group, Inc.

/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.isthmusgroup.websphere.tai.ntlm;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jcifs.Config;
import jcifs.UniAddress;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.smb.NtStatus;
import jcifs.smb.NtlmChallenge;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbAuthException;
import jcifs.smb.SmbException;
import jcifs.smb.SmbSession;
import jcifs.util.Base64;
import jcifs.util.LogStream;

import com.ibm.websphere.security.WebTrustAssociationException;
import com.ibm.websphere.security.WebTrustAssociationFailedException;
import com.ibm.wsspi.security.tai.TAIResult;
import com.ibm.wsspi.security.tai.TrustAssociationInterceptor;

public class JcifsNtlmTAI implements TrustAssociationInterceptor {

private boolean initialized = false;
private static LogStream log = LogStream.getInstance();
private String defaultDomain;
private String domainController;
private boolean loadBalance;
private boolean enableBasic;
private boolean insecureBasic;
private String realm;

public JcifsNtlmTAI() {
super();
}

/*
*
* Check here that this request is the correct one for this TAI (i.e you
* might have multiple TAI's). This module can be disabled by passing an
* auth=nontlm request variable via a browser. WAS will then fall back to
* normal behavior. Assuming the application is configured properly with a
* login page, that login page will then be displayed.
*
* @see
com.ibm.wsspi.security.tai.TrustAssociationInterceptor#isTargetInterceptor(javax.servlet.http.HttpServletRequest)
*/
public boolean isTargetInterceptor(HttpServletRequest req)
throws WebTrustAssociationException {

String auth = req.getParameter("auth");
if (!initialized || (auth != null && auth.equals("nontlm"))) {
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI will not handle this auth request.");
}
return false;
} else {
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI will handle this auth request.");
}
return true;
}
}

/*
*
* @see
com.ibm.wsspi.security.tai.TrustAssociationInterceptor#negotiateValidateandEstablishTrust(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
public TAIResult negotiateValidateandEstablishTrust(
HttpServletRequest req,
HttpServletResponse resp)
throws WebTrustAssociationFailedException {

try {
UniAddress dc;
String msg;
NtlmPasswordAuthentication ntlm = null;
msg = req.getHeader( "Authorization" );
boolean offerBasic = enableBasic && (insecureBasic || req.isSecure());

if( msg != null && (msg.startsWith( "NTLM " ) ||
(offerBasic && msg.startsWith("Basic ")))) {
if (msg.startsWith("NTLM ")) {
HttpSession ssn = req.getSession();
byte[] challenge;

if( loadBalance ) {
NtlmChallenge chal = (NtlmChallenge)ssn.getAttribute(
"NtlmHttpChal" );
if( chal == null ) {
chal = SmbSession.getChallengeForDomain();
ssn.setAttribute( "NtlmHttpChal", chal );
}
dc = chal.dc;
challenge = chal.challenge;
} else {
dc = UniAddress.getByName( domainController, true );
challenge = SmbSession.getChallenge( dc );
}
//authenticate using NTLM challenge/response
byte[] src = Base64.decode(msg.substring(5));
if (src[8] == 1) {
Type1Message type1 = new Type1Message(src);
Type2Message type2 = new Type2Message(type1, challenge, null);
msg = Base64.encode(type2.toByteArray());
resp.setHeader( "WWW-Authenticate", "NTLM " + msg );
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.setContentLength(0); /* Addresses a connection: close bug
in the WAS IIS plugin */
resp.flushBuffer();
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: NTLM Step 2: WWW-Authenticate
NTLM " + msg);
}
return TAIResult.create(HttpServletResponse.SC_UNAUTHORIZED);
} else if (src[8] == 3) {
Type3Message type3 = new Type3Message(src);
byte[] lmResponse = type3.getLMResponse();
if (lmResponse == null) lmResponse = new byte[0];
byte[] ntResponse = type3.getNTResponse();
if (ntResponse == null) ntResponse = new byte[0];
ntlm = new NtlmPasswordAuthentication(type3.getDomain(),
type3.getUser(), challenge, lmResponse, ntResponse);
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: NTLM Step 3: Authenticating using
NTLM credentials " + ntlm);
}

}
/* negotiation complete, remove the challenge object */
ssn.removeAttribute( "NtlmHttpChal" );
} else {
//authenticate using BASIC auth
String auth = new String(Base64.decode(msg.substring(6)),
"US-ASCII");
int index = auth.indexOf(':');
String user = (index != -1) ? auth.substring(0, index) : auth;
String password = (index != -1) ? auth.substring(index + 1) :
"";
index = user.indexOf('\\');
if (index == -1) index = user.indexOf('/');
String domain = (index != -1) ? user.substring(0, index) :
defaultDomain;
user = (index != -1) ? user.substring(index + 1) : user;
ntlm = new NtlmPasswordAuthentication(domain, user, password);
dc = UniAddress.getByName( domainController, true );
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: Authenticating using BASIC
credentials " + ntlm);
}
}
try {
// Test the credentials from either NTLM or BASIC
if( ntlm == null ) {
throw new WebTrustAssociationFailedException("NTLM Authentication
failed: ntlm is null");
}
SmbSession.logon( dc, ntlm );

if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: " + ntlm +
" successfully authenticated against " + dc );
}
return TAIResult.create(HttpServletResponse.SC_OK,
ntlm.getUsername());
} catch( SmbAuthException sae ) {
if( LogStream.level > 1 ) {
log.println( "NtlmHttpFilter: " + ntlm.getName() +
": 0x" + jcifs.util.Hexdump.toHexString( sae.getNtStatus(), 8 ) +
": " + sae );
}
if( sae.getNtStatus() == NtStatus.NT_STATUS_ACCESS_VIOLATION ) {
/* Server challenge no longer valid for
* externally supplied password hashes.
*/
HttpSession ssn = req.getSession(false);
if (ssn != null) {
ssn.removeAttribute( "NtlmHttpAuth" );
}
}
resp.setHeader( "WWW-Authenticate", "NTLM" );
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: Initial NTLM failed, returning to
NTLM Step 1: WWW-Authenticate NTLM");
}
if (offerBasic) {
resp.addHeader( "WWW-Authenticate", "Basic realm=\"" +
realm + "\"");
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: Initial NTLM failed, returning to
BASIC Step 1: WWW-Authenticate Basic realm=\"" + realm + "\"");
}
}
resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
resp.setContentLength(0); /* Marcel Feb-15-2005 */
resp.flushBuffer();
return TAIResult.create(HttpServletResponse.SC_UNAUTHORIZED);

}
} else {
HttpSession ssn = req.getSession(false);
if (ssn == null || (ntlm = (NtlmPasswordAuthentication)
ssn.getAttribute("NtlmHttpAuth")) == null) {
resp.setHeader( "WWW-Authenticate", "NTLM" );
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: NTLM Step 1: WWW-Authenticate NTLM");
}
if (offerBasic) {
resp.addHeader( "WWW-Authenticate", "Basic realm=\"" +
realm + "\"");
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: BASIC Step 1: WWW-Authenticate
Basic realm=\"" + realm + "\"");
}
}
resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
resp.setContentLength(0); /* Addresses a connection: close bug in
the WAS IIS plugin */
resp.flushBuffer();
return TAIResult.create(HttpServletResponse.SC_UNAUTHORIZED);
}
}
} catch (SmbException e) {
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
} catch (UnknownHostException e) {
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
} catch (UnsupportedEncodingException e) {
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
} catch (IOException e) {
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
}
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM Authentication failed: should not reach
this point.");
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed: should not reach this point.");

}

/*
* @see
com.ibm.wsspi.security.tai.TrustAssociationInterceptor#initialize(java.util.Properties)
*/
public int initialize(Properties props)
throws WebTrustAssociationFailedException {

String name;
int level;

/* Set jcifs properties we know we want; soTimeout and cachePolicy to
10min.
*/
Config.setProperty( "jcifs.smb.client.soTimeout", "300000" );
Config.setProperty( "jcifs.netbios.cachePolicy", "1200" );

Enumeration e = props.propertyNames();
while( e.hasMoreElements() ) {
name = (String)e.nextElement();
if( name.startsWith( "jcifs." )) {
Config.setProperty( name, props.getProperty( name ));
}
}
defaultDomain = Config.getProperty("jcifs.smb.client.domain");
domainController = Config.getProperty( "jcifs.http.domainController" );
if( domainController == null ) {
domainController = defaultDomain;
loadBalance = Config.getBoolean( "jcifs.http.loadBalance", true );
}
enableBasic = Boolean.valueOf(
Config.getProperty("jcifs.http.enableBasic")).booleanValue();
insecureBasic = Boolean.valueOf(
Config.getProperty("jcifs.http.insecureBasic")).booleanValue();
realm = Config.getProperty("jcifs.http.basicRealm");
if (realm == null) realm = "jCIFS";

if(( level = Config.getInt( "jcifs.util.loglevel", -1 )) != -1 ) {
LogStream.setLevel( level );
}
if( LogStream.level > 2 ) {
try {
Config.store( log, "JCIFS PROPERTIES" );
} catch( IOException ioe ) {
throw new WebTrustAssociationFailedException("JCIFS NTLM TAI
logging could not be initialized");
}
}
log.println("JCIFS NTLM TAI module initialized");
initialized = true;
return 0;
}

/*
* @see com.ibm.wsspi.security.tai.TrustAssociationInterceptor#getVersion()
*/
public String getVersion() {
return "1.0";
}

/*
* @see com.ibm.wsspi.security.tai.TrustAssociationInterceptor#getType()
*/
public String getType() {
return this.getClass().getName();
}

/*
* @see com.ibm.wsspi.security.tai.TrustAssociationInterceptor#cleanup()
*/
public void cleanup() {
log.println("JCIFS NTLM TAI module cleaned up.");
}

}

Scott Hasse

unread,
Dec 9, 2005, 3:33:21 PM12/9/05
to
Actually, I did have a problem with POST authentication that I was able
to work out. It turns out that testing to localhost did not uncover
this issue. However, I came up with a good workaround. Feel free to
email me if you'd like the details.

Scott

noamh...@gmail.com

unread,
Jul 3, 2007, 5:10:34 PM7/3/07
to
Hi Scott,
I tried your TAI module. It works great except for the POST problem.
Can you explain your solution?

Thanks,
noam

noamh...@gmail.com

unread,
Jul 4, 2007, 5:53:45 PM7/4/07
to

Paul Ilechko

unread,
Jul 4, 2007, 7:27:56 PM7/4/07
to

He also says:

"This approach may not necessarily be secure (NTLM itself is not that
secure), but it would be a poor man's approach to SSO in WAS. "


I have a hard time seeing the benefit of an insecure way of integrating
with WebSphere security. This is dangerous, because people will likely
use it without understanding the limitations. The SPNEGO TAI that ships
with WAS 6.1 *is* secure.

mike....@rivermine.com

unread,
Jun 27, 2008, 5:55:10 PM6/27/08
to
I have successfully gotten the 3-step NTLM challenge/response working using nothing but the JCIFS NtlmHttpFilter deployed to WebSphere 6.1 if I use Firefox as the browser. Similarly, the same filter & servlet work when deployed on Tomcat 6.x from either Firefox or IE. But when I try IE6 against WebSphere 6.1, IE sends request #1 and request #2 but WebSphere appears to close the connection after request #2 is sent. I can step through it in a debugger and see that request #1 and #2 are both sent and processed without any kind of error, but after request #2 leaves the filter, the next thing you know, IE is saying "This page can not be displayed".

Everything I've been able to "sniff" shows that the app server is closing the connect after the 2nd request.

Has anyone been able to get NTLM working with IE6/WebSphere 6.1 using nothing but the JCIFS HtlmHttpFilter?

Thanks in advance,

Mike Gorman

mike....@rivermine.com

unread,
Jun 28, 2008, 8:46:01 AM6/28/08
to
Just to give an update - I upgraded to the latest fix pack of WAS 6.1 (6.1.0.17) and everything is now working.

So for anyone trying to do NTLM-based SSO using nothing but the JCIFS filter, you're going to want to get the latest fix pack of WAS before you try it.

mg

0 new messages