(待翻译)Authentication Filters in ASP.NET Web API 2

转载 2016年08月31日 11:25:05
http://www.asp.net/web-api/overview/security/authentication-filters


The ASP.NET ISAPI Extension

The ASP.NET ISAPI extension (aspnet_isapi.dll) runs in the IIS process address space (inetinfo.exe) and forwards requests for ASP.NET file types to the ASP.NET worker process through a named pipe.

Specific ASP.NET file types are mapped to the ASP.NET ISAPI extension by mappings defined within the IIS metabase. Mappings for standard ASP.NET file types (including .aspx, .asmx, .rem, .soap) are established when the .NET Framework is installed.

Ff649266.fa3sn01(en-us,PandP.10).gif

An authentication filter is a component that authenticates an HTTP request. Web API 2 and MVC 5 both support authentication filters, but they differ slightly, mostly in the naming conventions for the filter interface. This topic describes Web API authentication filters.

Authentication filters let you set an authentication scheme for individual controllers or actions. That way, your app can support different authentication mechanisms for different HTTP resources.

In this article, I’ll show code from the Basic Authentication sample on http://aspnet.codeplex.com. The sample shows an authentication filter that implements the HTTP Basic Access Authentication scheme (RFC 2617). The filter is implemented in a class namedIdentityBasicAuthenticationAttribute. I won’t show all of the code from the sample, just the parts that illustrate how to write an authentication filter.

Setting an Authentication Filter

Like other filters, authentication filters can be applied per-controller, per-action, or globally to all Web API controllers.

To apply an authentication filter to a controller, decorate the controller class with the filter attribute. The following code sets the[IdentityBasicAuthentication] filter on a controller class, which enables Basic Authentication for all of the controller's actions.

[IdentityBasicAuthentication] // Enable Basic authentication for this controller.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }
    public IHttpActionResult Post() { . . . }
}

To apply the filter to one action, decorate the action with the filter. The following code sets the [IdentityBasicAuthentication] filter on the controller's Post method.

[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }

    [IdentityBasicAuthentication] // Enable Basic authentication for this action.
    public IHttpActionResult Post() { . . . }
}

To apply the filter to all Web API controllers, add it to GlobalConfiguration.Filters.

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

        // Other configuration code not shown...
    }
}

Implementing a Web API Authentication Filter

In Web API, authentication filters implement the System.Web.Http.Filters.IAuthenticationFilter interface. They should also inherit fromSystem.Attribute, in order to be applied as attributes.

The IAuthenticationFilter interface has two methods:

  • AuthenticateAsync authenticates the request by validating credentials in the request, if present.
  • ChallengeAsync adds an authentication challenge to the HTTP response, if needed.

These methods correspond to the authentication flow defined in RFC 2612 and RFC 2617:

  1. The client sends credentials in the Authorization header. This typically happens after the client receives a 401 (Unauthorized) response from the server. However, a client can send credentials with any request, not just after getting a 401.
  2. If the server does not accept the credentials, it returns a 401 (Unauthorized) response. The response includes a Www-Authenticate header that contains one or more challenges. Each challenge specifies an authentication scheme recognized by the server.

The server can also return 401 from an anonymous request. In fact, that’s typically how the authentication process is initiated:

  1. The client sends an anonymous request.
  2. The server returns 401.
  3. The clients resends the request with credentials.

This flow includes both authentication and authorization steps.

  • Authentication proves the identity of the client.
  • Authorization determines whether the client can access a particular resource.

In Web API, authentication filters handle authentication, but not authorization. Authorization should be done by an authorization filter or inside the controller action.

Here is the flow in the Web API 2 pipeline:

  1. Before invoking an action, Web API creates a list of the authentication filters for that action. This includes filters with action scope, controller scope, and global scope.
  2. Web API calls AuthenticateAsync on every filter in the list. Each filter can validate credentials in the request. If any filter successfully validates credentials, the filter creates an IPrincipal and attaches it to the request. A filter can also trigger an error at this point. If so, the rest of the pipeline does not run.
  3. Assuming there is no error, the request flows through the rest of the pipeline.
  4. Finally, Web API calls every authentication filter’s ChallengeAsync method. Filters use this method to add a challenge to the response, if needed. Typically (but not always) that would happen in response to a 401 error.

