mvc(7)——过滤器

版权声明:本文为博主原创文章,未经博主允许不得转载。本人观点或有不当之处,请在评论中及时指正,我会在第一时间内修改。 https://blog.csdn.net/aiming66/article/details/82083732

过滤器(Filter)把附加逻辑注入到MVC框架的请求处理。它们提供一种简单而雅致的方式,实现了交叉关注。所谓交叉关注(Cross-CuttingConcerns),是指可以用于整个应用程序,而又不适合放置在某个局部位置的功能,否则会打破关注分离模式。典型的交叉关注例子是登录、授权以及缓存等。今天我们来学习一下MVC框架所支持的不同类型的过滤器,如何创建和使用过滤器,以及如何控制它们的执行。

1、准备项目

我们通过使用“Empty(空)”模板,并检查选项以添加核心MVC文件夹和引用,创建了一个新的名称为Filters的MVC项目。在其中创建了Home控制器,它有一个动作方法,如下所示,今天我们只关注控制器,因此从动作方法中返回了字符串值,而不是ActionResult对象一一这使MVC框架可以绕过Razor视图引擎,而直接将字符串值发送给浏览器。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Filters.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public string  Index()
        {
            return "This is the Index action on the Home controller";
        }
    }
}

今天我们将演示如何使用认证过滤器这一MVC新特性,为此小编需要能够执行一些简单的用户认证。小编需要在web.config文件中定义静态用户凭据,如下所示:

 <system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5.2"/>
    <authentication mode="Forms">
      <forms loginUrl="~/Account/Login" timeout="2880">
        <credentials passwordFormat="Clear">
          <user name="user" password="secret"/>
          <user name="admin" password="secret"/>
        </credentials>
      </forms>
    </authentication>

  </system.web>

上面的代码中,我们定义了两个用户user和admin,并给它们分配相同的密码secret。再次使用forms
认证,并使用loginUrl属性来指定未认证的请求将被重定向到/Account/Login。
下面的代码中你可以看到添加到项目中的Account控制器的内容,它的Login动作将以默认的路由配置为目标。

using System.Web.Mvc;
using System.Web.Security;

namespace Filters.Controllers
{
    public class AccountController : Controller
    {
        // GET: Account
        public ActionResult Login()
        {
            return View();
        }
        [HttpPost]
        public ActionResult Login(string username, string password, string retrunurl)
        {
            bool result = FormsAuthentication.Authenticate(username,password);
            if (result)
            {
                FormsAuthentication.SetAuthCookie(username,false);
                return Redirect(retrunurl?? Url.Action("Index","Home"));//如果retrunurl不为空,则跳到retrunurl,否则跳到home控制器中的index
            }
            else
            {
                ModelState.AddModelError("","Incorrect username or password");
                return View();
            }
        }
    }
}

为了创建从用户那里收集候选凭证的视图,创建Views/Shared文件夹并右击它。选择”Add(添加)”一”MVC5ViewPage(Razor)(MVC5视图页(Razor))”,设置名称为”Login.cshtml”,并单击“确定(OK)”按钮以创建视图。编辑新视图内容如下:


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Login</title>
</head>
<body>
   @using (Html.BeginForm()) {
       @Html.ValidationSummary()
       <p><label>username:</label><input name="username"/></p>
       <p><label>password:</label><input name="password"/></p>
       <input type="submit" value="Log in"/>
   }
</body>
</html>

小编希望VisualStudio从应用程序的根URL开始启动,而不是基于正在编辑的文件猜测URL。从VisualStudio的’Project(项目)”菜单选择“Filters在“StartAction(开始动作)”小节检查”SpecificPage(特定页)”选项。你不需要提供值,仅检查选项就行了。如果启动示例应用程序,出现如下结果:
这里写图片描述

2、过滤器的类型

MVC框架支持5种不同类型的过滤器。每一种类型让你能够在请求处理的不同点上引入逻辑。

