Web API系列(二) Filter的使用以及执行顺序
在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理。引入了这一机制可以更好地践行DRY(Don’t Repeat Yourself)思想,通过Filter能统一地对一些通用逻辑进行处理,如:权限校验、参数加解密、参数校验、日志记录、异常处理、性能测量等方面我们都可以利用这一特性进行统一处理,今天我们来介绍Filter的开发、使用以及讨论他们的执行顺序。
一、Filter的开发和调用
在默认的WebApi中,框架提供了三种Filter,他们的功能和运行条件如下表所示:
Filter 类型 |
实现的接口 |
描述 |
Authorization |
IAuthorizationFilter |
最先运行的Filter,被用作请求权限校验 |
Action |
IActionFilter |
在Action运行的前、后运行 |
Exception |
IExceptionFilter |
当异常发生的时候运行 |
AuthorizatoinFilte
首先,我们实现一个AuthorizatoinFilter可以用以简单的权限控制(Basic Auth):
/// <summary>
/// 权限控制 特性
/// </summary>
public class AuthFilterAttribute : AuthorizationFilterAttribute
{
/// <summary>
/// 在过程请求授权时调用。
/// </summary>
/// <param name="actionContext"></param>
public override void OnAuthorization(HttpActionContext actionContext)
{
//如果用户方位的Action带有AllowAnonymousAttribute,则不进行授权验证
if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
{
return;
}
//要求请求中需要带有Authorization头,并且Authorization参数为123456则验证通过
if (actionContext.Request.Headers.Authorization != null)
{
string param = actionContext.Request.Headers.Authorization.Parameter;
string schme = actionContext.Request.Headers.Authorization.Scheme.ToLower();
bool verifyResult = false;
switch (schme)
{
//case AuthenticationSchemes.Basic.ToString():
case "basic":
verifyResult = Validate(param);
break;
default:
break;
} if (!verifyResult)
{
//如果验证不通过,则返回401错误,并且Body中写入错误原因
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("Token 不正确"));
}
} } /// <summary>
/// Basic验证,每次请求API时都提供用户的username和password
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
private bool Validate(string token)
{
bool result = false;
byte[] c = Convert.FromBase64String(token);
string baseString = System.Text.Encoding.Default.GetString(c);
string[] up = baseString.Split(':');
if (up.Length >= )
{
result = up[].ToLower() == "admin" && up[].ToLower() == "";
}
return result;
}
}
一个简单的用于用户验证的Filter就开发完了,这个Filter要求用户的请求中带有Authorization头并且Basic模式,admin:123456,如果通过则放行,不通过则返回401错误,并在Content中提示Token不正确。
Basic Auth 就是在请求的时候在headers中设置 Authorization: "Basic 用户名和密码的base64加密字符串"
用户名和密码之间用:冒号隔开。
在postman中可以测试下。
下面,我们需要注册这个Filter,注册Filter有三种方法:
第一种:在我们希望进行权限控制的Action上打上AuthFilterAttribute这个Attribute:
public class PersonController : ApiController
{
[AuthFilter]
public CreateResult Post(CreateUser user)
{
return new CreateResult() {Id = ""};
}
}
这种方式适合单个Action的权限控制。
第二种,找到相应的Controller,并打上这个Attribute:
[AuthFilter]
public class PersonController : ApiController
{
public CreateResult Post(CreateUser user)
{
return new CreateResult() {Id = ""};
}
}
这种方式适合于控制整个Controller,打上这个Attribute以后,整个Controller里所有Action都获得了权限控制。
第三种,找到App_Start\WebApiConfig.cs,在Register方法下加入Filter实例:
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
//注册全局Filter
config.Filters.Add(new AuthFilterAttribute()); config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
用这种方式适合于控制所有的API,任意Controller和任意Action都接受了这个权限控制。
总结:
在大多数场景中,每个API的权限验证逻辑都是一样的,在这样的前提下使用全局注册Filter的方法最为简单便捷,可这样存在一个显而易见的问题:如果某几个API是不需要控制的(例如登录)怎么办?我们可以在这样的API上做这样的处理:
[AllowAnonymous]
public CreateResult PostLogin(LoginEntity entity)
{
//TODO:添加验证逻辑
return new CreateResult() {Id = ""};
}
我为这个Action打上了AllowAnonymousAttribute,验证逻辑就放过了这个API而不进行权限校验。
在实际的开发中,我们可以设计一套类似Session的机制,通过用户登录来获取Token,在之后的交互HTTP请求中加上Authorization头并带上这个Token,并在自定义的AuthFilterAttribute中对Token进行验证,一套标准的Token验证流程就可以实现了。
ActionFilter
ActionFilterAttrubute提供了两个方法进行拦截:OnActionExecuting和OnActionExecuted,他们都提供了同步和异步的方法。
OnActionExecuting方法在Action执行之前执行,OnActionExecuted方法在Action执行完成之后执行。
我们来看一个应用场景:使用过MVC的同学一定不陌生MVC的模型绑定和模型校验,使用起来非常方便,定义好Entity之后,在需要进行校验的地方可以打上相应的Attribute,在Action开始时检查ModelState的IsValid属性,如果校验不通过直接返回View,前端可以解析并显示未通过校验的原因。而Web API中也继承了这一方便的特性,使用起来更加方便:
public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
这个Filter就提供了模型校验的功能,如果未通过模型校验则返回400错误,并把相关的错误信息交给调用者。他的使用方法和AuthFilterAttribute一样,可以针对Action、Controller、全局使用。我们可以用下面一个例子来验证:
代码如下:
public class LoginEntity
{
[Required(ErrorMessage = "缺少用户名")]
public string UserName { get; set; } [Required(ErrorMessage = "缺少密码")]
public string Password { get; set; }
}
[AllowAnonymous]
[CustomActionFilter]
public CreateResult PostLogin(LoginEntity entity)
{
//TODO:添加验证逻辑
return new CreateResult() {Id = ""};
}
当然,你也可以根据自己的需要解析ModelState然后用自己的格式将错误信息通过Request.CreateResponse()返回给用户。
OnActionExecuted方法【对某个Action的处理后的结果进行处理】我在实际工作中使用得较少,目前仅在一次部分响应数据加密的场景下进行过使用,使用方法一样,读取已有的响应,并加密后再给出加密后的响应赋值给actionContext.Response即可。
我给大家一个Demo:
public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
var key = ; var responseBody = await actionExecutedContext.Response.Content.ReadAsByteArrayAsync(); //以Byte数组方式读取Content中的数据 for (int i = ; i < responseBody.Length; i++)
{
responseBody[i] = (byte)(responseBody[i] ^ key); //对每一个Byte做异或运算
} actionExecutedContext.Response.Content = new ByteArrayContent(responseBody); //将结果赋值给Response的Content actionExecutedContext.Response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("Encrypt/Bytes"); //并修改Content-Type
}
通过这个方法我们将响应的Content每个Byte都做了一个异或运算,对响应内容进行了一次简单的加密,大家可以根据自己的需要进行更可靠的加密,如AES、DES或者RSA…通过这个方法可以灵活地对某个Action的处理后的结果进行处理,通过Filter进行响应内容加密有很强的灵活性和通用性,他能获取当前Action的很多信息,然后根据这些信息选择加密的方式、获取加密所需的参数等等。如果加密所使用参数对当前执行的Action没有依赖,也可以采取HttpMessageHandler来进行处理,在之后的教程中我会进行介绍。
ExceptionFilter
顾名思义,这个Filter是用来进行异常处理的,当业务发生未处理的异常,我们是不希望用户接收到黄页或者其他用户无法解析的信息的,我们可以使用ExceptionFilter来进行统一处理:
public class ExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
//如果截获异常为我们自定义,可以处理的异常则通过我们自己的规则处理
if (actionExecutedContext.Exception is DemoException)
{
//TODO:记录日志
actionExecutedContext.Response = actionExecutedContext.Request.CreateResponse(
HttpStatusCode.BadRequest, new {Message = actionExecutedContext.Exception.Message});
}
else
{
//如果截获异常是我没无法预料的异常,则将通用的返回信息返回给用户,避免泄露过多信息,也便于用户处理 //TODO:记录日志
actionExecutedContext.Response =
actionExecutedContext.Request.CreateResponse(HttpStatusCode.InternalServerError,
new {Message = "服务器被外星人拐跑了!"});
}
}
}
我们定义了一个ExceptoinFilter用于处理未捕获的异常,我们将异常分为两类:一类是我们可以预料的异常:如业务参数错误,越权等业务异常;还有一类是我们无法预料的异常:如数据库连接断开、内存溢出等异常。我们通过HTTP Code告知调用者以及用相对固定、友好的数据结构将异常信息告诉调用者,以便于调用者记录并处理这样的异常。
[CustomerExceptionFilter]
public class TestController : ApiController
{
public int Get(int a, int b)
{
if (a < b)
{
throw new DemoException("A必须要比B大!");
}
if (a == b)
{
throw new NotImplementedException();
}
return a*b;
}
}
我们定义了一个Action:在不同的情况下会抛出不同的异常,其中一个异常是我们能够预料并认为是调用者传参出错的,一个是不能够处理的,我们看一下结果:
在这样的RestApi中,我们可以预先定义好异常的表现形式,让调用者可以方便地判断什么情况下是出现异常了,然后通过较为统一的异常信息返回方式让调用者方便地解析异常信息,形成统一方便的异常消息处理机制。
但是,ExceptionFilter只能在成功完成了Controller的初始化以后才能起到捕获、处理异常的作用,而在Controller初始化完成之前(例如在Controller的构造函数中出现了异常)则ExceptionFilter无能为力。对此WebApi引入了ExceptionLogger和ExceptionHandler处理机制,我们将在之后的文章中进行讲解。
二、Filter的执行顺序
在使用MVC的时候,ActionFilter提供了一个Order属性,用户可以根据这个属性控制Filter的调用顺序,而Web API却不再支持该属性。Web API的Filter有自己的一套调用顺序规则:
所有Filter根据注册位置的不同拥有三种作用域:Global、Controller、Action:
通过HttpConfiguration类实例下Filters.Add()方法注册的Filter(一般在App_Start\WebApiConfig.cs文件中的Register方法中设置)就属于Global作用域;
通过Controller上打的Attribute进行注册的Filter就属于Controller作用域;
通过Action上打的Attribute进行注册的Filter就属于Action作用域;
他们遵循了以下规则:
1、在同一作用域下,AuthorizationFilter最先执行,之后执行ActionFilter
2、对于AuthorizationFilter和ActionFilter.OnActionExcuting来说,如果一个请求的生命周期中有多个Filter的话,执行顺序都是Global->Controller->Action;
3、对于ActionFilter,OnActionExecuting总是先于OnActionExecuted执行;
4、对于ExceptionFilter和ActionFilter.OnActionExcuted而言执行顺序为Action->Controller->Global;
5、对于所有Filter来说,如果阻止了请求:即对Response进行了赋值,则后续的Filter不再执行。
关于默认情况下的Filter相关知识我们就讲这么一些,如果在文章中有任何不正确的地方或者疑问,欢迎大家为我指出。
Web API系列(二) Filter的使用以及执行顺序的更多相关文章
- Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群
Redis总结(五)缓存雪崩和缓存穿透等问题 前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...
- Web API系列(二)接口安全和参数校验
以前简单介绍过web api 的设计,但是还是有很多朋友问我,如何合理的设计和实现web api.比如,接口安全,异常处理,统一数据返回等问题.所以有必要系统的总结总结 web api 的设计和实现. ...
- Web API系列(三)统一异常处理
前面讲了webapi的安全验证和参数安全,不清楚的朋友,可以看看前面的文章,<Web API系列(二)接口安全和参数校验>,本文主要介绍Web API异常结果的处理.作为内部或者是对外提供 ...
- Web API系列之三 基本功能实现
Web API系列之二讲解了如何搭建一个WebApi的基架,本文主要在其基础之上实现基本的功能.下面开始逐步操作: 一.配置WebApi的路由-用于配置外部如何访问内部资源的url的规则 1.添加Gl ...
- ASP.NET Web API系列教程目录
ASP.NET Web API系列教程目录 Introduction:What's This New Web API?引子:新的Web API是什么? Chapter 1: Getting Start ...
- 【转】ASP.NET WEB API系列教程
from: 西瓜小强 http://www.cnblogs.com/risk/category/406988.html ASP.NET Web API教程(六) 安全与身份认证 摘要: 在实际的项目应 ...
- ASP.NET Web API系列教程(目录)(转)
注:微软随ASP.NET MVC 4一起还发布了一个框架,叫做ASP.NET Web API.这是一个用来在.NET平台上建立HTTP服务的Web API框架,是微软的又一项令人振奋的技术.目前,国内 ...
- [转]ASP.NET Web API系列教程(目录)
本文转自:http://www.cnblogs.com/r01cn/archive/2012/11/11/2765432.html 注:微软随ASP.NET MVC 4一起还发布了一个框架,叫做ASP ...
- {MySQL的逻辑查询语句的执行顺序}一 SELECT语句关键字的定义顺序 二 SELECT语句关键字的执行顺序 三 准备表和数据 四 准备SQL逻辑查询测试语句 五 执行顺序分析
MySQL的逻辑查询语句的执行顺序 阅读目录 一 SELECT语句关键字的定义顺序 二 SELECT语句关键字的执行顺序 三 准备表和数据 四 准备SQL逻辑查询测试语句 五 执行顺序分析 一 SEL ...
- 【ABAP系列】SAP ABAP的事件执行顺序
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[ABAP系列]SAP ABAP的事件执行顺序 ...
随机推荐
- 同一个Tomcat部署多个springboot项目问题
2018-12-13 10:37:21,412 ERROR [localhost-startStop-2] c.a.d.s.DruidDataSourceStatManager [DruidDataS ...
- RESTful 的学习总结
RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构.比如,GET /articles这个命令,GET是动词,/articles是宾语.动词通常就是 ...
- chocolatey install curl and netcat
chocolatey install curl and netcat 软件仓库 https://chocolatey.org/packages choco install curl choco ins ...
- Java中关于Math的几个取整方法的区别
1.Math.ceil() 向上取整 System.out.println(Math.ceil(3.4)); //输出4 System.out.println(Math.ceil(3.7)); / ...
- windows下安装hexo和生成博客
首先在电脑上安装node和git,这个只要在相关官网的下载然后一步安装即可. 然后在你的电脑上新建一个文件夹,用来存放你的博客文件,比如创建hexo 进入该文件,右键打开git bash 安装hexo ...
- Java 之 Servlet 基础入门
Servlet 一.什么是 Servlet 1.概念 Servlet:server applet,是指运行在服务器端的小程序 2.Servlet servlet 就是一个接口,定义了 Java 类 ...
- Qt Table Widget常用操作
一.鼠标悬浮在item上 显示提示信息 1.在构造函数开启table Widget控件的鼠标捕获功能 // 开启鼠标捕获功能(实现table widget的悬浮功能) ui.tableWidget-& ...
- MySQL中使用函数时,与后面括号不能之间不能根空格
修改前代码: select MAX (article_order) from mall_school_article where 1=1 and is_deleted = 0 and status = ...
- Jquery简单闭包
<html> <body> <script src="Js/Index.js"></script> <script type= ...
- altium designer的pcb板如何移动到原点?
可以把所有的都选中,然后将光标移到起点处,将所有的移到原点的地方,但这种做法很多时候都不好:比较好的办法就是将原点设置到起点上来. 具体做法是:edit--origin --set. 这时光标成了十字 ...