Insomnia呼叫由Kestrel啟動的Web API時,會一直回應401 Unauthorized錯誤

這幾天再用Insomnia測試API時,發現到:

使用IIS Express啟動網頁時,API可以透過Insomnia成功呼叫。

但使用Kestrel(https)啟動網頁時,呼叫API會一直回應401 Unauthorized的錯誤,但直接用瀏覽器卻又可以成功呼叫API。

在看這篇文章前,建議先去看一下這篇.Net Core 登入驗證方式

先對Windows驗證. Kerberos(Negotiate)以及 TNLM有點概念後,再回來這裡。

可能比較不會看的霧沙沙@@


launchSettings.json設定:
{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:56467",
      "sslPort": 44371
    }
  },
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      //"launchUrl": "dar",
      "applicationUrl": "http://localhost:5153",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      //"launchUrl": "dar",
      "applicationUrl": "https://localhost:7162;http://localhost:5153",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      //"launchUrl": "dar",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

 

launchSettings.json的CommandName如果不是使用IIS Express的話,預設就會使用Kestrel來當作網頁伺服器來啟動網頁。

Kerstrel啟動:

profiles底下的http & https

IIS啟動:

profiles底下的IIS Express,iisSettings可以進行參數設定


Insomnia無法成功呼叫API錯誤重新呈現 & 問題發生原因:

由於開發API時,我是選用https(Port:7162)來啟動我的網頁。先前提到launchSettings.json的CommandName如果不是使用IIS Express的話,預設就會使用Kestrel來當作網頁伺服器來啟動網頁,當使用Kestrel + NTLM呼叫API時就會回應401錯誤。

但直接用網頁呼叫API又沒有問題(請忽略bad request,畢竟只是個測試API,至少是確定能確實呼叫API。)

 

一直找不到錯誤發生的原因,後來看到StackOverflow上也有人有提出Postman也有類似的問題[.NET Core 3.1 WebApi project + NTLM Authentication]。只知道使用API測試軟體時,如果使用Kestrel + NTLM,好像就會出現401 Unauthorized的問題。這篇文章[Postman 401 Unauthorized using NTLM]另外提到,這問題應該是跟NTLM的版本有關。Postman只支援發送NTLMv1。但我覺得[Windows Authentication not working in Kestrel running in Linux (Ubuntu)]] 裡面的這個回覆才是正解。

HTTP/2(什麼是 HTTP?為什麼 HTTP/2 比 HTTP/1.1 更快?)並不支援Windows驗證,所以也不支援我們使用的NTLM驗證方式。
來源:msdn - IIS 上的 HTTP/2( https://learn.microsoft.com/zh-tw/iis/get-started/whats-new-in-iis-10/http2-on-iis)

 

Insomnia呼叫kestrel(port:7162)的API時,預設Request所使用的HTTP Protocol為HTTP/2,在Kestrel預設也會啟用HTTP/2的情況下,Kestrel也是以HTTP/2來回應,但因為HTTP/2不支援NTLM,所以才會有使用Insomnia呼叫Kestrel+NTLM的API時會出現401 Unauthorized的問題發生。

 

直接使用瀏覽器Edge呼叫kestrel(port:7162)的API時,預設Request所使用的HTTP Protocol為HTTP/1,Kestrel也是以HTTP/1來回應,因為HTTP/1支援NTLM,所以不會有401的問題。

 

Insomnia呼叫IIS Express(port:44371)的API時,預設Request所使用的HTTP Protocol為HTTP/2。IIS Express預設也會啟用HTTP/2的情況下,原本來說IIS Express也是會以HTTP/2來回應,但因為HTTP/2不支援NTLM,IIS會自己恢復成使用HTTP/1回應,所以使用Insomnia呼叫IIS Express+NTLM的API時能回應200正常。
這邊順便補充一下,為什麼下圖最後結果是回應200。但中間卻出現了2次401。簡單來說就是Client跟Server之間的身分確認不是一個步驟就直接完成,而是透過來回交換不同資訊來完成驗證,在未確認身分前的回應都會為401

 

簡單的做個總結就是,NTLM不支援HTTP/2。但Insomnia發出Request給Kestrel時,預設使用的HTTP版本為HTTP/2,Kestrel也用HTTP/2回應的話,因為無法進行NTLM驗證,導致401錯誤。但Insomnia發出Request給IIS Express時,IIS Express會自己選擇以HTTP/1回應,再重新進行以HTTP/1進行TLS交握流程,NTLM支援HTTP/1,所以不會有問題。


解決方法1_改用IIS_Express啟動網頁:

於是我們改用IIS Express(Port:44371)來啟動網頁

 

卻還是出現了錯誤

 

原來是我們有啟用了Windows驗證,launchSettings.json裡面的windowsAuthentication也要從false改成true。

 

再透過IIS Express重新啟動一次網頁,輸入NTML驗證帳號密碼後,再重新呼叫一次API,可以看到API呼叫成功了(請忽略測試的Bad Request結果)!!


解決方法2_Kestrel的驗證方式由NTLM改成用JWT來進行身分驗證
前面有提到,在使用Postman或Insomnia時。如果使用Kestrel + NTLM,好像就會出現401 Unauthorized的問題。後來想到有另外一個專案,有使用到JWT驗證,如果我們把驗證方法由Kestrel + NTLM改成Kestrel + JWT不知道能不能透過Insomnia成功呼叫API。

 

另外一個專案的launchSettings.json如下:

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:9881",
      "sslPort": 44313
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

 

先使用Kestrel + NTLM來測試呼叫API一下看看,結果也是跟之前一樣出現了401 Unauthorized的錯誤。測試之前要記得到Program.cs把JWT相關的註冊註解掉落,不然www-authenticate會變成Bearer,就不會是我們要的NTLM了

 

順便補上用Postman的測試結果,Kestrel + NTLM回應401。

 

接著使用Kestrel + JWT來測試呼叫API一下看看,會不會也跟之前一樣出現了401 Unauthorized的錯誤,結果是回應200,終於成功打通了!測試之前要記得到Program.cs把剛剛JWT相關的註冊註解拿掉

 

順便補上用Postman的測試結果,Kestrel + JWT回應200。


總結一下:

當使用API測試軟體時(這篇文章主要以Insomnia為主):

Kestrel + NTLM => 401

Kestrel + JWT => 200

IIS Express + NTLM => 200


手動修改Insomnia HTTP版本測試:

今天發現Insomnia可以設定發出Request要使用的HTTP Protocol,照之前的測試來看,Kestrel + NTLM會回應401的原因是因為HTTP/2不支援Windows驗證的問題。那我如果手動把Insomnia發出Request時的HTTP Protocol改成HTTP/1,再去呼叫Kestrel+NTLM的API會不會就能呼叫成功呢?

把HTTP Version改成HTTP/1

但結果還是出現了401。事到如今,我也想不出真正原因是什麼了…

 

 

Ref:
1.在 ASP.NET Core 中設定 Windows 驗證
2.Postman 401 Unauthorized using NTLM
3.冷知識 - NTLMv1 為什麼不安全?
4.Windows Authentication not working in Kestrel running in Linux (Ubuntu)
5.什麼是 HTTP?為什麼 HTTP/2 比 HTTP/1.1 更快?
6.IIS 上的 HTTP/2
7.HTTPClient getting two 401s before success (sending wrong token)