过滤器类型 接口 默认实现 描述
认证过滤器 IAuthenticationFilter N/A 最先运行,在任何其他过滤器或动作方法之前,但在授权过滤器之后可以再次运行
授权过滤器 IAuthorizationFiIter AuthorizeAttribute 在认证过后,其他过滤器或动作方法之前,第二个运行
动作过滤器 IActionFilter ActionFiIterAttribute 在动作方法之前及之后运行
结果过滤器 IResu1tFilter ActionFilterAttribute 在动作结果被执行之前和之后运行
异常过滤器 IExceptionFiIter HandleErrorAttribute 仅在另一个过滤器、动作方法或动作结果抛出异常时运行

在MVC框架调用一个动作之前,会首先检测该方法的定义,以查看它是否具有实现上面表格中所列接口的注解属性。如果有,那么便在请求处理程序的相应点上调用这些接口所定义的方法。框架包含了一些默认的注解属性类,它们实现了这些过滤器接口。

3、使用授权过滤器

授权过滤器在认证过滤器之后,其他过滤器和动作方法被调用之前运行。正如其名称的含义一样,这些过滤器执行你的授权策略,以确保动作方法只被己认证用户所调用。授权过滤器实现IAuthorizationFilter接口。

namespaceSystem.Web.MVC{
    public interface IAuthorizationFilter{
        void OnAuthorization(AuthorizationContext filterContext);
    }
}

3.1、使用内建授权过滤器

直接使用AuthorizeAttribute时,可以用这个类的两个public属性来指定授权策略。

名称 类型 描述
user string 一个逗号分隔的用户名列表,允许这些用户访问该动作方法
Roles string 一个逗号分隔的角色列表。为了访问该动作方法,用户必须至少是这些角色之一
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Filters.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        [Authorize(Users ="admin")]
        public string  Index()
        {
            return "This is the Index action on the Home controller";
        }
    }
}

小编己经指出授权admin用户调用Index动作方法,但这里还有一个隐含条件,即该请求己被认证。如果未指定任何用户或角色,那么任何己被认证的用户都可以使用这个动作方法。对于大多数应用程序,AuthorizeAttribute提供的授权策略己经足够。
启动程序的界面:
这里写图片描述
使用admin登录后的结果:
这里写图片描述
如果使用user用户登录,不会进入。

4、使用认证过滤器

认证过滤器是MVC第5版本的新特性,它对应用程序中的控制器和动作如何验证用户提供了细粒度的控制。
认证过滤器有一个相对复杂的生命周期。它们在其他过滤器之前运行,在其他类型的过滤器被使用之前,让你定义一个将要运用的认证策略。认证过滤器也可以结合授权过滤器对请求提出认证挑战,而该认证挑战又不遵循授权策略。认证过滤器也可以在一个动作方法执行之后,但在ActionResult被处理之前运行。

4.1理解IAuthenticationFilter接口

认证过滤器实现了IAuthenticationFilter接口

namespace System.Web.MVC.Filters
{
    public interface IAuthenticationFilter
    {
        void OnAuthentication(Authenticationcontext context);
        void OnAuthenticationCha11enge(AuthenticationChallengeContext context);
    }
}

无论对认证的请求或对动作方法授权策略的请求失败,MVC框架都调用OnAuthenticationChallenge方法,并给该方法传递一个AuthenticationChallengeContext对象,下表是AuthenticationChallengeContext
类的一些附加属性。

名称 描述
ActionDescriptor 返回描述动作方的ActionDescriptor,这些动作方法上运用了过滤器
Result 设置表示认证质疑结果的ActionResult

  上表中,最重要的属性是Result,因为它允许认证过滤器传递一个ActionResult给MVC框架,小编将简要描述短路这一过程。
  但是解释认证过滤器工作机制的最好方式是通过示例。在小编看来,认证过滤器最有趣的方面是,它们允许一个单一的控制器定义动作方法,而这些动作方法将以不同的方式进行验证,因此第一步是添加一个新的模拟Google登录的控制器。

//google登录控制器代码如下:
using System.Web.Mvc;
using System.Web.Security;

