[ASP.NET MVC] 利用自定义的AuthenticationFilter实现Basic认证

 

很多情况下目标Action方法都要求在一个安全上下文中被执行,这里所谓的安全上下文主要指的是当前请求者是一个经过授权的用户。授权的本质就是让用户在他许可的权限范围内做他能够做的事情,授权的前提是请求者是一个经过认证的用户。质询-应答(Chanllenge-Response)”是用户认证采用的一种常用的形式,认证方向被认证方发出质询以要求其提供用于实施认证的用户凭证,而被认证方提供相应的凭证以作为对质询的应答。旨在目标Action方法执行之前实施身分认证的AuthenticationFilter也对这种认证方法提供了支持。

一、IAuthenticationFilter接口

所有的AuthenticationFilter类型均实现了IAuthenticationFilter接口,该接口定义在命名空间“System.Web.Mvc.Filters”下(其他四种过滤器接口都定义在“System.Web.Mvc”命名空间下)。如下面的代码片断所示,OnAuthentication和OnAuthenticationChallenge这两个方法被定义在此接口中,前者用于对请求实施认证,后者则负责将相应的认证质询发送给请求者。

   1: public interface IAuthenticationFilter
   2: {
   3:     void OnAuthentication(AuthenticationContext filterContext);
   4:     void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext);
   5: }

定义在IAuthenticationFilter接口的两个方法都将一个上下文对象作为其唯一参数。OnAuthentication方法的这个参数类型为AuthenticationContext,如下面的代码片断所示,它是ControllerContext的子类。AuthenticationContext的ActionDescriptor返回的自然是用于描述目标Action方法的ActionDescriptor对象。借助于Principal属性,我们可以获取或设置代表当前用户的Principal对象。如果我们在执行OnAuthentication方法的过程中设置了AuthenticationContext的Result属性,提供的ActionResult将直接用于响应当前请求。

   1: public class ActionExecutingContext : ControllerContext
   2: {    
   3:     public ActionExecutingContext();
   4:     public ActionExecutingContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor,IDictionary<string, object> actionParameters);
   5:  
   6:     public virtual ActionDescriptor             ActionDescriptor { get; set; }
   7:     public virtual IDictionary<string, object>  ActionParameters { get; set; }
   8:     public ActionResult                         Result { get; set; }
   9: }

OnAuthenticationChallenge方法的参数类型为AuthenticationChallengeContext。如下面的代码片断所示,它依然是ControllerContext的子类。它同样具有一个用于描述目标Action方法的ActionDescriptor属性,其Result属性代表的ActionResult对象将用于响应当前请求。

   1: public class ActionExecutedContext : ControllerContext
   2: {    
   3:     public ActionExecutedContext();   
   4:     public ActionExecutedContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, bool canceled, Exception exception);
   5:  
   6:     public virtual ActionDescriptor     ActionDescriptor { get; set; }
   7:     public virtual bool                 Canceled { get; set; }
   8:     public virtual Exception            Exception { get; set; }
   9:     public bool                         ExceptionHandled { get; set; }
  10:     public ActionResult                 Result { get; set; }
  11: }

二、AuthenticationFilter的执行流程

我们知道身份认证总是对请求处理的第一个步骤,因为只有确定了请求者的真实身份,安全才能得到保障,所以AuthenticationFilter是最先被执行的一类过滤器。所有过滤器的执行都是ActionInvoker来驱动的,ASP.NET MVC在默认情况下采用的ActionInvoker是一个AsyncControllerActionInvoker对象,后者类型派生于ControllerActionInvoker。ControllerActionInvoker针对AuthenticationFilter的执行体现在如下两个方法(InvokeAuthenticationFilters和InvokeAuthenticationFiltersChallenge)上。

   1: public class ControllerActionInvoker : IActionInvoker
   2: {
   3:     //其他成员
   4:     protected virtual AuthenticationContext InvokeAuthenticationFilters(ControllerContext controllerContext,IList<IAuthenticationFilter> filters, ActionDescriptor actionDescriptor);
   5:     protected virtual AuthenticationChallengeContext InvokeAuthenticationFiltersChallenge(ControllerContext controllerContext, IList<IAuthenticationFilter> filters, ActionDescriptor actionDescriptor, ActionResult result);   
   6: }

如果多个AuthenticationFilter同时被应用到目标Action方法上,ControllerActionInvoker会根据对应Filter的Order/Scope属性对它们进行排序。随后ControllerActionInvoker会根据当前ControllerContext、描述目标Action方法的ActionDescriptor对象以及原始的Principal(对应于当前HttpContext的User属性)创建一个AuthenticationContext对象,并以此作为参数以此调用每个AuthenticationFilter对象的OnAuthentication对象实施认证。

