共用方式為


ASP.NET Web API 中的基本驗證

作者:Mike Wasson

如需基本驗證定義,請參閱 RFC 2617,HTTP 驗證:基本和摘要式存取驗證

缺點

  • 會在要求中傳送使用者認證。
  • 認證會以純文字的形式傳送。
  • 認證會隨每個要求一起傳送。
  • 除了結束瀏覽器工作階段之外,無法使用其他方式登出。
  • 容易受到跨網站偽造要求 (CSRF) 影響:需要防 CSRF 措施。

優點

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

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

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

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

基本驗證的圖表

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

基本驗證也容易受到 CSRF 攻擊的影響。 在使用者輸入認證之後,瀏覽器會在工作階段期間,在後續要求上自動將認證傳送至相同的網域。 這包括 AJAX 要求。 請參閱防止 Web API 中的跨網站偽造要求 (CSRF) 攻擊

採用 IIS 的基本驗證

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

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

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

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

IIS 管理員儀表板的影像

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

用戶端藉由在要求中設定 [授權] 標頭來進行自我驗證。 瀏覽器用戶端會自動執行此步驟。 非瀏覽器用戶端必須設定標頭。

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

如上所述,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 驗證。