namespace Filters.Controllers
{
    public class GoogleAccountController : Controller
    {
        // GET: GoogleAccount
        public ActionResult Login()
        {
            return View();
        }
        [HttpPost]
        public ActionResult Login(string username,string password,string returnUrl)
        {
            if (username.EndsWith("@google.com") && password == "secret")
            {
                FormsAuthentication.SetAuthCookie(username,false);
                return Redirect(returnUrl ?? Url.Action("Index","Home"));
            }
            else
            {
                ModelState.AddModelError("","Incorrect username or password");
                return View();
            }
        }
    }
}

  因为这仅仅是一个示例,为此小编建立了一个可怕的攻击,只要提供密码secret,它将认证所有以google.com结尾的用户名。
  此刻,小编的认证控制器没有连接应用程序,而是引入了认证过滤器。在Infrastructure文件夹中,小编创建了一个新的类文件,名称为GoogleAuthAttribute.cs,代码如下,在如下代码中FilterAttribute类(GoogleAuth过滤器派生自它)是所有类的基础。

using System.Web.Mvc.Filters;
using System.Web.Mvc;
using System.Web.Routing;

namespace Filters.Infrastructure
{
    public class GoogleAuthAttribute:FilterAttribute,IAuthenticationFilter
    {
        public void OnAuthentication(AuthenticationContext context)
        {
            //未实现
        }
        public void OnAuthenticationChallenge(AuthenticationChallengeContext context)
        {
            if (context.Result==null)
            {
                context.Result = new RedirectToRouteResult(
                    new RouteValueDictionary {
                        { "controller","GoogleAccount" },
                        { "action","Login"},
                        { "returnUrl",context.HttpContext.Request.RawUrl}
                    }
                    );
            }
        }
    }
}

OnAuthenticationChallenge方法的实现检查AuthenticationChallengeContext参数的Result属性是否己经设置。当动作方法执行后运行过滤器时,这让我们避免质疑用户。不用担心,我们稍后在处理中将解释为什么这点很重要。
在这一部分最重要的是,通过重定向用户浏览器到带有RedirectToRouteResuIt的GoogleAccount控制器,使用OnAuthenticationChallenge方法来检测用户的凭据。

4.2 实现认证检查

  认证过滤器己经准备好对用户伪造的Google凭据提出挑战,现在可以接上剩余的行为。在运行其他类型的过滤器之前,控制器将调用OnAuthentication方法,以提供机会执行更大范围的认证检查。你不需要实现OnAuthentication方法,但小编打算这样做是为了检查自己正在处理一个Google账号。
  像AuthenticationChallengeContext类一样,OnAuthentication方法被传递了一个AuthenticationContext对象,该对象派生于Controllerconte。
Authenticationcontext类定义的属性如下表所示:

名称 描述
ActionDescriptor 返回一个描述动作方法的Actionoescriptor,这些动作方法已经运用了过滤器
Principal 如果用户己经被认证,返回一个识别当前用户的Iprincipal实现
Result 设置一个表示认证检查结果的ActionResult

  如果OnAuthentication为上下文对象的Result属性设置了一个值,那么MVC框架将调用OnAuthenticationChallenge方法。如果未设置,那么OnAuthentication中的一个方法将被执行。使用OnAuthentication方法创建一个报告用户凭据错误的结果,它可以被OnAuthenticationChallenge方法重载以便对用户凭据提出质疑。这让小编确信,他们看到了一个有意义的响应,即使没有发出挑战(但是小编必须承认己经碰到了这种情况)。在下面的代码中,你可以看到如何实现OnAuthentication方法,以便检查通过使用Google证书请求已经被认证。

using System.Web.Mvc.Filters;
using System.Web.Mvc;
using System.Web.Routing;
using System.Security.Principal;//新增代码