在目标Action方法被执行之后,通过本书第11章“View的呈现”我们知道最终执行的结果会被封装为一个ActionResult对象。ControllerActionInvoker会利用当前ControllerContext、描述目标Action方法的ActionDescriptor对象和这个ActionResult创建一个AuthenticationChallengeContext对象,并将其作为参数依次调用每个AuthenticationFilter的OnAuthenticationChallenge方法。这个AuthenticationChallengeContext对象的Result属性最终返回的ActionResult对象将被用来对请求予以响应。

右图基本反映了整个“AuthenticationFilter链”的执行流程,但是如果在执行某个AuthenticationFilter对象的OnAuthenticatio方法时对作为参数的AuthenticationContext对象的Result属性作了相应的设置,针对整个“AuthenticationFilter链”的执行将会立即中止,指定的这个ActionResult对象将用于响应当前请求。如果在执行过程中对AuthenticationContext对象的Principal属性作了相应的设置,该属性值将会作为当前HttpContext和当前线程的Principal。

三、实例演示:通过自定义AuthenticationFilter实现Basic认证

在ASP.NET MVC的应用编程接口中,我们找不到IAuthenticationFilter接口的实现者。为了让大家对这个在ASP.NET MVC 5才引入的过滤器具有更加深刻的认识,我们接下来会通过一个实例来演示如何通过自定义的AuthenticationFilter实现针对Basic方案的认证。不过在这之前,我们有必要对Basic这种基本的认证方法作一个基本的了解。Basic和Digest是两种典型的HTTP认证方案。对于前者,虽然客户端提供的认证凭证(用户名+密码)仅仅是被Base64编码而没有被加密,但是我们可以通过采用HTTPS传输利用SSL来解决机密性的问题,所以Basic认证也不失为一种不错的认证方案。左图体现了Basic认证的基本流程,可以看出这也是一种典型的采用“质询-应答”模式的认证方案,整个流程包含如下两个基本步骤。

  • 客户端向服务端发送一个HTTP请求,服务端返回一个状态为“401, Unauthorized”的响应。该响应具有一个“WWW-Authenticate”的报头标明采用的是Basic认证方案。Basic认证是在一个“领域(Realm)”限定的上下文中进行的,该报头还可以执行认证的领域,左图所示的WWW-Authenticate报头值为:Basic realm="localhost"。
  • · 客户端向服务端发送一个携带基于用户名/密码的认证凭证的请求。认证凭证的格式为“{UserName}:{Password}”,并采用Base64编码(编码的目的不是为了保护提供的密码)。这样一个经过编码的认证凭证被存放在请求报头Authorization中,相应的认证方案类型(Basic)依然需要在该报头中指定,左图所示的Authorization报头值为:Basic YcdfaYsss==。服务端接收到请求之后,从Authorization报头中提取凭证并对其进行解码,最后采用提取的用户名和密码实施认证。认证成功之后,该请求会得到正常的处理,并回复一个正常的响应。

在正式介绍如果定义这个实现Basic认证的AuthenticationFilter之前,我们不妨先来看看使用了这个自定义AuthenticationFilter会产生怎样的效果。我们在一个ASP.NET MVC应用中定义了如下一个HomeController,定义其中的默认Action方法Index会输出以三种形式体现的“当前用户名”。HomeController类型上应用的AuthenticateAttribute特性正是我们自定义的AuthenticationFilter。

   1: [Authenticate]
   2: public class HomeController : Controller
   3: {
   4:     public void Index()
   5:     {
   6:         Response.Write(string.Format("Controller.User: {0}<br/>", this.User.Identity.Name));
   7:         Response.Write(string.Format("HttpContext.User: {0}<br/>", this.ControllerContext.HttpContext.User.Identity.Name));
   8:         Response.Write(string.Format("Thread.CurrentPrincipal: {0}", Thread.CurrentPrincipal.Identity.Name));
   9:     }
  10: }

由于浏览器默认提供对Basic认证的支持,所以当我们运行该程序后如下图所示的登录对话框会自动弹出,当我们输入正确的用户名和密码(用户名和密码直接维护在AuthenticateAttribute上)后,当前登录用户名会呈现在浏览器上。

