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

