使用 Apache HttpClient 和负载平衡的 SharePoint REST 调用在第一次 GET 后失败

SharePoint REST calls with Apache HttpClient and Load Balancing fails after first GET

我偶然发现了 SharePoint REST 调用负载平衡器的问题。当我将 REST 调用直接发送到网络服务器时,一切似乎都运行良好。但是,当 REST 调用发送到负载均衡器时,只有第一个调用获得 HTTP 200 OK 状态,其余的 returns HTTP 401 Unauthorized。经过一些研究,我发现 this article 上面写着:

这不是错误。 Windows 网络堆栈中内置了一个安全修复程序,可防止解析为环回地址的机器名称接受连接,除非该机器名称与 NETBIOS 名称匹配。

那篇文章中提到的唯一解决方法是更改​​服务器的配置,这不是我们部门愿意做的事情。

目标是我们可以从 Java 应用程序进行 SharePoint REST 调用。

例如,我制作了一个小测试程序,当我按下按钮时,它会向我的 SharePoint REST API 发出 GET 请求。

当我调整我的 Windows 主机文件以将负载平衡 url 映射到我们的一个网络服务器的 IP 地址(在负载平衡器下)时,一切似乎都工作正常:

//first button click
Executing request GET https://lbserver.domain.com/sites/my_site/_api/web/Lists/getByTitle('Registratielijst')/Items('1') HTTP/1.1
[principal: domain.com\<username>] [workstation: <LBSERVER>]
----------------------------------------
HTTP/1.1 200 OK

//second button click
Executing request GET https://lbserver.domain.com/sites/my_site/_api/web/Lists/getByTitle('Registratielijst')/Items('1') HTTP/1.1
[principal: domain.com\<username>] [workstation: <LBSERVER>]
----------------------------------------
HTTP/1.1 200 OK

但是,一旦我们注释掉映射(并且请求转到负载均衡器),问题就开始发生了。

//first button click
Executing request GET https://lbserver.domain.com/sites/my_site/_api/web/Lists/getByTitle('Registratielijst')/Items('1') HTTP/1.1
[principal: domain.com\<username>] [workstation: <LBSERVER>]
----------------------------------------
HTTP/1.1 200 OK

//second button click
Executing request GET https://lbserver.domain.com/sites/my_site/_api/web/Lists/getByTitle('Registratielijst')/Items('2') HTTP/1.1
[principal: domain.com\<username>] [workstation: <LBSERVER>]
----------------------------------------
HTTP/1.1 401 Unauthorized

如您所见,第一个 GET 请求运行得很好。之后,我收到 HTTP 401 Unauthorized 错误。

即使身份验证数据位于 HttpContext 中,为什么 HttpClient 会丢失它的 authentication/session(作为第一个请求 运行 很好)?我的客户端代码中是否缺少任何解决此问题的方法?


更新:

在 get() 方法中调用 openClient() 和 closeClient() 时,两个 GET 请求都可以正常工作。但是没有必要重新创建我假设的 httpClient?


完整代码如下:

CloseableHttpClient httpClient;
CredentialsProvider credsProvider;
HttpClientContext context;
HttpHost targetHost;

Account account;

private void handleButtonAction(ActionEvent event) throws IOException {
    get("https://lbserver.domain.com/sites/my_site/_api/web/Lists/getByTitle('Registratielijst')/Items('1')");
}

private void get(String uri) throws IOException {
    HttpGet httpget = new HttpGet(uri);

    System.out.println("\nExecuting request " + httpget.getRequestLine());

    CloseableHttpResponse response = httpClient.execute(targetHost, httpget, context);
    System.out.println("----------------------------------------");
    System.out.println(response.getStatusLine());

    response.close();
}

private void openClient() throws IOException {
    System.out.println("Open client");

    httpClient = HttpClients.createDefault();
    targetHost = new HttpHost("lbserver.domain.com", 443, "https");
    context = HttpClientContext.create();

    credsProvider = new BasicCredentialsProvider();
    credsProvider.setCredentials(
            new AuthScope(targetHost, AuthScope.ANY_REALM, "ntlm"), 
            new NTCredentials(account.getUsername(), account.getPassword(), "lbserver", "domain.com"));

    context.setCredentialsProvider(credsProvider);

    CookieStore cookieStore = new BasicCookieStore();
    context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);
}

private void closeClient() throws IOException {
    System.out.println("Close client");
    if (httpClient != null) {
        httpClient.close();
    }
}

public void initialize(URL url, ResourceBundle rb) {
    account = new Account();

    try {
        openClient();
    } catch (IOException ex) {
        Logger.getLogger(FXMLController.class.getName()).log(Level.SEVERE, null, ex);
    }

    Runtime.getRuntime().addShutdownHook(new Thread(new Task() {
        @Override
        protected Object call() throws Exception {
            closeClient();
            return null;
        }
    }));
}

经过更多研究,我发现问题是由错误的 cookie 处理引起的。原因是 Apache HTTP 客户端使用 CookieSpecs.DEFAULT (RFC2109)。将其更改为 CookieSpecs.STANDARD (RFC6265) 后,它起作用了。

RequestConfig localConfig = RequestConfig.custom()
            .setCookieSpec(CookieSpecs.STANDARD)
            .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM))
            .build();

httpClient = HttpClients.custom()
            .setDefaultCookieStore(new BasicCookieStore())
            .setDefaultCredentialsProvider(credsProvider)
            .setDefaultRequestConfig(localConfig)
            .build();