五、在 Web API 中使用认证过滤器启用基本认证

本章将展示如何使用实现 HTTP 基本认证方案的认证过滤器为单个控制器或动作设置认证方案,并讨论使用基本认证的优缺点。

在本章中,我们将学习以下主题:

  • 使用 IIS 进行基本认证
  • 具有自定义成员资格的基本认证
  • 使用认证过滤器的基本认证
  • 设置认证过滤器
  • 实现 Web API 认证过滤器
  • 设置错误结果
  • 将认证过滤器与主机级认证相结合

使用 IIS 的基本认证

InternetInformation Services(IIS)允许基于其 Windows 凭据对用户进行认证。 因此,用户必须拥有域服务器帐户。 IIS 中的基本认证构建为使用 Windows 凭据进行认证。

下面的步骤将使用 IIS 启用基本认证:

  1. 打开您的ASP。 从 Visual Studio 的Start页面中获取 asp.net 应用
  2. 打开Web。 config文件。
  3. Web 界面中设置认证方式为Windows。 config文件:
  4. 打开IIS 管理器
  5. 进入特性视图
  6. Select Authentication in IIS Manger:

    Basic authentication with IIS

    图 1 -在特征视图中选择认证

  7. Disable Anonymous Authentication and enable Basic Authentication:

    Basic authentication with IIS

    图 2 -右击并启用基本认证

注意事项

如果你不觉得基本认证验证下,去程序和功能,打开或关闭【显示】窗口组件,并使基本认证下的【病人】IIS|万维网服务|【t16.1】安全。

**# 具有自定义会员资格的基本认证

我们刚刚看到了如何使用 IIS 启用基本认证。 然而,如果该网站是一个公开可用的网站,则不可能使用 Windows 凭据对用户进行认证。 在这种情况下,网站应该使用 ASP 进行认证。 网会员提供者。 为了实现这一点,我们需要实现具有基本认证的自定义成员。

具有基本认证的自定义成员可以使用 HTTP 模块实现,如下所示:

public class BasicAuthHttpModule : IHttpModule
{
   //...
}

我们需要创建两个方法,并将它们与Init方法中的AuthenticateRequestEndRequest事件挂钩,如下代码所示:

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

如您所见,OnApplicationAuthenticateRequest被添加或注册到HttpApplicationAuthenticateRequest事件中,OnApplicationEndRequest被添加或注册到HttpApplicationEndRequest事件中。

OnApplicationAuthenticateRequest方法应该检查请求头中的Authorization请求,如下所示:

var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
     //...
}

如果Authorization头包含基本认证的信息,那么应该从凭证中提取用户名和密码,并使用 ASP 进行验证。 注入到 HTTP 模块中的 NET 成员资格提供者,如下所示:

var encoding = Encoding.GetEncoding("iso-8859-1");
var credentials = encoding.GetString(Convert.FromBase64String(authHeader.Parameter));
int separator = credentials.IndexOf(':');
string name = credentials.Substring(0, separator);
string password = credentials.Substring(separator + 1);

如果获得的用户名和密码有效,则创建 IPrincipal 并将其分配给 HTTP 上下文的当前用户,如下所示:

// If credential is valid, then build the principal
var identity = new GenericIdentity(name);
Thread.CurrentPrincipal = new GenericPrincipal(identity, null));
if (HttpContext.Current != null)
{
    HttpContext.Current.User = principal;
}

web.config文件的system.webServer部分下添加以下代码来启用 HTTP 模块:

<modules>
      <add name="BasicAuthHttpModule" 
        type="ContactLookup.Modules.BasicAuthHttpModule, AssemblyName"/>
</modules>

用程序集的实际名称替换AssemblyName,不包括dll扩展名。 此外,我们还需要禁用其他认证,例如表单和 Windows 认证。

使用认证过滤器的基本认证

