HTTP Message Handler
在 Web Api 2 认证与授权 中讲解了几种实现机制,本篇就详细讲解 Message Handler 的实现方式
关于 Message Handler 在 request 到 response 过程所处于的位置,可以参考这里 HTTP Message Handlers
Authentication Message Handler
先看一段实现的代码,然后再做讲解,完整代码可以在 Github 上参考,WebApi2.Authentication
1 using System; 2 using System.Net; 3 using System.Net.Http; 4 using System.Security.Claims; 5 using System.Threading; 6 using System.Threading.Tasks; 7 // WebPrint.Framework reference https://github.com/LeafDuan/WebPrint/tree/master/WebPrint.Framework 8 using WebPrint.Framework; 9 10 namespace Server.Helper 11 { 12 // references 13 // http://www.codeproject.com/Articles/630986/Cross-Platform-Authentication-With-ASP-NET-Web-API 14 // http://dgandalf.github.io/WebApiTokenAuthBootstrap/ 15 public class AuthenticationMessageHandler : DelegatingHandler 16 { 17 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 18 CancellationToken cancellationToken) 19 { 20 if (request.Headers.Authorization == null) 21 { 22 var reply = request.CreateResponse(HttpStatusCode.Unauthorized, "Missing authorization token."); 23 24 return Task.FromResult(reply); 25 } 26 27 try 28 { 29 var encryptedToken = request.Headers.Authorization.Parameter; 30 var token = Token.Decrypt(encryptedToken); 31 //bool isValidUser 32 var isIpMathes = token.ClientIp.EqualTo(request.GetClinetIp()); 33 34 if (!isIpMathes) 35 { 36 var reply = request.CreateResponse(HttpStatusCode.Unauthorized, "Invalid authorization token"); 37 return Task.FromResult(reply); 38 } 39 40 var principal = new ClaimsPrincipal(new ClaimsIdentity(new[] 41 { 42 new Claim(ClaimTypes.Name, token.UserId.ToString()) 43 }, "Basic")); 44 45 // authorize attribute 46 request.GetRequestContext().Principal = principal; 47 } 48 catch (Exception ex) 49 { 50 var reply = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex.Message); 51 return Task.FromResult(reply); 52 } 53 54 return base.SendAsync(request, cancellationToken); 55 } 56 } 57 }
实现也是很简单,通过继承 DelegatingHandler 重写 SendAsync 方法实现,整个流程需要的步骤如下:
1 登录,通过 api/auth 接收登录信息,验证后生成一个 token
2 每次请求判断 request.Headers.Authorization 参数,看是否携带 token (Http Client 将步骤 1 中的 token 设置到 request.Headers.Authorization)
3 解析 token,设置请求上下文的 Principal 用于 Authorize 属性使用
基本过程就差不多这三部曲,其中关于 token 的验证,如是否超时,是否重复,可自行想办法去实现
Web Api Config
大家都知道 Message Handler 在 pipeline 里是在 controller 之前运行,因此请求所有的 Api Controller 都会先执行 handler,因此针对登录,需要给予额外的照顾,允许匿名访问,实现方法:handler 可以是全局的,也可以是 per router 的,因此此处通过后一种方式实现:
1 using System.Linq; 2 using System.Net.Http.Formatting; 3 using System.Web.Http; 4 using System.Web.Http.Dispatcher; 5 using Newtonsoft.Json; 6 using Server.Helper; 7 8 namespace Server 9 { 10 public static class WebApiConfig 11 { 12 public static void Register(HttpConfiguration config) 13 { 14 config.MapHttpAttributeRoutes(); 15 16 config.Routes.MapHttpRoute( 17 name: "Authentication", 18 routeTemplate: "api/auth", 19 defaults: new {controller = "account"} 20 ); 21 22 config.Routes.MapHttpRoute( 23 name: "DefaultApi", 24 routeTemplate: "api/{controller}/{id}", 25 defaults: new {id = RouteParameter.Optional}, 26 constraints: null, 27 handler: new AuthenticationMessageHandler {InnerHandler = new HttpControllerDispatcher(config)} 28 ); 29 30 var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First(); 31 32 jsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; 33 jsonFormatter.SerializerSettings.ContractResolver = new NHibernateContractResolver(); 34 } 35 } 36 }
总结
最近匆匆忙忙使用托管在 Owin Self Host 上的 Web Api 2,遇到问题颇多,很多也是匆匆忙忙解决的,这里也就匆匆忙忙做一个分享。