使用 NTLM 的卷曲成功,但使用 NTLM 凭据的 HttpClient 失败 401

问题描述 投票:0回答:1

尝试通过 HTTP 与 SSRS 集成。我们在 VNET 上有一个 Azure Web 应用服务,并有一个托管 SQL 和 SSRS 的 VM。我们可以验证网络连接是否已建立,我们有一个继承 VM SQL 的 ADO.NET SQL 连接。

卷曲成功

从 Web 应用服务上的 Azure 门户中的 Web SSH 控制台,我们能够运行

curl -v --ntlm -u SomeDomain\\SomeUser:SomePassword http://[some ip]/reports/api/v2.0/Reports

输出如下。请注意最初的 401 后面是 200。我们认为这是 NTLM 握手/挑战

root@xxxxxxxxxxxx:/# curl -v --ntlm -u SomeDomain\\SomeUser:SomePassword http://[some ip]/reports/api/v2.0/Reports
*   Trying [some ip]:80...
* Connected to [some ip] ([some ip]) port 80 (#0)
* Server auth using NTLM with user 'SomeDomain\SomeUser'
> GET /reports/api/v2.0/Reports HTTP/1.1
> Host: [some ip]
> Authorization: NTLM [some base64 value]
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 401 Unauthorized
< Content-Length: 0
< Server: Microsoft-HTTPAPI/2.0
< WWW-Authenticate: NTLM [Another base64 value]
< Date: Mon, 09 Dec 2024 18:31:20 GMT
< 
* Connection #0 to host [some ip] left intact
* Issue another request to this URL: 'http://[some ip]/reports/api/v2.0/Reports'
* Found bundle for host: 0x56e3158ace40 [serially]
* Can not multiplex, even if we wanted to
* Re-using existing connection #0 with host [some ip]
* Server auth using NTLM with user 'SomeDomain\SomeUser'
> GET /reports/api/v2.0/Reports HTTP/1.1
> Host: [some ip]
> Authorization: NTLM [yet another base64 value]
> User-Agent: curl/7.88.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Cache-Control: no-cache
< Content-Length: 1868
< Content-Type: application/json; odata.metadata=minimal
< Server: Microsoft-HTTPAPI/2.0
< X-Content-Type-Options: nosniff
< Set-Cookie: XSRF-NONCE=EYQGqwO3ZSX4wJ4gAt7zUvHyoCxWWoFI%2B0I4m3zkliw%3D; path=/reports; HttpOnly
< Set-Cookie: XSRF-TOKEN=deprecated; path=/reports
< OData-Version: 4.0
< Date: Mon, 09 Dec 2024 18:31:20 GMT
< 
{
  "@odata.context":"http://[some ip]/reports/api/v2.0/$metadata#Reports","value":[
    ...
  ]
* Connection #0 to host [some ip] left intact

HttpClient 失败

尝试执行相同的操作,但从 C# Core Web App(托管在与上面的 Web SSH Shell 相同的 Azure Web App 上),我们使用

// test endpoint
[AllowAnonymous]
[HttpGet("report")]
public async Task<ActionResult> GetReport([FromQuery] string password)
{
    string user = "SomeUser";
    string domain = "SomeDomain";
    string url = "http://[some ip]";

    HttpClient client = new HttpClient(
        new HttpClientHandler
        {
            Credentials = new CredentialCache
            {
                {
                    new Uri(url),
                    "NTLM",
                    new NetworkCredential(user, password, domain)
                }
            },
        })
    {
        BaseAddress = new Uri(url),
    };

    HttpResponseMessage? result = null;
    Exception? exception = null;
    try
    {
        result = await client.GetAsync("/reports/api/v2.0/Reports");
    }
    catch (Exception e)
    {
        exception = e;
    }

    return Ok(
        new
        {
            client.BaseAddress,
            client.DefaultRequestHeaders,
            user,
            domain,
            password,
            url,
            Result = result,
            Exception = exception?.ToString(),
        });
}

我们得到类似的输出

{
  "baseAddress": "http://[some ip]",
  "defaultRequestHeaders": [
    {
      "key": "Expect",
      "value": [
        "100-continue"
      ]
    }
  ],
  "user": "SomeUser",
  "domain": "SomeDomain",
  "password": "SomePassword",
  "url": "http://[some ip]",
  "result": {
    "version": "1.1",
    "content": {
      "headers": [
        {
          "key": "Content-Length",
          "value": [
            "0"
          ]
        }
      ]
    },
    "statusCode": "Unauthorized",
    "reasonPhrase": "Unauthorized",
    "headers": [
      {
        "key": "Server",
        "value": [
          "Microsoft-HTTPAPI/2.0"
        ]
      },
      {
        "key": "WWW-Authenticate",
        "value": [
          "NTLM"
        ]
      },
      {
        "key": "Date",
        "value": [
          "Mon, 09 Dec 2024 18:49:16 GMT"
        ]
      }
    ],
    "trailingHeaders": [],
    "requestMessage": {
      "version": "1.1",
      "versionPolicy": "RequestVersionOrLower",
      "method": {
        "method": "GET"
      },
      "requestUri": "http://[some ip]/reports/api/v2.0/Reports",
      "headers": [
        {
          "key": "Expect",
          "value": [
            "100-continue"
          ]
        },
        {
          "key": "Request-Context",
          "value": [
            "appId=cid-v1:e81bb4ec-82a5-4ff3-a0fe-37bdc20cf3e6"
          ]
        },
        {
          "key": "Request-Id",
          "value": [
            "|f49543737031b257e9269e9f2fe9e719.19a134c67a9ab46f."
          ]
        },
        {
          "key": "traceparent",
          "value": [
            "00-f49543737031b257e9269e9f2fe9e719-19a134c67a9ab46f-00"
          ]
        }
      ],
      "properties": {},
      "options": {}
    },
    "isSuccessStatusCode": false
  }
}

问题

  • 此请求的准备有问题吗?
  • NTLM 凭据是否按预期应用?
  • 这 401 是 HttpClient(具有准备好的 NTLM 凭据)没有处理的握手/挑战的一部分吗?
  • 还有其他想法吗?输入?分析?
asp.net-core reporting-services azure-web-app-service dotnet-httpclient
1个回答
0
投票

我相信这个注释:https://learn.microsoft.com/en-us/dotnet/api/system.net.networkcredential?view=net-8.0#remarks

意味着带有提供密码的 NetworkCredential 仅适用于该库的 BASIC 身份验证。 对于 NTLM 和 Kerberos,它使用正在运行的程序的标识。 检查传出的身份验证标头以进行确认。

如果您愿意将密码发送到报表服务器,则可以切换到 HTTP 基本身份验证。

https://learn.microsoft.com/en-us/sql/reporting-services/security/configure-basic-authentication-on-the-report-server?view=sql-server-ver16

如果这是 Windows,您可以设置 UseDefaultCredentials = true 并以目标用户身份运行该进程或模拟目标用户,或者将用户名/密码注入 Windows 凭据管理器。

© www.soinside.com 2019 - 2024. All rights reserved.