这个用于实现Basic认证的AuthenticateAttribute定义如下,简单起见我们将帐号采用的用户名和密码保存在一个静态字段中。具体的认证实现在实现的OnAuthentication方法中,我们在该方法中调用IsAuthenticated判断请是否经过认证,并在认证成功的情况下得到代表请求用户的Principal对象,然对作为参数的AuthenticationContext对象的Principal属性进行赋值。对于没有经过认证的请求,我们会调用另一个方法ProcessUnauthenticatedRequest对其进行处理。

   1: public class AuthenticateAttribute:FilterAttribute,IAuthenticationFilter
   2: {
   3:     public const string AuthorizationHeaderName           ="Authorization";
   4:     public const string WwwAuthenticationHeaderName       ="WWW-Authenticate";
   5:     public const string BasicAuthenticationScheme         ="Basic";
   6:     private static Dictionary<string, string> userAccounters;
   7:  
   8:     static AuthenticateAttribute()
   9:     {
  10:         userAccounters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  11:  
  12:         userAccounters.Add("Foo", "Password");
  13:         userAccounters.Add("Bar", "Password");
  14:         userAccounters.Add("Baz", "Password");
  15:     }
  16:  
  17:     public void OnAuthentication(AuthenticationContext filterContext)
  18:     {
  19:         IPrincipal user;
  20:         if (this.IsAuthenticated(filterContext, out user))
  21:         {
  22:             filterContext.Principal = user;
  23:         }
  24:         else
  25:         {
  26:             this.ProcessUnauthenticatedRequest(filterContext);
  27:         }
  28:     }
  29:  
  30:     protected virtual AuthenticationHeaderValue GetAuthenticationHeaderValue(AuthenticationContext filterContext)
  31:     {
  32:         string rawValue = filterContext.RequestContext.HttpContext.Request.Headers[AuthorizationHeaderName];
  33:         if (string.IsNullOrEmpty(rawValue))
  34:         {
  35:             return null;
  36:         }
  37:         string[] split = rawValue.Split(' ');
  38:         if (split.Length != 2)
  39:         {
  40:             return null;
  41:         }
  42:         return new AuthenticationHeaderValue(split[0], split[1]);
  43:     }
  44:  
  45:     protected virtual bool IsAuthenticated(AuthenticationContext filterContext, out IPrincipal user)
  46:     {
  47:         user = filterContext.Principal;
  48:         if (null != user & user.Identity.IsAuthenticated)
  49:         {
  50:             return true;
  51:         }
  52:  
  53:         AuthenticationHeaderValue token = this.GetAuthenticationHeaderValue(filterContext);
  54:         if (null != token && token.Scheme ==  BasicAuthenticationScheme)
  55:         {
  56:             string credential = Encoding.Default.GetString(Convert.FromBase64String(token.Parameter));
  57:             string[] split = credential.Split(':');
  58:             if (split.Length == 2)
  59:             {
  60:                 string userName = split[0];
  61:                 string password;
  62:                 if (userAccounters.TryGetValue(userName, out password))
  63:                 {
  64:                     if (password == split[1])
  65:                     {
  66:                         GenericIdentity identity = new GenericIdentity(userName);
  67:                         user = new GenericPrincipal(identity, new string[0]);
  68:                         return true;
  69:                     }
  70:                 }
  71:             }
  72:         }
  73:         return false;
  74:     }
  75:  
  76:     protected virtual void ProcessUnauthenticatedRequest(AuthenticationContext filterContext)
  77:     {
  78:         string parameter = string.Format("realm=\"{0}\"", filterContext.RequestContext.HttpContext.Request.Url.DnsSafeHost);
  79:         AuthenticationHeaderValue challenge = new AuthenticationHeaderValue(BasicAuthenticationScheme, parameter);
  80:         filterContext.HttpContext.Response.Headers[WwwAuthenticationHeaderName] = challenge.ToString();
  81:         filterContext.Result = new HttpUnauthorizedResult();
  82:     }
  83:  
  84:     public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext) {}
  85: }

在对请求实施认证的IsAuthenticated方法中,我们会试图从请求的Authorization报头中提取安全凭证,并按照Basic凭证的格式解析出用户名和密码。只有在用户名和密码匹配的情况下,我们认为请求通过认证,并根据解析出来的用户名创建一个GenericPrincipal对象作为输出参数user的值。如果请求并为通过认证(它可以是一个匿名请求,或者提供的用户名与密码不匹配),方法ProcessUnauthenticatedRequest会被调用。在此情况下,它会对响应的WWW-Authenticate报头进行相应的设置,并创建一个HttpUnauthorizedResult对象作为AuthenticationContext对象的Result属性,那么客户端最终会接收到一个状态为“401, Unauthorized”的响应。

作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 
绿色通道: 好文要顶 已关注 收藏该文与我联系 
11
0
 
(请您对文章做出评价)
 
posted @ 2014-04-16 09:21 Artech 阅读(964) 评论(15) 编辑 收藏
 