随着 ASP 的发布。 在基本认证中使用认证过滤器而不是使用 HTTP 模块是最佳实践。 按照给出的步骤使用认证过滤器实现基本认证:

  1. 在 Visual Studio 的Start页面中创建新项目
  2. 选择 Visual c# Installed Template 命名为Web
  3. 选择ASP.NET Web 应用
  4. Name the project Chapter05.BasicAuthentication and click OK:

    Basic authentication using an authentication filter

    图 3 -我们已经命名了 ASP.NET Web 应用作为“第五章”。 BasicAuthentication”

  5. Select the MVC template in the New ASP.NET Project dialog.

    Basic authentication using an authentication filter

    图 4 -选择 MVC 模板,在添加文件夹和核心引用中检查 Web API

  6. 检查Web API,然后点击添加文件夹和核心参考文献,【显示】认证个人用户帐户:

  7. 添加名为BasicAuthorizeAttribute的过滤器,继承AuthorizeAttribute并将代码替换为下面给出的代码:

    ``` namespace Chapter05.BasicAuthentication.Filters { public class BasicAuthorizeAttribute : System.Web.Http.AuthorizeAttribute { private const string BasicAuthResponseHeader = "WWW-Authenticate"; private const string BasicAuthResponseHeaderValue = "Basic";

        public override void OnAuthorization(HttpActionContext actionContext)
        {
            try
            {
                var authValue = actionContext.Request.Headers.Authorization;
    
                if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter) && authValue.Scheme == BasicAuthResponseHeaderValue)
                {
                    var credentials = ParseAuthorizationHeader(authValue.Parameter);
    
                    if (credentials != null)
                    {
                        // Check if the username and passowrd in credentials are valid against the ASP.NET membership.
                        // If valid, the set the current principal in the request context
                        var identity = new GenericIdentity(credentials.Username);
                        actionContext.RequestContext.Principal = new GenericPrincipal(identity, null);
                    }
                }
                else
                {
                    actionContext.Response = GetUnauthorizedResponse();
                    return;
                }
            }
            catch (Exception)
            {
                actionContext.Response = GetUnauthorizedResponse();
                return;
    
            }
        }
    
        private Credentials ParseAuthorizationHeader(string authHeader)
        {
            var credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(new[] { ':' });
    
            if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]))
                return null;
    
            return new Credentials() { Username = credentials[0], Password = credentials[1], };
        }
    
        private HttpResponseMessage GetUnauthorizedResponse()
        {
            var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
            response.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
            return response;
        }
    }
    public class Credentials
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
    

    } ```

  8. 正如您在代码中看到的,方法检查请求头中的Authorization。 如果Authroization头和基本认证信息都可用,那么它将尝试从Base64编码的令牌值中提取用户名和密码。 提取的用户凭证将根据 ASP 进行验证.NET 成员认证。

  9. Add a Web API controller named ContactsController and replace the code with the following code:

    ``` namespace Chapter05.BasicAuthentication.Api { public class ContactsController : ApiController { IEnumerable contacts = new List { new Contact { Id = 1, Name = "Steve", Email = "steve@gmail.com", Mobile = "+1(234)35434" }, new Contact { Id = 2, Name = "Matt", Email = "matt@gmail.com", Mobile = "+1(234)5654" }, new Contact { Id = 3, Name = "Mark", Email = "mark@gmail.com", Mobile = "+1(234)56789" } };

        [BasicAuthorize]
        // GET: api/Contacts
        public IEnumerable<Contact> Get()
        {
            return contacts;
        }
    }
    

    } ```

    也可以在控制器或应用级别配置 BasicAuthorize属性。 这可以通过用这个属性装饰控制器或者在一个global.asax文件中配置它来实现,以便在应用中启用它。

  10. ContactsController中的Get操作使用BasicAuthorize属性进行装饰,该属性是我们为基本认证创建的自定义属性。 因此,只有在 header 中具有有效的基本认证细节的请求才能访问 API 控制器中的GET动作。

设置认证过滤器

http://aspnet.codeplex.com提供了名为IdentityBasicAuthenticationAttribute的认证过滤器的示例代码,该过滤器实现 HTTP 基本访问认证方案(RFC 2617)。 我们可以使用这个[IdentityBasicAuthentication]认证过滤器,并将其应用于动作级别、控制器级别或可应用于所有控制器和动作的全局级别。

动作级认证过滤器

要在操作级别应用基本认证过滤器,我们需要用[IdentityBasicAuthentication]过滤器装饰各自的操作,如下所示:

// Require authenticated requests.
public class ContactsController : ApiController
{
    public HttpResponseMessage GetContact() { . . . }

    // Enable Basic authentication for this action
    [IdentityBasicAuthentication]
    public HttpResponseMessage PostContact(Contact contact) { . . . }
}

控制器级认证过滤器

如果我们在控制器级别修饰基本认证过滤器,那么访问该控制器内的所有操作需要一个经过认证的请求。 在控制器级应用基本认证过滤器如下所示:

// Enable Basic authentication for this controller [IdentityBasicAuthentication]
// Require authenticated requests.
[Authorize] 
public class ContactsController : ApiController
{
    public HttpResponseMessage GetContact() { . . . }
    public HttpResponseMessage PostContact(Contact contact) { . . . }
}

