ASP.NET Web API的安全管道

Posted stay hungry, stay modest

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASP.NET Web API的安全管道相关的知识,希望对你有一定的参考价值。

 

本篇体验ASP.NET Web API的安全管道。这里的安全管道是指在请求和响应过程中所经历的各个组件或进程,比如有IIS,HttpModule,OWIN,WebAPI,等等。在这个管道中大致分两个阶段,一个是验证阶段,另一个是授权阶段。

在ASP.NET Web API v1版本的时候,安全管道大致是这样的:

→ Authentication,请求来到IIS中的HttpModule
→ Authenticatin, 请求来到API的HttpMessageHandler
→ Authorization, 请求来到Authorization Filter
→ Authorization, 请求来到Controller

当ASP.NET Web API来到v2版本的时候,安全管道大致是:

→ 请求来到Host中的OWIN组件
→ 请求来到MessageHandler,全局或按每个请求
→ 请求来到Authentication Filter
→ 请求来到Authorization Filter

可见,加入了OWIN组件,OWIN是开源的, Microsoft在此基础上开发出了Katana验证中间件。

我们知道,ASP.NET Web API的宿主有两种方式:

1、Web宿主,ASP.NET, IIS
2、自宿主,WCF,.NET进程

如果把OWIN考虑进去,那就是:
1、IIS→ASP.NET+OWIN Bridge→ OWIN→Web API + OWIN Adapter
2、Process/Host+OWIN Bridge→OWIN→Web API + OWIN Adapter

一、了解管道中的各个组件


1.1 OWIN中间件

 

public class AuthenticationMiddleware
{
    private readonly Func<IDictionary<string, object>, Task> _next;
    
    public AuthenticationMiddleware(Func<IDictionary<string, object>, Task> next)
    {
        _next = next;
    }
    
    public async Task Invoke(IDictionary<string, object> env)
    {
        //检查env集合,进行验证
        env["server.user"] = CreatePrincipal();//设置principal;
        await _next(env);
    }
}

 

OWIN中间件的大致工作原理是:请求中的Header,Body,路由等信息被放在了IDictionary<string, object>这个字典集合中,并且提供了Invoke方法,把获取到的用户信息放在env["server.user"]中,并且调用一个动作处理IDictionary<string, object>集合。


1.2 Katana Authentication Middleware

这是Microsoft基于OWIN开发出来的验证组件,大致是:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticaitonOptions{
            AuthenticationType = "Cookies",
            //more
        });
        
        app.UseGoogleAuthentication(new GoogleAuthenticationOptions{
            AuthenticationType = "Google";
            //more
        });
        
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions{
            AuthenticationType = "Bearer";
            // more
        })
    }
}

以上,至少可以看出,可以为OWIN组件选择验证方式。

1.3 Message Handler

实施在全局或某个请求上。大致是:

public class MyHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        //检查请求
        
        var response = await base.SendAsync(request, cancellationToken);
        
        //检查响应
        return response;
    }
}

 

Message Handler从ASP.NET WEB API 2以后就不存在了。

 

1.4 Authentication Filter 验证过滤器

可以在全局配置:

WebApiConfig.cs
config.Filters.Add(new HostAuthenticationFilter("Bearer"));

当然过滤器也可以放在控制器和方法层面:

[HostAuthentication("Bearer")]
public class TestController : ApiController
{
    [HostAuthentication("Google")]
    public HttpResponseMessage Get(){}
    
    [OverrideAuthentication]
    [HostAuthentication("Cookies")]
    public HttpResponseMessage Delete(){}
}

 

1.5 Authorization Filter 授权过滤器

 

[Authorize]
public class DataController : ApiController
{
    [AllowAnonymous]
    public Data Get(){}
    
    [Authorize(Role = "Foo")]
    public HttpResponseMessage Delete(int id){}
}

如果授权失败,返回401报错。

1.6 获取用户的Identity

通过ApiController的User属性获取到用户的Identity。注意,User属性值可能为null。

二、通过例子来体验安全管道


2.1 自定义HttpModule

首先,请求过来,肯定要通过HttpModule。我们需要自定义一个HttpModule,通过一个版主方法把当前的用户信息打印出来。

 