The following diagrams show two possible cases. In the first, the authentication filter successfully authenticates the request, an authorization filter authorizes the request, and the controller action returns 200 (OK).

In the second example, the authentication filter authenticates the request, but the authorization filter returns 401 (Unauthorized). In this case, the controller action is not invoked. The authentication filter adds a Www-Authenticate header to the response.

Other combinations are possible—for example, if the controller action allows anonymous requests, you might have an authentication filter but no authorization.

Implementing the AuthenticateAsync Method

The AuthenticateAsync method tries to authenticate the request. Here is the method signature:

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

The AuthenticateAsync method must do one of the following:

  1. Nothing (no-op).
  2. Create an IPrincipal and set it on the request.
  3. Set an error result.

Option (1) means the request did not have any credentials that the filter understands. Option (2) means the filter successfully authenticated the request. Option (3) means the request had invalid credentials (like the wrong password), which triggers an error response.

Here is a general outline for implementing AuthenticateAsync.

  1. Look for credentials in the request.
  2. If there are no credentials, do nothing and return (no-op).
  3. If there are credentials but the filter does not recognize the authentication scheme, do nothing and return (no-op). Another filter in the pipeline might understand the scheme.
  4. If there are credentials that the filter understands, try to authenticate them.
  5. If the credentials are bad, return 401 by setting context.ErrorResult.
  6. If the credentials are valid, create an IPrincipal and set context.Principal.

The follow code shows the AuthenticateAsync method from the Basic Authentication sample. The comments indicate each step. The code shows several types of error: An Authorization header with no credentials, malformed credentials, and bad username/password.

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;
    }

}

Setting an Error Result

If the credentials are invalid, the filter must set context.ErrorResult to an IHttpActionResult that creates an error response. For more information about IHttpActionResult, see Action Results in Web API 2.

The Basic Authentication sample includes an AuthenticationFailureResult class that is suitable for this purpose.

public class AuthenticationFailureResult : IHttpActionResult
{
    public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
    {
        ReasonPhrase = reasonPhrase;
        Request = request;
    }

    public string ReasonPhrase { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    private HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        response.RequestMessage = Request;
        response.ReasonPhrase = ReasonPhrase;
        return response;
    }
}

Implementing ChallengeAsync

The purpose of the ChallengeAsync method is to add authentication challenges to the response, if needed. Here is the method signature:

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

The method is called on every authentication filter in the request pipeline.

It's important to understand that ChallengeAsync is called before the HTTP response is created, and possibly even before the controller action runs. When ChallengeAsync is called, context.Result contains an IHttpActionResult, which is used later to create the HTTP response. So when ChallengeAsync is called, you don't know anything about the HTTP response yet. The ChallengeAsync method should replace the original value of context.Result with a new IHttpActionResult. This IHttpActionResult must wrap the original context.Result.

I'll call the original IHttpActionResult the inner result, and the new IHttpActionResult the outer result. The outer result must do the following:

  1. Invoke the inner result to create the HTTP response.
  2. Examine the response.
  3. Add an authentication challenge to the response, if needed.

The following example is taken from the Basic Authentication sample. It defines an IHttpActionResult for the outer result.

public class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
    public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
    {
        Challenge = challenge;
        InnerResult = innerResult;
    }

    public AuthenticationHeaderValue Challenge { get; private set; }

    public IHttpActionResult InnerResult { get; private set; }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            // Only add one challenge per authentication scheme.
            if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
            {
                response.Headers.WwwAuthenticate.Add(Challenge);
            }
        }

        return response;
    }
}

The InnerResult property holds the inner IHttpActionResult. The Challenge property represents a Www-Authentication header. Notice thatExecuteAsync first calls InnerResult.ExecuteAsync to create the HTTP response, and then adds the challenge if needed.

Check the response code before adding the challenge. Most authentication schemes only add a challenge if the response is 401, as shown here. However, some authentication schemes do add a challenge to a success response. For example, see Negotiate (RFC 4559).

Given the AddChallengeOnUnauthorizedResult class, the actual code in ChallengeAsync is simple. You just create the result and attach it tocontext.Result.

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

Note: The Basic Authentication sample abstracts this logic a bit, by placing it in an extension method.