全局级别的认证过滤器

通过将该过滤器添加到webApiConfig类文件中的过滤器集合中,我们可以在 Web API 中全局应用任何过滤器,如下所示。 这段代码将IdentityBasicAuthentication过滤器添加到过滤器集合中,如下所示:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new IdentityBasicAuthenticationAttribute());
        //...
    }
}

实现 Web API 认证过滤器

Web API 中的认证过滤器必须实现System.Web.Http.Filters.IAuthenticationFilter接口。 该接口包含一个布尔类型的AllowMultiple属性,它指示可以为单个程序元素指定该属性的多个实例。 它有两个方法,即验证请求中的凭证的AuthenticateAsync和在需要时向响应附加认证挑战的ChallengeAsync

由于过滤器可以修饰为控制器和操作,我们还需要继承System.Attribute

在 Web API 控制器中执行操作之前,它首先构建一个在控制器级别和特定操作级别全局配置的认证过滤器列表。 然后它在列表中找到的每个过滤器中调用AuthenticateAsync方法。 每个过滤器中的AuthenticateAsync方法验证请求中的凭据,如果凭据验证成功,则创建 IPrincipal 并将其附加到请求中。 CodePlex 中 Basic Authentication sample 中的AuthenticateAsync方法的代码片段如下:

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
    // 1\. Look for credentials in the request.
    HttpRequestMessage request = context.Request;
    AuthenticationHeaderValue authorization = request.Headers.Authorization;

    // 2\. If there are no credentials, do nothing.
    if (authorization == null)
    {
        return;
    }

    // 3\. If there are credentials but the filter does not recognize the 
    //    authentication scheme, do nothing.
    if (authorization.Scheme != "Basic")
    {
        return;
    }

    // 4\. If there are credentials that the filter understands, try to validate them.
    // 5\. If the credentials are bad, set the error result.
    if (String.IsNullOrEmpty(authorization.Parameter))
    {
        context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
        return;
    }

    Tuple<string, string> userNameAndPasword = ExtractUserNameAndPassword(authorization.Parameter);
    if (userNameAndPasword == null)
    {
        context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
    }

    string userName = userNameAndPasword.Item1;
    string password = userNameAndPasword.Item2;

    IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
    if (principal == null)
    {
        context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
    }

    // 6\. If the credentials are valid, set principal.
    else
    {
        context.Principal = principal;
    }

}

成功创建 IPrincipal 之后,Web API 在列表中的可用过滤器中执行ChallengeAsync方法,向响应添加挑战。 方法的代码片段如下所示:

public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
    var challenge = new AuthenticationHeaderValue("Basic");
    context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
    return Task.FromResult(0);
}

设置错误结果

如果列表中的任何筛选器发现凭据无效,那么它将在AuthenticateAsync方法的上下文参数中设置ErrorResult。 在过滤器的AuthenticateAsync方法中,AuthenticationFailureResult实例以适当的错误消息被分配给上下文:

    // If the sufficient information for credentials not supplied.
    if (String.IsNullOrEmpty(authorization.Parameter))
    {
        context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
        return;
    }

    Tuple<string, string> userNameAndPasword = ExtractUserNameAndPassword(authorization.Parameter);
    string userName = userNameAndPasword.Item1;
    string password = userNameAndPasword.Item2;

    IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);

    // if the provided username and password is not valid
    if (principal == null)
    {
        context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
    }

将认证过滤器与主机级认证相结合

主机级的认证将由主机(如 IIS)本身在请求到达 Web API 框架之前执行。 我们可以通过调用 Web API 配置中的config.SuppressHostPrincipal()方法来禁用 Web API 中的主机级认证,如下所示:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SuppressHostPrincipal();
    }
}

最佳实践是在 Web API 中禁用主机级认证,并在应用的其余部分启用它。 一旦禁用了主机级认证,我们就可以应用前面在应用级、控制器级或 Web API 操作级创建的认证过滤器。

总结

万岁! 我们只是使用基本认证来保护我们的 Web API。

您了解了如何在 IIS 中配置基本认证,并使用自定义成员资格实现了基本认证。

我们已经看到了如何在不同的级别上设置认证过滤器,并一步一步地介绍了如何在 ASP 中实现认证过滤器。 净 Web API。

您还学习了如何在认证失败时设置错误结果。

最后,我们看到了如何在 ASP 中抑制主机级认证。 净 Web API。

在下一章中,让我们使用表单和 Windows 认证来保护 Web API。**