namespace SecurityPipeline
{
    public class HttpModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.BeginRequest += context_BeginRequest;
        }
        
        void context_BeginRequest(object sender, EventArgs e)
        {
            Helper.Write("HttpModule", HttpContext.Current)
        }
        
        public void Dispose()
        {
        
        }
    }
}

namespace SecurityPiepeline
{
    public static class Helper
    {
        public static void Write(string state, IPrincipal principal)
        {
            Debug.WriteLine("------------" + stage + "--------");
            if(principal == null || principal.Identity == null  || !principal.Identity.IsAuthenticated)
            {
                Debug.WriteLine("anonymous user");
            }
            else
            {
                Debug.WriteLine("User:" + principal.Identity.User);
            }
            
            Debug.WriteLine("\n");
        }
    }
}

 

可见,HttpContext.Current是IPrincipal类型。

然后这是一个Web项目,需要把HTTP module注册一下。

 

<configuration>
    <system.webServer>
        <modules>
            <add name="DemoModule" type="SecurityPipeline.HttpModule"/>
        </modules>
    </system.webServer>
</configuration>

 

如果此时项目下有一个default.html页面的话,运行项目,展示default.html的时候,控制台打印出如下信息:

-----HttpModule-------
anonymouse

显然,请求过来,自定义的Http Module起了作用,但目前还不能从IPrincipal中拿到User信息。

2.2 安装ASP.NET Web API 2

2.3 创建控制器

 

using System.Net.Http;
public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        Helper.Write("Controller", User);
        
        //获取用户也可以这样写
        //Helper.Write("Controller",Request.GetRequestContext().Principal);
        return Ok();
    }
}

 

以上,通过ApiController的User属性获取到IPincipal类型。

2.4 安装Microsoft.Owin.Host.SystemWeb

2.5 安装Microsoft. ASP.NET Web API 2.1 OWIN

2.6 创建Startup类

 

using OWin;
using System.Web.Http;

namespace SecurityPopeline
{
    public class Startup
    {
        public void Configuraiton()
        {
            var configuration = new HttpConfiguration();
            configuration.Routes.MapHttpRoute("default", "api/{controller}");
            
        }
    }
}

这里,让WEB API的HttpConfiguraton实例赋值给类OWIN的IAppBuilder的UseWebApi方法。

2.7 请求路由:localhsot:8000/api/test

显示
------HttpModule--------
anonymous user

------Controller--------
anonymous user

可见,请求一路经过管道中的HttpModule和Controller,但依然没有拿到用户信息。

2.8 创建TestMiddleware类

进入HttpModule之后,和进入Controller之前,这里是OWIN组件的生存之地。

 

using Microsoft.Owin;

namespace SecurityPopeline.Pipeline
{
    public class TestMiddleware
    {
        private Func<IDictionary<string, object>, Task> _next;
        public TestMiddleware(Func<IDictionary<string, object>, Task> next)
        {
            _next = next;
        }
        
        public async Task Invoke(IDictionary<string, object> env)
        {
            var context = new OwinContext(env);
            Helper.Write("Middleware", context.Request.User);
            await _next(env);
        }
    }
}

 

2.9 Startup类中增加有关TestMiddleware部分

 

using OWin;
using System.Web.Http;

namespace SecurityPopeline
{
    public class Startup
    {
        public void Configuraiton(IAppBuilder app)
        {
            var configuration = new HttpConfiguration();
            configuration.Routes.MapHttpRoute("default", "api/{controller}");
            
            app.Use(typeo(TestMiddleware));
            
            app.UseWebApi(configuration);
        }
    }
}

 

3.10 请求路由:localhsot:8000/api/test

显示
------HttpModule--------
anonymous user

------Middleware--------
anonymous user

------Controller------
anonymous user

可见,请求一路过来历经管道中的HttoModule, OWIN, 最后到达Controller,依然没有获取到用户信息。

3.11 添加TestAuthenticationFilterAttribute类

在OWIN和Controller之间,还有验证的接口,这也是安全管道中的一个重要环节。

 

using System.Web.Http.Filters;
using System.Threading.Tasks;

namespace SecurityPipeline.Pipeline
{
    public class TestAuthenticationFilterAttribute : Attribute, IAuthenticationFilter
    {
        public async Task AuthenticateAsync(HttpAuthenticationContext context)
        {
            Helper.Write("AuthenticationFilter", context.ActionContext.RequestContext.Principal, CancellationToken..)
        }
        
