ASP.NET Web API 中的基本身份验证

作者:Mike Wasson

基本身份验证在 RFC 2617、HTTP 身份验证:基本和摘要访问身份验证中定义。

缺点

  • 用户凭据在请求中发送。
  • 凭据以纯文本形式发送。
  • 凭据随每个请求一起发送。
  • 除非结束浏览器会话,否则无法注销。
  • 易受到跨站点请求伪造 (CSRF) ;需要反 CSRF 措施。

优势

  • Internet 标准。
  • 受所有主要浏览器支持。
  • 相对简单的协议。

基本身份验证的工作原理如下:

  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 经理仪表板的图像

在 Web API 项目中,为需要身份验证的任何控制器操作添加 [Authorize] 属性。

客户端通过在请求中设置 Authorization 标头对自身进行身份验证。 浏览器客户端自动执行此步骤。 非浏览器客户端将需要设置 标头。

使用自定义成员身份进行基本身份验证

如前所述,内置于 IIS 的基本身份验证使用 Windows 凭据。 这意味着需要在托管服务器上为用户创建帐户。 但对于 Internet 应用程序,用户帐户通常存储在外部数据库中。

以下代码如何执行基本身份验证的 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”扩展) 。

应禁用其他身份验证方案,例如窗体或 Windows 身份验证。