WEB API 系列(二) Filter的使用以及执行顺序

WebAPI Filter详解
本文介绍了WebAPI中的Filter机制,包括权限验证、模型校验及异常处理等Filter的开发与使用方法,并探讨了它们的执行顺序。

 在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理。引入了这一机制可以更好地践行DRY(Don’t Repeat Yourself)思想,通过Filter能统一地对一些通用逻辑进行处理,如:权限校验、参数加解密、参数校验等方面我们都可以利用这一特性进行统一处理,今天我们来介绍Filter的开发、使用以及讨论他们的执行顺序。

一、Filter的开发和调用

         在默认的WebApi中,框架提供了三种Filter,他们的功能和运行条件如下表所示:

Filter 类型

实现的接口

描述

Authorization

IAuthorizationFilter

最先运行的Filter,被用作请求权限校验

Action

IActionFilter

Action运行的前、后运行

Exception

IExceptionFilter

当异常发生的时候运行

       首先,我们实现一个AuthorizatoinFilter可以用以简单的权限控制:

    public class AuthFilterAttribute : AuthorizationFilterAttribute
    {        public override void OnAuthorization(HttpActionContext actionContext)
        {            //如果用户方位的Action带有AllowAnonymousAttribute,则不进行授权验证
            if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
            {                return;
            }            var verifyResult = actionContext.Request.Headers.Authorization!=null &&  //要求请求中需要带有Authorization头
                               actionContext.Request.Headers.Authorization.Parameter == "123456"; //并且Authorization参数为123456则验证通过

            if (!verifyResult)
            {                //如果验证不通过,则返回401错误,并且Body中写入错误原因
                actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized,new HttpError("Token 不正确"));
            }
        }
   }

    一个简单的用于用户验证的Filter就开发完了,这个Filter要求用户的请求中带有Authorization头并且参数为123456,如果通过则放行,不通过则返回401错误,并在Content中提示Token不正确。下面,我们需要注册这个Filter,注册Filter有三种方法:

第一种:在我们希望进行权限控制的Action上打上AuthFilterAttribute这个Attribute:

    public class PersonController : ApiController
    {
        [AuthFilter]        public CreateResult Post(CreateUser user)
        {            return new CreateResult() {Id = "123"};
        }
    }

这种方式适合单个Action的权限控制。

第二种,找到相应的Controller,并打上这个Attribute:

    [AuthFilter]    
   public class PersonController : ApiController    {        public CreateResult Post(CreateUser user)        {            return new CreateResult() {Id = "123"};        }    }

这种方式适合于控制整个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 = "123456"}; }

我为这个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 = "123456"};
}

当然,你也可以根据自己的需要解析ModelState然后用自己的格式将错误信息通过Request.CreateResponse()返回给用户。

  OnActionExecuted方法我在实际工作中使用得较少,目前仅在一次部分响应数据加密的场景下进行过使用,使用方法一样,读取已有的响应,并加密后再给出加密后的响应赋值给actionContext.Response即可。

我给大家一个Demo: 

public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{        var key = 10;        var responseBody = await actionExecutedContext.Response.Content.ReadAsByteArrayAsync(); //以Byte数组方式读取Content中的数据

        for (int i = 0; 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来进行处理,在之后的教程中我会进行介绍。

    最后一个Filter: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相关知识我们就讲这么一些,如果在文章中有任何不正确的地方或者疑问,欢迎大家为我指出。


相关文章:


原文地址:http://www.cnblogs.com/UliiAn/p/5402146.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注


### Java Filter 链中执行顺序的控制方法 在Java Web开发中,`Filter` 是一种用于拦截请求响应的技术。为了实现复杂的业务逻辑处理,通常会将多个 `Filter` 组合成一条链(称为 `Filter Chain`)。这些 `Filter` 的执行顺序可以通过多种方式加以控制。 #### 方法一:基于 `web.xml` 文件配置 当使用传统的 XML 配置文件时,`Filter` 的执行顺序由其在 `web.xml` 中定义的顺序决定。位于上方的 `filter-mapping` 定义会被优先加载并执行[^5]。例如: ```xml <filter> <filter-name>UserLoginFilter</filter-name> <filter-class>net.tfgzs.demo.filter.UserLoginFilter</filter-class> </filter> <filter-mapping> <filter-name>UserLoginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>ApiLog</filter-name> <filter-class>net.tfgzs.demo.filter.ApiLog</filter-class> </filter> <filter-mapping> <filter-name>ApiLog</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` 在此示例中,“用户登录检查过滤器”会在“接口日志过滤器”之前执行[^5]。 --- #### 方法:基于注解 `@WebFilter` 自 Servlet 3.0 开始引入了注解支持,开发者可以利用 `@WebFilter` 注解替代传统 XML 配置。此时,`Filter` 的执行顺序可通过 `dispatcherTypes` 或者显式的 URL 映射来间接影响[^4]。然而,默认情况下,容器不会严格保证注解形式下的顺序一致性。因此,在实际项目中推荐配合其他机制(如框架工具)进一步明确顺序关系。 如果确实需要指定严格的顺序,则可以在某些高级框架(比如 Spring Boot)中借助额外功能完成定制化管理[^4]。 --- #### 方法三:职责链模式的应用 Tomcat 提供了一个名为 `ApplicationFilterFactory.createFilterChain()` 的内部方法,它负责动态构建完整的 `Filter Chain` 并确保各节点之间的连接性良好[^1]。具体而言,每个 `Filter` 实现都需要调用参数传入的对象——即下一个环节所对应的 `FilterChain.chain.doFilter(...)` 来触发后续操作[^2]。 以下是典型代码片段展示如何编写单个 `Filter` 类及其行为表现: ```java package com.Higgin.web.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class FilterTest01 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("FilterTest01 执行前---"); chain.doFilter(request, response); // 让目标资源继续运行 System.out.println("FilterTest01 执行后---"); } @Override public void init(FilterConfig config) throws ServletException {} @Override public void destroy() {} } ``` 此例子展示了标准做法下前后端交互过程中的预处理与后处理阶段分别打印消息[^3]。 --- #### 最佳实践建议 综合以上几种方案考虑最佳实践时应遵循以下原则: - **清晰可读**:无论采用何种手段调整次序,务必保持源码结构直观易懂; - **灵活性高**:对于大型系统来说可能涉及频繁变更需求,故而应当倾向于更灵活的方式而非硬编码固定下来; - **兼容性强**:考虑到不同版本间可能存在差异性问题,选用广泛接受的标准技术路线往往更为稳妥可靠; 最终选择取决于团队偏好以及现有架构特点等因素共同作用的结果。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值