[ASP.NET MVC] 利用自定義的AuthenticationFilter實(shí)現(xiàn)Basic認(rèn)證
很多情況下目標(biāo)Action方法都要求在一個(gè)安全上下文中被執(zhí)行,這里所謂的安全上下文主要指的是當(dāng)前請(qǐng)求者是一個(gè)經(jīng)過授權(quán)的用戶。授權(quán)的本質(zhì)就是讓用戶在他許可的權(quán)限范圍內(nèi)做他能夠做的事情,授權(quán)的前提是請(qǐng)求者是一個(gè)經(jīng)過認(rèn)證的用戶。質(zhì)詢-應(yīng)答(Chanllenge-Response)”是用戶認(rèn)證采用的一種常用的形式,認(rèn)證方向被認(rèn)證方發(fā)出質(zhì)詢以要求其提供用于實(shí)施認(rèn)證的用戶憑證,而被認(rèn)證方提供相應(yīng)的憑證以作為對(duì)質(zhì)詢的應(yīng)答。旨在目標(biāo)Action方法執(zhí)行之前實(shí)施身分認(rèn)證的AuthenticationFilter也對(duì)這種認(rèn)證方法提供了支持。
一、IAuthenticationFilter接口
所有的AuthenticationFilter類型均實(shí)現(xiàn)了IAuthenticationFilter接口,該接口定義在命名空間“System.Web.Mvc.Filters”下(其他四種過濾器接口都定義在“System.Web.Mvc”命名空間下)。如下面的代碼片斷所示,OnAuthentication和OnAuthenticationChallenge這兩個(gè)方法被定義在此接口中,前者用于對(duì)請(qǐng)求實(shí)施認(rèn)證,后者則負(fù)責(zé)將相應(yīng)的認(rèn)證質(zhì)詢發(fā)送給請(qǐng)求者。
1: public interface IAuthenticationFilter
2:
{
3: void OnAuthentication(AuthenticationContext filterContext);
4: void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext);
5:
}
定義在IAuthenticationFilter接口的兩個(gè)方法都將一個(gè)上下文對(duì)象作為其唯一參數(shù)。OnAuthentication方法的這個(gè)參數(shù)類型為AuthenticationContext,如下面的代碼片斷所示,它是ControllerContext的子類。AuthenticationContext的ActionDescriptor返回的自然是用于描述目標(biāo)Action方法的ActionDescriptor對(duì)象。借助于Principal屬性,我們可以獲取或設(shè)置代表當(dāng)前用戶的Principal對(duì)象。如果我們?cè)趫?zhí)行OnAuthentication方法的過程中設(shè)置了AuthenticationContext的Result屬性,提供的ActionResult將直接用于響應(yīng)當(dāng)前請(qǐng)求。
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方法的參數(shù)類型為AuthenticationChallengeContext。如下面的代碼片斷所示,它依然是ControllerContext的子類。它同樣具有一個(gè)用于描述目標(biāo)Action方法的ActionDescriptor屬性,其Result屬性代表的ActionResult對(duì)象將用于響應(yīng)當(dāng)前請(qǐng)求。
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的執(zhí)行流程
我們知道身份認(rèn)證總是對(duì)請(qǐng)求處理的第一個(gè)步驟,因?yàn)橹挥写_定了請(qǐng)求者的真實(shí)身份,安全才能得到保障,所以AuthenticationFilter是最先被執(zhí)行的一類過濾器。所有過濾器的執(zhí)行都是ActionInvoker來驅(qū)動(dòng)的,ASP.NET MVC在默認(rèn)情況下采用的ActionInvoker是一個(gè)AsyncControllerActionInvoker對(duì)象,后者類型派生于ControllerActionInvoker。ControllerActionInvoker針對(duì)AuthenticationFilter的執(zhí)行體現(xiàn)在如下兩個(gè)方法(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:
}
在目標(biāo)Action方法被執(zhí)行之后,通過本書第11章“View的呈現(xiàn)”我們知道最終執(zhí)行的結(jié)果會(huì)被封裝為一個(gè)ActionResult對(duì)象。ControllerActionInvoker會(huì)利用當(dāng)前ControllerContext、描述目標(biāo)Action方法的ActionDescriptor對(duì)象和這個(gè)ActionResult創(chuàng)建一個(gè)AuthenticationChallengeContext對(duì)象,并將其作為參數(shù)依次調(diào)用每個(gè)AuthenticationFilter的OnAuthenticationChallenge方法。這個(gè)AuthenticationChallengeContext對(duì)象的Result屬性最終返回的ActionResult對(duì)象將被用來對(duì)請(qǐng)求予以響應(yīng)。
右圖基本反映了整個(gè)“AuthenticationFilter鏈”的執(zhí)行流程, 但是如果在執(zhí)行某個(gè)AuthenticationFilter對(duì)象的OnAuthenticatio方法時(shí)對(duì)作為參數(shù)的AuthenticationContext對(duì)象的Result屬性作了相應(yīng)的設(shè)置,針對(duì)整個(gè)“AuthenticationFilter鏈”的執(zhí)行將會(huì)立即中止,指定的這個(gè)ActionResult對(duì)象將用于響應(yīng)當(dāng)前請(qǐng)求 。如果在執(zhí)行過程中對(duì)AuthenticationContext對(duì)象的Principal屬性作了相應(yīng)的設(shè)置,該屬性值將會(huì)作為當(dāng)前HttpContext和當(dāng)前線程的Principal。
三、實(shí)例演示:通過自定義AuthenticationFilter實(shí)現(xiàn)Basic認(rèn)證
在ASP.NET MVC的應(yīng)用編程接口中,我們找不到IAuthenticationFilter接口的實(shí)現(xiàn)者。為了讓大家對(duì)這個(gè)在ASP.NET MVC 5才引入的過濾器具有更加深刻的認(rèn)識(shí),我們接下來會(huì)通過一個(gè)實(shí)例來演示如何通過自定義的AuthenticationFilter實(shí)現(xiàn)針對(duì)Basic方案的認(rèn)證。不過在這之前,我們有必要對(duì)Basic這種基本的認(rèn)證方法作一個(gè)基本的了解。Basic和Digest是兩種典型的HTTP認(rèn)證方案。對(duì)于前者,雖然客戶端提供的認(rèn)證憑證(用戶名+密碼)僅僅是被Base64編碼而沒有被加密,但是我們可以通過采用HTTPS傳輸利用SSL來解決機(jī)密性的問題,所以Basic認(rèn)證也不失為一種不錯(cuò)的認(rèn)證方案。左圖體現(xiàn)了Basic認(rèn)證的基本流程,可以看出這也是一種典型的采用“質(zhì)詢-應(yīng)答”模式的認(rèn)證方案,整個(gè)流程包含如下兩個(gè)基本步驟。
- 客戶端向服務(wù)端發(fā)送一個(gè)HTTP請(qǐng)求,服務(wù)端返回一個(gè)狀態(tài)為“401, Unauthorized”的響應(yīng)。該響應(yīng)具有一個(gè)“WWW-Authenticate”的報(bào)頭標(biāo)明采用的是Basic認(rèn)證方案。Basic認(rèn)證是在一個(gè)“領(lǐng)域(Realm)”限定的上下文中進(jìn)行的,該報(bào)頭還可以執(zhí)行認(rèn)證的領(lǐng)域,左圖所示的WWW-Authenticate報(bào)頭值為:Basic realm="localhost"。
- · 客戶端向服務(wù)端發(fā)送一個(gè)攜帶基于用戶名/密碼的認(rèn)證憑證的請(qǐng)求。認(rèn)證憑證的格式為“{UserName}:{Password}”,并采用Base64編碼(編碼的目的不是為了保護(hù)提供的密碼)。這樣一個(gè)經(jīng)過編碼的認(rèn)證憑證被存放在請(qǐng)求報(bào)頭Authorization中,相應(yīng)的認(rèn)證方案類型(Basic)依然需要在該報(bào)頭中指定,左圖所示的Authorization報(bào)頭值為:Basic YcdfaYsss==。服務(wù)端接收到請(qǐng)求之后,從Authorization報(bào)頭中提取憑證并對(duì)其進(jìn)行解碼,最后采用提取的用戶名和密碼實(shí)施認(rèn)證。認(rèn)證成功之后,該請(qǐng)求會(huì)得到正常的處理,并回復(fù)一個(gè)正常的響應(yīng)。
在正式介紹如果定義這個(gè)實(shí)現(xiàn)Basic認(rèn)證的AuthenticationFilter之前,我們不妨先來看看使用了這個(gè)自定義AuthenticationFilter會(huì)產(chǎn)生怎樣的效果。我們?cè)谝粋€(gè)ASP.NET MVC應(yīng)用中定義了如下一個(gè)HomeController,定義其中的默認(rèn)Action方法Index會(huì)輸出以三種形式體現(xiàn)的“當(dāng)前用戶名”。HomeController類型上應(yīng)用的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:
}
由于瀏覽器默認(rèn)提供對(duì)Basic認(rèn)證的支持,所以當(dāng)我們運(yùn)行該程序后如下圖所示的登錄對(duì)話框會(huì)自動(dòng)彈出,當(dāng)我們輸入正確的用戶名和密碼(用戶名和密碼直接維護(hù)在AuthenticateAttribute上)后,當(dāng)前登錄用戶名會(huì)呈現(xiàn)在瀏覽器上。
這個(gè)用于實(shí)現(xiàn)Basic認(rèn)證的AuthenticateAttribute定義如下,簡(jiǎn)單起見我們將帳號(hào)采用的用戶名和密碼保存在一個(gè)靜態(tài)字段中。具體的認(rèn)證實(shí)現(xiàn)在實(shí)現(xiàn)的OnAuthentication方法中,我們?cè)谠摲椒ㄖ姓{(diào)用IsAuthenticated判斷請(qǐng)是否經(jīng)過認(rèn)證,并在認(rèn)證成功的情況下得到代表請(qǐng)求用戶的Principal對(duì)象,然對(duì)作為參數(shù)的AuthenticationContext對(duì)象的Principal屬性進(jìn)行賦值。對(duì)于沒有經(jīng)過認(rèn)證的請(qǐng)求,我們會(huì)調(diào)用另一個(gè)方法ProcessUnauthenticatedRequest對(duì)其進(jìn)行處理。
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:
}
在對(duì)請(qǐng)求實(shí)施認(rèn)證的IsAuthenticated方法中,我們會(huì)試圖從請(qǐng)求的Authorization報(bào)頭中提取安全憑證,并按照Basic憑證的格式解析出用戶名和密碼。只有在用戶名和密碼匹配的情況下,我們認(rèn)為請(qǐng)求通過認(rèn)證,并根據(jù)解析出來的用戶名創(chuàng)建一個(gè)GenericPrincipal對(duì)象作為輸出參數(shù)user的值。如果請(qǐng)求并為通過認(rèn)證(它可以是一個(gè)匿名請(qǐng)求,或者提供的用戶名與密碼不匹配),方法ProcessUnauthenticatedRequest會(huì)被調(diào)用。在此情況下,它會(huì)對(duì)響應(yīng)的WWW-Authenticate報(bào)頭進(jìn)行相應(yīng)的設(shè)置,并創(chuàng)建一個(gè)HttpUnauthorizedResult對(duì)象作為AuthenticationContext對(duì)象的Result屬性,那么客戶端最終會(huì)接收到一個(gè)狀態(tài)為“401, Unauthorized”的響應(yīng)。
出處: http://artech.cnblogs.com/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