namespace Filters.Infrastructure
{
    public class GoogleAuthAttribute:FilterAttribute,IAuthenticationFilter
    {
        public void OnAuthentication(AuthenticationContext context)
        {   //新增
            IIdentity ident = context.Principal.Identity;
            if (!ident.IsAuthenticated || !ident.Name.EndsWith("@google.com"))
            {
                context.Result = new HttpUnauthorizedResult();
            }
        }
        public void OnAuthenticationChallenge(AuthenticationChallengeContext context)
        {
            if (context.Result==null || context.Result is HttpUnauthorizedResult)//修改代码
            {
                context.Result = new RedirectToRouteResult(
                    new RouteValueDictionary {
                        { "controller","GoogleAccount" },
                        { "action","Login"},
                        { "returnUrl",context.HttpContext.Request.RawUrl}
                    }
                    );
            }
        }
    }
}

  OnAuthentication方法的实现检查使用以@google.com结尾的用户名的请求是否己经被认证。如请求未被认证,或使用不同种类的凭据认证,那么小编将把AuthenticationContext对象的Result属性设置成一个新的HttpUnauthorizedResult。
  HttpUnauthorizedResult被设置为传递给OnAuthenticationChallenge方法的
AuthenticationChallengeContext对象的Result值,你可以看到,小编己经更新了此方法以对用户提出申请,当这一切发生的时候,协调过滤器中两个方法的动作。下一步是将过滤器运用于home控制器,代码如下:

using System.Web.Mvc;
using Filters.Infrastructure;//需要引用

namespace Filters.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        [Authorize(Users ="admin")]
        public string  Index()
        {
            return "This is the Index action on the Home controller";
        }
        [GoogleAuth] //新增
        public string List() //新增
        {
            return "This is the list action on the Home controller";
        }
    }
}

  小编己经定义了一个新的动作方法,名称为“List”,该方法用GoogleAuth过滤器修饰。结果是,通过内置支持表单认证对Index方法的访问是安全的,而且通过自定义伪造Google认证系统对List动作的访问也是安全的。
  启动应用程序可以看到效果。默认情况下,浏览器将以Index动作方法为目标,这将触发标准认证,并要求你使用在web.config文件中定义的用户名登录。
  如果你请求/Home/List,那么你现有的凭据将被拒绝,你将不得不使用Google的用户名认证。
启动程序:
这里写图片描述

输入账号密码
这里写图片描述
运行结果:
这里写图片描述
如果直接输入/Home/List
当输入网址后,回车后会跳到account/login 的等界面。
如果要是输入谷歌账号:
这里写图片描述
结果如下:
这里写图片描述

4.3、组合认证和授权过滤器

其实,我们可以在同一个动作方法组合认证和授权过滤器,以缩小安全策略的范围。MVC框架将调用认证过滤器的OnAuthentication方法,正如前面的示例,如果请求通过了认证检查,那么转去运行授权过滤器。如果请求没有传递给授权过滤器,那么将调用认证过滤器的OnAuthenticationChallenge方法,这样就可以对请求凭据的用户提出挑战。在接下来的代码中,你可以看到小编组合了GoogleAuth和Authorize过滤器以限制对Home控制器中List动作的访问。

using System.Web.Mvc;
using Filters.Infrastructure;

namespace Filters.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        [Authorize(Users ="admin")]
        public string  Index()
        {
            return "This is the Index action on the Home controller";
        }
        [GoogleAuth]
        [Authorize(Users ="[email protected]")]//新增
        public string List()
        {
            return "This is the list action on the Home controller";
        }
    }
}

  Authorize过滤器限制访问[email protected]账号。如果另一个Google账号以该动作方法为目标,那么认证过滤器OnAuthenticationCha11enge方法将被传递一个AuthenticationChallengecontext对象,该对象的Result属性被设置成HttpUnauthorizedResu1t类的实例(这是小编为什么在OnAuthentication方法中使用相同类的原因)。
  对使用AccountController认证的用户admin,Home控制器中的过滤器限制访问Index方法,而对通过GoogleAccount控制器认证的bogoogle跹om用户,限制访问List方法。
启动程序:
这里写图片描述
输入账号admin 密码:secret后的结果:
这里写图片描述
导航到/home/list,会自动跳转到认证页面: 输入账号:[email protected] 密码secret后的结果:
这里写图片描述
结果:无法访问。


在此网址,输入账号:[email protected] 密码:secret 结果:
这里写图片描述
这里写图片描述

程序源码:https://download.csdn.net/download/aiming66/10633374

猜你喜欢

转载自blog.csdn.net/aiming66/article/details/82083732
今日推荐