利用自定义的AuthenticationFilter实现Basic认证的更多相关文章

  1. [ASP.NET MVC] 利用自定义的AuthenticationFilter实现Basic认证

    很多情况下目标Action方法都要求在一个安全上下文中被执行,这里所谓的安全上下文主要指的是当前请求者是一个经过授权的用户.授权的本质就是让用户在他许可的权限范围内做他能够做的事情,授权的前提是请求者 ...

  2. Nginx 配置 Basic 认证

    /* * 环境:LNMP(CentOS 6.6 + Nginx 1.8.0) */ 在 Nginx 下配置 Basic 认证需要依靠 Nginx 的 http_auth_basic_module 模块 ...

  3. angularjs+webapi2 跨域Basic 认证授权(二)

    在上一篇中大概演示了 整个认证授权的过程.今天在这篇博客中将结合上一篇的例子继续在跨域的情况 我们用ionic 写一个简单的页面 值得注意的是 在ionic.bundle.js 里面集成了angula ...

  4. angularjs+webapi2 跨域Basic 认证授权(一)

    如今的app,利用各种前端框架结合html5的混合开发模式已然盛极一时.其中ionic+angularjs更是如日中天.这种模式利用angularjs $http 请求数据api 以达到前后端分离深得 ...

  5. django 自定义auth中user登陆认证以及自写认证

    第一种: 重写自定义auth中user登陆认证模块, 引入MobelBackend from django.contrib.auth.backends import ModelBackend 重写验证 ...

  6. iOS进行Basic认证与NTLM认证

    一.iOS进行Basic认证 只需要在NSMutableURLRequest的Header中添加认证所需的Username和password. NSMutableURLRequest *webReq ...

  7. Apache 配置 Basic 认证

    /* * 环境:WAMP( Windows7 + WampServer2.2(Apache 2.2.21)) */ 配置过程: ① 生成用户文件,文件路径可以使用绝对路径,也可以使用相对路径 进入 a ...

  8. Basic认证

    Basic 概述 Basic 认证是HTTP 中非常简单的认证方式,因为简单,所以不是很安全,不过仍然非常常用. 当一个客户端向一个需要认证的HTTP服务器进行数据请求时,如果之前没有认证过,HTTP ...

  9. Http Basic认证

    Http Basic认证就是访问的时候把用户名和密码用base64加密放在request的header的authorization中 服务端直接获取authorization,解析,跟用户名匹配即可. ...

随机推荐

  1. Display Database Image using MS SQL Server 2008 Reporting Services

    原文 Display Database Image using MS SQL Server 2008 Reporting Services With the new release of MS SQL ...

  2. 波折yosemite下载过程

    已经知道Yosemite正式宣布了这一消息,为了尽快有效地使用该系统尽可能.上学前把一个新的硬盘驱动器准备就绪-但不幸的是,我不知道是谁动手当天学校欠网关停电,我没有强迫受害者上课听老师讲废话(这是什 ...

  3. ABP之动态WebAPI

    ABP之动态WebAPI ABP的动态WebApi实现了直接对服务层的调用(其实病没有跨过ApiController,只是将ApiController公共化,对于这一点的处理类似于MVC,对服务端的 ...

  4. javascript系列之执行上下文

    原文:javascript系列之执行上下文 写在前面:一 直想系统的总结一下学过的javascript知识,喜欢这门语言也热爱这门语言.未来想从事前端方面的工作,提前把自己的知识梳理一下.前面写了些 ...

  5. Merge into的使用详解-你Merge了没有

    Merge是一个非常有用的功能,类似于Mysql里的insert into on duplicate key. Oracle在9i引入了merge命令, 通过这个merge你能够在一个SQL语句中对一 ...

  6. js中arguments

    arguments 每天一对象,JS天天见,今天我们来看看arguments对象及属性.arguments对象不能显式创建,arguments对象只有函数开始时才可用.函数的 arguments 对象 ...

  7. HDOJ 3518 Boring counting

    SAM基本操作 拓扑寻求每个节点  最左边的出现left,最右边的出现right,已经有几个num ...... 对于每个出现两次以上的节点.对其所相应的一串子串的长度范围 [fa->len+1 ...

  8. Oracle 11g 环境,使用utl_smtp创建一个存储过程来发送邮件

    太多的在线电子邮件存储过程.我不转发,弄个作为一个简单的例子演示. create or replace procedure Send_mail(mail_body varchar2) is smtp_ ...

  9. Net开源网络爬虫

    转载.Net开源网络爬虫Abot介绍 .Net中也有很多很多开源的爬虫工具,abot就是其中之一.Abot是一个开源的.net爬虫,速度快,易于使用和扩展.项目的地址是https://code.goo ...

  10. 【IOS】 遍历info 所有内容 &amp;&amp; 唯一的节能设备UUID

    /**获取装置imie*/ std::string DeviceInfo::getIMIE() { #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) NSStri ...