Skip to main content

Exchange web service authenticate with NTLMv2

1 reply [Last post]
dgodbey
Offline
Joined: 2006-10-27
Points: 0

I have been trying unsuccessfully for several weeks to authenticate to a Microsoft Exchange
server (EWS) protected by NTLMv2 authentication. I am hoping that someone here knows how to do this.

First, JAXWS starts with a port. We do all operations on the port. All of the Http connections and sockets are created and managed within the JAXWS framework. Prior to the Exchange server turning off Basic authentication, I was simply able to set a default authenticator. However that no longer works with NTLMv2. Below is an attempt to work around the authenticator, which I have removed. Here is an example of getting the port for an Exchange server.

Notice the new bottom block. I'm trying to authenticate to an NTLMv2 website. To do that, I think I have to get hold of the URL connections. So I'm adding my own HttpsURLConnection default socket factory and HttpURLConnection content handler factory. I don't know if this is the best way to do this. My default content handler factory never gets called. I tried different things with the handlers as well with no success.

public ExchangeServicePortType getExchangeServicePort(String username, String password,
String domain,
URL wsdlURL) throws MalformedURLException {
// Concatenate domain and username for the UID needed in authentication.
String uid = domain + "\\" + username;

// Create an ExchangeWebService object that uses the supplied WSDL file, wsdlURL.
ExchangeServices services = new ExchangeServices(wsdlURL, new QName("http://schemas.microsoft.com/exchange/services/2006/messages", "ExchangeServices"));
ExchangeServicePortType port = services.getExchangeServicePort();
// Supply username and password when the ExchangeServicePortType is used for binding in the SOAP request.
((BindingProvider)port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, uid);
((BindingProvider)port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);

// Previous method bottom here

// Binding binding = ((BindingProvider)port).getBinding();
// List handlerList = binding.getHandlerChain();
// handlerList.add(new CeiLogicalHandler());
// handlerList.add(new CeiSoapHandler());
// binding.setHandlerChain(handlerList);

Object request = ((BindingProvider) port).getRequestContext().get(MessageContext.SERVLET_RESPONSE);

SSLSocketFactory ssf = HttpsURLConnection.getDefaultSSLSocketFactory();
System.out.println(ssf.getClass().getName());
ContentHandlerFactory chf = new MyContentHandlerFactory();
MySSLSocketFactory msf = new MySSLSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(msf);
HttpURLConnection.setContentHandlerFactory(chf);

return port;

// Anything I can do inside he SSL socket factory? Here is an attempt to make a connection to the source, and in fact succeeds in connecting.
// However, whatever it is that provides an authenticated connection to JAXWS to send the SOAP to does not know about my successful authentication.
// Anyone have suggestions?

public MySSLSocketFactory() {
try {
if (_sf == null)
_sf = new org.apache.http.conn.ssl.SSLSocketFactory(new TrustSelfSignedStrategy());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public Socket createSocket() {
Socket socket = null;
try {
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "UTF-8");
HttpProtocolParams.setUseExpectContinue(params, true);
if (_socket == null){
authenticate(_sf);
socket = _sf.createSocket(params);
}
else
socket = _socket;
} catch (Exception e) {
throw new RuntimeException(e);
}
return socket;
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) {
Socket socket = null;
try {
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "UTF-8");
HttpProtocolParams.setUseExpectContinue(params, true);
if (_socket == null) {
socket = _sf.createLayeredSocket(s, host, port, params);
}
else
socket = _socket;
} catch (Exception e) {
throw new RuntimeException(e);
}
return socket;
}

private void authenticate(org.apache.http.conn.ssl.SSLSocketFactory sf) {
try {
HttpHost httpHost = new HttpHost(_host, 443, "https");

// general setup
// This method results in a successful authentication, but that does not translate to
// an authenticated "socket," if that even makes sense.

SchemeRegistry supportedSchemes = new SchemeRegistry();

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "UTF-8");
HttpProtocolParams.setUseExpectContinue(params, true);
NTLMSchemeFactory nsf = new NTLMSchemeFactory();
AuthScheme authScheme = nsf.newInstance(params);
AuthCache authCache = new BasicAuthCache();
authCache.put(httpHost, authScheme);
supportedSchemes.register(new Scheme("https", 443, sf));
BasicHttpContext localcontext = new BasicHttpContext();
localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache);

ClientConnectionManager ccm = new PoolingClientConnectionManager(supportedSchemes);
HttpRoute hr = new HttpRoute(httpHost);
ClientConnectionRequest ccr = ccm.requestConnection(hr, null);
ManagedClientConnection mcc = ccr.getConnection(30L, TimeUnit.SECONDS);
mcc.open(hr, localcontext, params);

DefaultHttpClient httpclient = new DefaultHttpClient(ccm, params);

NTCredentials ntCredentials = new NTCredentials(_user, _password, _localIp, _domain);
AuthScope authScope = new AuthScope(httpHost);
httpclient.getCredentialsProvider().setCredentials(authScope, ntCredentials);

HttpGet httpget = new HttpGet(_urlFrag);

HttpResponse response = httpclient.execute(httpHost, httpget, localcontext);
HttpEntity entity = response.getEntity();
HttpParams hp = response.getParams();
if (entity != null) {
System.out.println("Response content length: " + entity.getContentLength());
System.out.println(EntityUtils.toString(entity));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
dgodbey
Offline
Joined: 2006-10-27
Points: 0

I finally got this worked out, although I'm not completely happy with resolution. Still tweaking and fine tuning.

First, you will recall that you had to store a copy of the Services.wsdl locally, specifically so that you could add the following element to it:

  <wsdl:service name="ExchangeServices">
    <wsdl:port name="ExchangeServicePort" binding="tns:ExchangeServiceBinding">
      <soap:address
      location="http://localhost:8080/exchangeCron/ewsProxy" />
    </wsdl:port>
  </wsdl:service>


It's the location address that your jax-ws client uses as ws endpoint. What you can do is write a servlet, and put it's address in the location attribute instead.

Inside the servlet you can get the SOAP request, post it to the real exchange server using http-components version 4.2.2 or later, receive the SOAP response, and send that back to the client.

Don't forget the headers!

I plan to convert this eventually to a real proxy server. There is an open source proxy server

Stratus
Offline
Joined: 2012-09-14
Points: 0

The JWebServices for Exchange supports NTLM and NTLMv2 authentication.