[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. Android在第三方应用程序系统应用尽早开始,杀死自己主动的第三方应用程序,以重新启动

    1.为什么第三方应用程序可能早于System的app启动? Android能够查阅了,这里就不细述了,这里不阐述ROM启动还有bootloader.软件启动的大致流程应该是 启动kernel 执行se ...

  2. Android经常使用的布局类整理(一)

    Android经常使用的布局类整理 近期又回头做了一下android的项目,发觉越来越不从心,非常多东西都忘了,简单的页面布局也非常多写不出来,首先还是先整理一下一些会混淆的概念先 layout_wi ...

  3. HTML5游戏开发引擎Pixi.js完全入门手册(一)框架简介及框架结构分析,作者思路剖析

    前言: 最近无聊在淘宝弄了个小店,打算做一个兼职.遇到一个客户,要我帮忙拷贝一个html5游戏.. 我这人有一个习惯,拿到自己没见过的东西.都会去研究一番.去网上查了下发现,资料都是英文版.感觉极度不 ...

  4. Ubuntu 中查看内核版本和系统版本的三个命令

    一.查看内核版本:cat /proc/version 二.查看内核版本:uname -a 三.查看系统版本:lsb_release -a 四.查看发行版类型:cat /etc/issue

  5. Huffman树与最优二叉树续

    OK,昨天我们对huffman数的基本知识,以及huffman树的创建做了一些简介,http://www.cnblogs.com/Frank-C/p/5017430.html 今天接着聊: huffm ...

  6. android 实现真正意义上的服务及源代码下载

    android APP后台服务和长期server长期互动,保证实时数据,这个小项目主要实施app退出后仍然能够执行服务. 使用该系统的Intent.ACTION_TIME_TICK现,这个系统的广播每 ...

  7. cocos2d 缓存池 对象的再利用

    1.简单的叙述说明池 例如,我们知道,游戏的游戏类型跑酷,游戏元素都在不断重复.游戏的内容将继续从屏幕右侧的创建,当元件在屏幕的左侧的,将消失.假设不变new 对象.release 对象 性能影响.怎 ...

  8. Android测试流量的几种方法

    1. tcpdump + wireshark 1.1 tcpdump抓包 注意:Android设备使用tcpdump需要root权限 tcpdump是一个在Unix-like系统中通用的网络抓包工具, ...

  9. DevExpress asp.net 导出Excel 自动开启迅雷问题,默认保存为aspx页面

    目前采取曲线救国策略: 利用MVC ..... <dx:ASPxGridView ID="ASPxGridView1" runat="server" Au ...

  10. linux perm

    转自: http://www.linuxidc.com/Linux/2012-05/59693.htm 有修改 nd -perm,根据文件的权限来查找文件,有三种形式:find -perm modef ...