[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”的响应。
[ASP.NET MVC] 利用自定义的AuthenticationFilter实现Basic认证的更多相关文章
- 利用自定义的AuthenticationFilter实现Basic认证
[ASP.NET MVC] 利用自定义的AuthenticationFilter实现Basic认证 很多情况下目标Action方法都要求在一个安全上下文中被执行,这里所谓的安全上下文主要指的是当前 ...
- 转:【译】Asp.net MVC 利用自定义RouteHandler来防止图片盗链
[译]Asp.net MVC 利用自定义RouteHandler来防止图片盗链 你曾经注意过在你服务器请求日志中多了很多对图片资源的请求吗?这可能是有人在他们的网站中盗链了你的图片所致,这会占用你 ...
- Asp.net MVC 利用自定义RouteHandler来防止图片盗链
你曾经注意过在你服务器请求日志中多了很多对图片资源的请求吗?这可能是有人在他们的网站中盗链了你的图片所致,这会占用你的服务器带宽.下面这种方法可以告诉你如何在ASP.NET MVC中实现一个自定义Ro ...
- [ASP.NET MVC] 利用动态注入HTML的方式来设计复杂页面
原文:[ASP.NET MVC] 利用动态注入HTML的方式来设计复杂页面 随着最终用户对用户体验需求的不断提高,实际上我们很多情况下已经在按照桌面应用的标准来设计Web应用,甚至很多Web页面本身就 ...
- ASP.NET MVC利用PagedList分页(二)PagedList+Ajax+JsRender
(原文) 昨天在ASP.NET MVC利用PagedList分页(一)的 最后一节提到,一个好的用户体验绝对不可能是点击下一页后刷新页面,所以今天来说说利用Ajax+PagedList实现无刷新(个人 ...
- 【转】Asp.net MVC 通过自定义ControllerFactory实现构造器注入(重写DefaultControllerFactory)
[转]Asp.net MVC 通过自定义ControllerFactory实现构造器注入 一.重写ControllerFactory的GetControllerInstance ControllerF ...
- [转]Asp.net MVC 利用PartialView 构造自定义菜单
本文转自:http://www.cnblogs.com/huyq2002/archive/2012/01/06/2314838.html 在VS2010中利用Asp.net MVC自带的模板生成的菜单 ...
- ASP.NET MVC 利用IRouteHandler, IHttpHandler实现图片防盗链
你曾经注意过在你服务器请求日志中多了很多对图片资源的请求吗?这可能是有人在他们的网站中盗链了你的图片所致,这会占用你的服务器带宽.下面这种方法可以告诉你如何在ASP.NET MVC中实现一个自定义Ro ...
- ASP.NET MVC下自定义错误页和展示错误页的几种方式
在网站运行中,错误是不可避免的,错误页的产生也是不可缺少的. 这几天看了博友的很多文章,自己想总结下我从中学到的和实际中配置的. 首先,需要知道产生错误页的来源,一种是我们的.NET平台抛出的,一种是 ...
随机推荐
- 【BZOJ3156】防御准备 斜率优化DP
裸题,注意:基本的判断(求Min还是Max),因为是顺着做的,且最后一个a[i]一定要取到,所以是f[n]. DP:f[i]=min(f[j]+(i-j-1)*(i-j)/2+a[i]) 依旧设x&g ...
- HTML常用标签总结
HTML 的常用标签总结 <font size="字体大小1-7" color="red或0xff00ff" face="字体类型(楷体等)&q ...
- libCURL开源库在VS2010环境下编译安装,配置详解
libCURL开源库在VS2010环境下编译安装,配置详解 转自:http://my.oschina.net/u/1420791/blog/198247 http://blog.csdn.net/su ...
- Torch7学习笔记(三)Sequencialization
1.序列化 Torch提供4种高级方法来序列化或者反序列化任意Lua/Torch对象.这些方法都是从File对象抽象出来的,为了方便操作而创建. 前两种方法用来从文件序列化或者反序列化的: torch ...
- Ubuntu 安装 JDK 7
直接下载jdk压缩包方式安装 分为下面5个步骤 1.官网下载JDK 2.解压缩,放到指定目录 3.配置环境变量 4.设置系统默认JDK 5. 测试jdk 1.官网下载JDK 地址: http ...
- js Date学习
Date.parse()接收一个表示日期的字符串参数(参数错误时返回NaN),返回相应日期的毫秒数.(使用自 UTC(Coordinated Universal Time,国际协调时间)1970 年 ...
- >hibernate初认识
一.什么是hibernate 1.hibernate是java领域的一款开源的ORM框架技术 2.hibernate对JDBC进行了非常轻量级的封装(使用了反射机制+配置或注解) 二.hibernat ...
- 电容参数:X5R,X7R,Y5V,COG 详解
电容参数:X5R,X7R,Y5V,COG 详解 文章来源:http://www.hzlitai.com.cn/article/ARM9-article/cphard/1777.html 仅供分享学习~ ...
- listview嵌套gridview,并实现grid元素部分显示以及点击展开与折叠
原文链接:http://blog.csdn.net/duguju/article/details/49538341 有时我们需要用GridView显示目录列表,有时甚至是二级的,即listview每一 ...
- WPF整理-Style
"Consistency in a user interface is an important trait; there are many facets of consistency, ...