        public async Task ChallengeAsync(HttpAuthenticationContext context, CancellationToken..)
        {
        
        }
        
        public bool AllowMultiple
        {
            get {
                return false;
            }
        }
    }
}

 

控制器增加过滤特性

 

using System.Net.Http;


[TestAuthenticationFilter]
public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        Helper.Write("Controller", User);
        
        //获取用户也可以这样写
        //Helper.Write("Controller",Request.GetRequestContext().Principal);
        return Ok();
    }
}

 

3.12 请求路由:localhsot:8000/api/test

显示
------HttpModule--------
anonymous user

------Middleware--------
anonymous user

------AuthenticationFilter--------
anonymous user

------Controller------
anonymous user

可见,请求路径安全管道中的HttpModule,OWIN,验证,依然没有获取到用户信息。

3.13 增加TestAuthorizationFilterAttrbute类

在经过验证特性,以及进入Controller或Action之前,安全管道中还有一个重要的成员,就是授权特性。

 

public class TestAuthorizationFilterAttribute : AuthorizeAttibute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        Helper.Write("AuthorizationFilter", actionContext.RequestContext.Prioncipal);
        
        return base.IsAuthorized(actionContext);
    }
}

 

控制器增加授权特性

 

using System.Net.Http;


[TestAuthenticationFilter]
[TestAuthorizationFilter]
public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        Helper.Write("Controller", User);
        
        //获取用户也可以这样写
        //Helper.Write("Controller",Request.GetRequestContext().Principal);
        return Ok();
    }
}

 

3.14 请求路由:localhsot:8000/api/test

显示
------HttpModule--------
anonymous user

------Middleware--------
anonymous user

------AuthenticationFilter--------
anonymous user

------AuthorizationFilter--------
anonymous user

并报错:Authorization has been denied for this request

可见,在请求还没有到达Controller之前,就开始报错了。

于是,修改TestAuthorizationFilterAttrbute类如下:

public class TestAuthorizationFilterAttribute : AuthorizeAttibute
{
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        Helper.Write("AuthorizationFilter", actionContext.RequestContext.Prioncipal);
        
        //return base.IsAuthorized(actionContext);
        return true;
    }
}

 

3.15 请求路由:localhsot:8000/api/test

显示
------HttpModule--------
anonymous user

------Middleware--------
anonymous user

------AuthenticationFilter--------
anonymous user

------AuthorizationFilter--------
anonymous user

------Controller--------
anonymous user

可见,路由一路经过安全管道中的HttpModule, OWIN, AuthenticaitonFilter, AuthorizationFilter, Controller,依然没有获取到用户信息?

3.16 用户信息从哪里注入呢?

接下来要修改TestMiddleware类

 

using Microsoft.Owin;
using System.Security.Principal;

namespace SecurityPopeline.Pipeline
{
    public class TestMiddleware
    {
        private Func<IDictionary<string, object>, Task> _next;
        public TestMiddleware(Func<IDictionary<string, object>, Task> next)
        {
            _next = next;
        }
        
        public async Task Invoke(IDictionary<string, object> env)
        {
            var context = new OwinContext(env);
            
            //authentication
            //new string[]数组存放用户
            context.Request.User = new GenericPrincipal(new GenericIdentity("dom"),new string[]{});
            
            Helper.Write("Middleware", context.Request.User);
            await _next(env);
        }
    }
}

 

3.17 请求路由:localhsot:8000/api/test

显示
------HttpModule--------
anonymous user

------Middleware--------
User: dom

------AuthenticationFilter--------
User: dom

------AuthorizationFilter--------
User: dom

------Controller--------
User: dom


总结:请求一路过来,会经过安全管道中的HttpModule, OWIN,AuthenticaitonFilter, AuthorizationFilter, Controller,最后到达Action, 而用户信息可以在OWIN中注入。

 

以上是关于ASP.NET Web API的安全管道的主要内容,如果未能解决你的问题,请参考以下文章

细说Asp.Net Web API消息处理管道

如果我的标头中根本没有 Content-Type,ASP.NET Core Web API 管道的流程是啥?

ASP.NET Web API路由系统:路由系统的几个核心类型

ASP.NET Web API Security

学习ASP.NET Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位

学习ASP.NET Core,怎能不了解请求处理管道[1]: 中间件究竟是个什么东西?