Combining Authentication Filters with Host-Level Authentication

“Host-level authentication” is authentication performed by the host (such as IIS), before the request reaches the Web API framework.

Often, you may want to to enable host-level authentication for the rest of your application, but disable it for your Web API controllers. For example, a typical scenario is to enable Forms Authentication at the host level, but use token-based authentication for Web API.

To disable host-level authentication inside the Web API pipeline, call config.SuppressHostPrincipal() in your configuration. This causes Web API to remove the IPrincipal from any request that enters the Web API pipeline. Effectively, it "un-authenticates" the request.

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

        // Other configuration code not shown...
    }
}

Asp.net WebAPI Request参数验证-请不要重复造轮子

随着web客户端的发展,现在很多公司都有专业的前端开发,做到系统前后端分离。ap.net后端典型的就是采用webapi,但是发现很多时候大家对webapi并不了解,这里我们来说说输入参数的验证。前一段...
  • dz45693
  • dz45693
  • 2016年04月07日 21:19
  • 2516

ASP.NET Web API(三):安全验证之使用摘要认证(digest authentication)

在前一篇文章中,主要讨论了使用HTTP基本认证的方法,因为HTTP基本认证的方式决定了它在安全性方面存在很大的问题,所以接下来看看另一种验证的方式:digest authentication,即摘要认...
  • dyllove98
  • dyllove98
  • 2013年07月05日 22:09
  • 9029

ASP.NET Web API之消息[拦截]处理

标题相当难取,内容也许和您想的不一样,而且网上已经有很多这方面的资料了,我不过是在实践过程中作下记录。废话少说,直接开始。 Exception 当服务端抛出未处理异常时,most except...
  • dyllove98
  • dyllove98
  • 2013年08月05日 19:37
  • 10434

Using SSL in ASP.NET Web API

原文:http://www.codeguru.com/csharp/.net/using-ssl-in-asp.net-web-api.htm Introduction While...
  • Joyhen
  • Joyhen
  • 2016年04月26日 17:05
  • 833

Simple way to implement caching in ASP.NET Web API

另附:WebCache Class https://msdn.microsoft.com/en-us/library/system.web.helpers.webcache(v=vs.111).asp...
  • Joyhen
  • Joyhen
  • 2016年04月01日 15:53
  • 759

ASP.NET Web API2 Introduction

应用程序通信一直是架构设计方面的终点,微软从DCOM到.NET Remoting,再到WCF,一致致力于提供统一,规范的通信编程模式。WCF作为重量级的通信框架,一时间成为了SOA和WebServic...
  • afandaafandaafanda
  • afandaafandaafanda
  • 2015年01月16日 14:32
  • 706

ASP.NET Web API 2 入门教程

译者:jiankunking 出处:http://blog.csdn.net/jiankunking 源码下载HTTP不仅提供web页面服务,在构建公开服务和数据api方面,它也是一个强大的平台。HT...
  • xunzaosiyecao
  • xunzaosiyecao
  • 2016年05月17日 20:57
  • 3549

支持Ajax跨域访问ASP.NET Web Api 2(Cors)的示例

随着深入使用ASP.NET Web Api,我们可能会在项目中考虑将前端的业务分得更细。比如前端项目使用Angularjs的框架来做UI,而数据则由另一个Web Api 的网站项目来支撑。注意,这里是...
  • hoiven
  • hoiven
  • 2016年05月10日 14:24
  • 1691

使用angular4和asp.net core 2 web api做个练习项目(二), 这部分都是angular

import { Injectable } from '@angular/core'; import { Http, Headers } from '@angular/http'; import { ...
  • q781045982
  • q781045982
  • 2017年11月01日 11:02
  • 166

ASP.NET4.5Web API及非同步程序开发系列(2)

认识ASP.NET WEB API 他的前身为WCF WEB API用于协助WCF支持RestFul。现在集成进ASP.NET,正式更名为ASP.NET WEB API,ASP.NET Web AP...
  • qq1162195421
  • qq1162195421
  • 2013年08月07日 00:06
  • 3660
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:(待翻译)Authentication Filters in ASP.NET Web API 2
举报原因:
原因补充:

(最多只允许输入30个字)