本篇體驗ASP.NET Web API的安全管道。這裡的安全管道是指在請求和響應過程中所經歷的各個組件或進程,比如有IIS,HttpModule,OWIN,WebAPI,等等。在這個管道中大致分兩個階段,一個是驗證階段,另一個是授權階段。在ASP.NET Web API v1版本的時候,安全管道大致
本篇體驗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中註入。