I started a thread a while ago about a WAS v6 NTLM TAI. I have had
enough people contact me regarding the solution to the
re-authentications on POST that I thought I would post a reply. The
code for the module is at the end of the post.
The workaround is to basically add a 4th step to the ntlm process to
trick IE into thinking that NTLM hasn't succeeded. The attached code
does that. The primary problem is that if IE thinks that NTLM has been
established, it will try to re-authenticate for every POST request, by
initially submitting an empty POST. Since the TAI module is only called
when no session exists, this means that the TAI has no way of
re-authenticating. Instead of sending back a 200 OK as the third step
in the NTLM, I send back a 401 unauthorized, with an HTTP redirect in
the body. When the HTTP redirect takes place, the TAI module intercepts
it and finally authenticates the user.
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.
Scott Hasse
Comprehensive Computer Consulting
/*
* 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.io.Writer;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpUtils;
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.CustomRegistryException;
import com.ibm.websphere.security.EntryNotFoundException;
import com.ibm.websphere.security.UserRegistry;
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;
import com.ibm.wsspi.security.token.AttributeNameConstants;
import com.ibm.wsspi.security.token.WSSecurityPropagationHelper;
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;
String method = "";
NtlmPasswordAuthentication ntlm = null;
msg = req.getHeader( "Authorization" );
boolean offerBasic = enableBasic && (insecureBasic || req.isSecure());
//bad hack to get around IE re-NTLM'ing POSTs
HttpSession session = req.getSession();
if (session != null) {
String username =
(String)session.getAttribute("com.isthmusgroup.websphere.tai.ntlm.username");
if (username != null && !username.equals("")) {
session.removeAttribute("com.isthmusgroup.websphere.tai.ntlm.username");
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: Logging in user using stored
session data: " + username);
}
return TAIResult.create(HttpServletResponse.SC_OK, username);
}
}
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);
method = "NTLM";
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 );
method = "BASIC";
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 );
}
if( LogStream.level > 2 ) {
log.println("JCIFS NTLM TAI: ntlm.username is [" +
ntlm.getUsername() + "]");
}
String userAgent = req.getHeader("User-Agent").toLowerCase();
if (method.equals("NTLM") && userAgent.indexOf("msie")!=-1) {
// This is designed to fool IE into believing that NTLM did not
succeed
// which will prevent it from trying to re-NTLM-authorize
subsequent POSTs
// TODO: this code should not run if BASIC auth was used
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
req.getSession().setAttribute("com.isthmusgroup.websphere.tai.ntlm.username",
ntlm.getUsername());
//resp.setHeader( "WWW-Authenticate", "Negotiate" );
//resp.setContentLength(0);
String requestURL = HttpUtils.getRequestURL(req).toString();
String queryString = req.getQueryString();
if (queryString != null) {
requestURL += "?"+queryString;
}
Writer out = resp.getWriter();
out.write("<html><head>");
out.write("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0; URL=" +
requestURL + "\">");
out.write("</head><body>");
out.write("<!--If you are not redirected, please click ");
out.write("<a href=\"" + requestURL + "\">here</a>-->");
out.write("</body></html>");
resp.flushBuffer();
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM TAI: Returning unauthorized, and
storing user information for: " + ntlm.getUsername());
}
return TAIResult.create(HttpServletResponse.SC_UNAUTHORIZED);
}
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 ) {
e.printStackTrace();
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
} catch (UnknownHostException e) {
if( LogStream.level > 2 ) {
e.printStackTrace();
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
} catch (UnsupportedEncodingException e) {
if( LogStream.level > 2 ) {
e.printStackTrace();
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
} catch (IOException e) {
if( LogStream.level > 2 ) {
e.printStackTrace();
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
throw new WebTrustAssociationFailedException("JCIFS NTLM
Authentication failed:" + e.getMessage());
} catch (Exception e) {
if( LogStream.level > 2 ) {
log.println( "JCIFS NTLM Authentication failed:" + e.getMessage());
}
//unexpected exception
e.printStackTrace();
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.");
}
private Subject createSubject(String userid, String uniqueid, List groups,
String key) {
Subject subject = new Subject();
Hashtable hashtable = new Hashtable();
hashtable.put(AttributeNameConstants.WSCREDENTIAL_UNIQUEID, uniqueid);
hashtable.put(AttributeNameConstants.WSCREDENTIAL_SECURITYNAME, userid);
hashtable.put(AttributeNameConstants.WSCREDENTIAL_GROUPS, groups);
System.out.println("Subject cache key is " + key);
// Adds a cache key that is used as part of the look up mechanism for
// the created Subject. The cache key can be an object, but should have
// an implemented toString() method. Make sure the cacheKey contains
enough
// information to scope it to the user and any additional attributes
you are
// using. If you do not specify this property, the Subject is scoped
to the
// WSCREDENTIAL_UNIQUEID returned, by default.
hashtable.put(AttributeNameConstants.WSCREDENTIAL_CACHE_KEY, key);
subject.getPublicCredentials().add(hashtable);
return subject;
}
}