使用Apache CXF创建SOAP Web服务客户端,用于NTLM身份验证。

4
我想使用CXF开发一个SOAP客户端来连接SharePoint,身份验证方案是NTLM。但是,我遇到了一个问题:当运行SOAP客户端的机器上已登录的用户拥有访问SharePoint的权限时,CXF SOAP客户端总是使用已登录的用户,而我想指定其他用户凭据(不是已登录的用户)。CXF使用JDK中的HttpURLConnection,在HttpURLConnection中,当已登录的用户经过NTLM身份验证时,指定的凭据将被忽略。这些代码在CXF版本2.7.11上尝试过。
我尝试过的解决方法如下:

1)设置通道授权

String username = "user";     
String password = "password";    

JaxWsProxyfactoryBean factory1 = new JaxWsProxyfactoryBean();    
factory1.setServiceClass(WebsSoap.class);    
factory1.setAddress(url);    
factory1.setUsername(username);    
factory1.setPassword(password);

WebsSoap service = (WebsSoap) factory1.create();    
Client client = ClientProxy.getClient(service);    

HTTPconduit conduit = (HTTPconduit) client.getconduit();    
conduit.getAuthorization().setAuthorizationType("NTLM");    
conduit.getAuthorization().setUserName(username);    
conduit.getAuthorization().setPassword(password);

HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();  
httpClientPolicy.setConnectionTimeout(36000);  
httpClientPolicy.setAllowChunking(false);  
conduit.setClient(httpClientPolicy);  
        
service.getWeb(".");
问题:

对于上述场景,这种方法不起作用,因为它始终使用已登录的凭据。当我指定无效凭据时,它也不会失败。


2) AsyncHTTPConduit

另一种解决方案是使用AsyncHTTPConduit,它使用HttpAsyncClient而不是HttpURLConnection。这是因为HTTP组件不会绕过指定的凭据,可以忽略已登录的用户(我已经使用HttpClient测试客户端成功验证了这一点)。

以下是代码片段:

Bus bus = BusFactory.getDefaultBus();    
bus.setProperty( "use.async.http.conduit", "true" );
 
Client client = ClientProxy.getClient( service );    
HTTPConduit http = (HTTPConduit)client.getConduit();    
if ( http instanceof AsyncHTTPConduit ) {    
    AsyncHTTPConduit conduit = (AsyncHTTPConduit)http;    
    DefaultHttpAsyncClient defaultHttpAsyncClient;    
    try {    
        defaultHttpAsyncClient = conduit.getHttpAsyncClient();    
    }    
    catch ( IOException exception ) {    
        throw new RuntimeException( exception );    
    }    
    defaultHttpAsyncClient.getCredentialsProvider().setCredentials( AuthScope.ANY,
                        new NTCredentials( "username", "password", "", "domain" ) );         
    conduit.getClient().setAllowChunking( false );
    conduit.getClient().setAutoRedirect( true );
}

问题:

上述代码引发错误:

在传送带上检测到授权循环。

以上代码快照显示了使用现已被弃用的DefaultHttpAsyncClient,现在应该使用CloseableHttpAsyncClient。但是,CloseableHttpAsyncClient没有提供一种方法来指定凭据给一个已经存在的CloseableHttpAsyncClient对象。不确定如何在此情况下使用CloseableHttpAsyncClient


3) 其他解决方案

我尝试的另一种解决方案是使用sun.net.www.protocol.http.ntlm.NTLMAuthenticationCallback,绕过已登录用户的身份验证,如此处所述。将此方法与上面提到的解决方案#1结合使用。对于有效/无效的凭据,此方法按预期工作,并且代码可以绕过已登录的凭据 :)

但是,当我指定无效凭据时,我没有收到HTTP 401错误,而是收到

无法发送消息,服务器达到最大重试20次

我尝试避免使用此解决方案,因为它使用了Java的内部包,并且没有直接确定HTTP 401错误的方法。

我该怎么做才能得出一个完整的解决方案?

1个回答

0

尝试使用这个拦截器。它可以避免自动认证。

public class DisableAutomaticNTLMAuthOutInterceptor extends AbstractPhaseInterceptor<Message>
{
    private boolean isFieldsAvailable;

    private Field tryTransparentNTLMProxyField;

    private Field tryTransparentNTLMServerField;

    public DisableAutomaticNTLMAuthOutInterceptor() {
        super(Phase.PRE_STREAM);

        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Void run() {
                try {
                    DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMServerField = HttpURLConnection.class.getDeclaredField("tryTransparentNTLMServer");
                    DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMServerField.setAccessible(true);

                    DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMProxyField = HttpURLConnection.class.getDeclaredField("tryTransparentNTLMProxy");
                    DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMProxyField.setAccessible(true);
                    DisableAutomaticNTLMAuthOutInterceptor.this.isFieldsAvailable = true;

                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }

    @Override
    public void handleMessage(final Message message) throws Fault {
        if (this.isFieldsAvailable)
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Void run() {
                    try {
                        Object httpConnection = message.get("http.connection");
                        if (httpConnection != null) {
                            DisableAutomaticNTLMAuthOutInterceptor.this.processHttpConnection(message.get("http.connection"));
                        }
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                    return null;
                }
            });

    }

    private void processHttpConnection(Object httpConnection) throws IllegalArgumentException, IllegalAccessException {

        if (HttpURLConnection.class.isAssignableFrom(httpConnection.getClass())) {
            tryTransparentNTLMServerField.set(httpConnection, Boolean.FALSE);
            tryTransparentNTLMProxyField.set(httpConnection, Boolean.FALSE);
        } else {
            Field tempField = null;
            for (Field field : httpConnection.getClass().getDeclaredFields()) {
                if (HttpURLConnection.class.isAssignableFrom(field.getType())) {
                    field.setAccessible(true);
                    tempField = field;
                    break;
                }
            }
            if (tempField != null) {
                processHttpConnection(tempField.get(httpConnection));
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接