ASP.NET Web API中的基本驗證

作者:Mike Wasson

基本驗證定義于 RFC 2617、HTTP 驗證:基本和摘要式存取驗證

缺點

  • 系統會在要求中傳送使用者認證。
  • 認證會以純文字傳送。
  • 認證會隨每個要求一起傳送。
  • 無法登出,但結束瀏覽器會話除外。
  • 容易受到跨網站偽造要求 (CSRF) ;需要反 CSRF 量值。

優點

  • 網際網路標準。
  • 所有主要瀏覽器都支援。
  • 相對簡單的通訊協定。

基本驗證的運作方式如下:

  1. 如果要求需要驗證,伺服器會傳回 401 (未經授權) 。 回應包含WWW-Authenticate標頭,表示伺服器支援基本驗證。
  2. 用戶端會傳送另一個要求,並使用 Authorization 標頭中的用戶端認證。 認證會格式化為字串 「name:password」,base64 編碼。 認證不會加密。

基本驗證是在「領域」的內容內執行。伺服器會在WWW-Authenticate標頭中包含領域的名稱。 使用者的認證在該領域內有效。 領域的實際範圍是由伺服器所定義。 例如,您可以定義數個領域來分割資源。

基本驗證的圖表

由於認證會以未加密的方式傳送,因此基本驗證只會透過 HTTPS 保護。 請參閱 在 Web API 中使用 SSL

基本驗證也很容易遭受 CSRF 攻擊。 在使用者輸入認證之後,瀏覽器會在會話持續期間,自動將後續要求傳送給相同的網域。 這包括 AJAX 要求。 請參閱 防止跨網站偽造要求 (CSRF) 攻擊

使用 IIS 進行基本驗證

IIS 支援基本驗證,但有一個警告:使用者會根據其 Windows 認證進行驗證。 這表示使用者必須在伺服器的網域上擁有帳戶。 針對公開的網站,您通常會想要針對 ASP.NET 成員資格提供者進行驗證。

若要使用 IIS 啟用基本驗證,請在 ASP.NET 專案的Web.config中,將驗證模式設定為 「Windows」:

<system.web>
    <authentication mode="Windows" />
</system.web>

在此模式中,IIS 會使用 Windows 認證進行驗證。 此外,您必須在 IIS 中啟用基本驗證。 在 [IIS 管理員] 中,移至 [功能檢視],選取 [驗證],然後啟用 [基本驗證]。

I S Manager 儀表板的影像

在您的 Web API 專案中,為任何需要驗證的控制器動作新增 [Authorize] 屬性。

用戶端會在要求中設定 Authorization 標頭來驗證本身。 瀏覽器用戶端會自動執行此步驟。 非瀏覽器用戶端必須設定 標頭。

具有自訂成員資格的基本驗證

如前所述,IIS 內建的基本驗證會使用 Windows 認證。 這表示您必須為主控伺服器上的使用者建立帳戶。 但對於網際網路應用程式,使用者帳戶通常會儲存在外部資料庫中。

下列程式碼如何執行基本驗證的 HTTP 模組。 您可以藉由取代 CheckPassword 方法,輕鬆地插入 ASP.NET 成員資格提供者,這是此範例中的虛擬方法。

在 Web API 2 中,您應該考慮撰寫 驗證篩選器OWIN 中介軟體,而不是 HTTP 模組。

namespace WebHostBasicAuth.Modules
{
    public class BasicAuthHttpModule : IHttpModule
    {
        private const string Realm = "My Realm";

        public void Init(HttpApplication context)
        {
            // Register event handlers
            context.AuthenticateRequest += OnApplicationAuthenticateRequest;
            context.EndRequest += OnApplicationEndRequest;
        }

        private static void SetPrincipal(IPrincipal principal)
        {
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }

        // TODO: Here is where you would validate the username and password.
        private static bool CheckPassword(string username, string password)
        {
            return username == "user" && password == "password";
        }

        private static void AuthenticateUser(string credentials)
        {
            try
            {
                var encoding = Encoding.GetEncoding("iso-8859-1");
                credentials = encoding.GetString(Convert.FromBase64String(credentials));

                int separator = credentials.IndexOf(':');
                string name = credentials.Substring(0, separator);
                string password = credentials.Substring(separator + 1);

                if (CheckPassword(name, password))
                {
                    var identity = new GenericIdentity(name);
                    SetPrincipal(new GenericPrincipal(identity, null));
                }
                else
                {
                    // Invalid username or password.
                    HttpContext.Current.Response.StatusCode = 401;
                }
            }
            catch (FormatException)
            {
                // Credentials were not formatted correctly.
                HttpContext.Current.Response.StatusCode = 401;
            }
        }

        private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
        {
            var request = HttpContext.Current.Request;
            var authHeader = request.Headers["Authorization"];
            if (authHeader != null)
            {
                var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);

                // RFC 2617 sec 1.2, "scheme" name is case-insensitive
                if (authHeaderVal.Scheme.Equals("basic",
                        StringComparison.OrdinalIgnoreCase) &&
                    authHeaderVal.Parameter != null)
                {
                    AuthenticateUser(authHeaderVal.Parameter);
                }
            }
        }

        // If the request was unauthorized, add the WWW-Authenticate header 
        // to the response.
        private static void OnApplicationEndRequest(object sender, EventArgs e)
        {
            var response = HttpContext.Current.Response;
            if (response.StatusCode == 401)
            {
                response.Headers.Add("WWW-Authenticate",
                    string.Format("Basic realm=\"{0}\"", Realm));
            }
        }

        public void Dispose() 
        {
        }
    }
}

若要啟用 HTTP 模組,請將下列內容新增至 system.webServer 區段中的 web.config 檔案:

<system.webServer>
    <modules>
      <add name="BasicAuthHttpModule" 
        type="WebHostBasicAuth.Modules.BasicAuthHttpModule, YourAssemblyName"/>
    </modules>

將 「YourAssemblyName」 取代為元件名稱, (不包含 「dll」 延伸模組) 。

您應該停用其他驗證配置,例如 Forms 或 Windows 驗證。