ASP.NET Form身份驗證方式詳解

注:不會涉及ASP.NET的登陸系列控件以及membership的相關話題, 我只想用比較原始的方式來講明在ASP.NET中是如何實現身份認證的過程。 css

  ASP.NET身份認證基礎html

  在開始今天的博客以前,我想有二個最基礎的問題首先要明確:web

  1. 如何判斷當前請求是一個已登陸用戶發起的?算法

  2. 如何獲取當前登陸用戶的登陸名?瀏覽器

  在標準的ASP.NET身份認證方式中,上面二個問題的答案是:安全

  1. 若是Request.IsAuthenticated爲true,則表示是一個已登陸用戶。服務器

  2. 若是是一個已登陸用戶,訪問HttpContext.User.Identity.Name可獲取登陸名(都是實例屬性)。cookie

 

 ASP.NET身份認證過程數據結構

  在ASP.NET中,整個身份認證的過程其實可分爲二個階段:認證與受權。less

  1. 認證階段:識別當前請求的用戶是否是一個可識別(的已登陸)用戶。

  2. 受權階段:是否容許當前請求訪問指定的資源。

  這二個階段在ASP.NET管線中用AuthenticateRequest和AuthorizeRequest事件來表示。

  在認證階段,ASP.NET會檢查當前請求,根據web.config設置的認證方式,嘗試構造HttpContext.User對象供咱們在後續的處理中使用。 在受權階段,會檢查當前請求所訪問的資源是否容許訪問,由於有些受保護的頁面資源可能要求特定的用戶或者用戶組才能訪問。 因此,即便是一個已登陸用戶,也有可能會不能訪問某些頁面。 當發現用戶不能訪問某個頁面資源時,ASP.NET會將請求重定向到登陸頁面。

  受保護的頁面與登陸頁面咱們均可以在web.config中指定,具體方法可參考後文。

  在ASP.NET中,Forms認證是由FormsAuthenticationModule實現的,URL的受權檢查是由UrlAuthorizationModule實現的。

    1. 登陸:調用FormsAuthentication.SetAuthCookie()方法,傳遞一個登陸名便可。

  2. 註銷:調用FormsAuthentication.SignOut()方法。

 

 保護受限制的頁面

  爲了保護受限制的頁面的訪問,ASP.NET提供了一種簡單的方式: 能夠在web.config中指定受限資源容許哪些用戶或者用戶組(角色)的訪問,也能夠設置爲禁止訪問。

  好比,網站有一個頁面:MyInfo.aspx,它要求訪問這個頁面的訪問者必須是一個已登陸用戶,那麼能夠在web.config中這樣配置:

<location path="MyInfo.aspx">
   <system.web>
     <authorization>
        <deny users="?"/>
     </authorization>
   </system.web>
</location>

 爲了方便,我可能會將一些管理相關的多個頁面放在Admin目錄中,顯然這些頁面只容許Admin用戶組的成員才能夠訪問。 對於這種狀況,咱們能夠直接針對一個目錄設置訪問規則:

<location path="Admin">
<system.web>
<authorization>
<allow roles="Admin"/>
<deny users="*"/>
</authorization>
</system.web>
</location>

這樣就沒必要一個一個頁面單獨設置了,還能夠在目錄中建立一個web.config來指定目錄的訪問規則。在前面的示例中,有一點要特別注意的是:

1. allow和deny之間的順序必定不能寫錯了,UrlAuthorizationModule將按這個順序依次判斷。

2. 若是某個資源只容許某類用戶訪問,那麼最後的一條規則必定是在allow和deny的配置中,咱們能夠在一條規則中指定多個用戶:

  1. 使用users屬性,值爲逗號分隔的用戶名列表。

  2. 使用roles屬性,值爲逗號分隔的角色列表。

  3. 問號 (?) 表示匿名用戶。

  4. 星號 (*) 表示全部用戶。

在ASP.NET內部,當發現是在訪問登陸面時,會設置HttpContext.SkipAuthorization = true (實際上是一個內部調用), 這樣的設置會告訴後面的受權檢查模塊:跳過此次請求的受權檢查。所以,登陸頁老是容許全部用戶訪問,可是登陸頁所引用的CSS文件以及JS文件(頁面在訪問CSS, JS文件時,實際上是被重定向到登陸頁面了) 是在另外的請求中發生的,那些請求並不會要跳過受權模塊的檢查。

  爲了解決登陸頁不能正確顯示的問題,咱們能夠這樣處理:

  1. 在網站根目錄中的web.config中設置登陸頁所引用的JS, CSS文件都容許匿名訪問。

  2. 也能夠直接針對JS, CSS目錄設置爲容許匿名用戶訪問。

  3. 還能夠在CSS, JS目錄中建立一個web.config文件來配置對應目錄的受權規則。可參考如下web.config文件:

<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</configuration>

注意:在IIS中看到的狀況就和在Visual Studio中看到的結果就不同了。由於,像js, css, image這類文件屬於靜態資源文件,IIS能直接處理,不須要交給ASP.NET來響應,所以就不會發生受權檢查失敗, 因此,若是這類網站部署在IIS中,看到的結果又是正常的。

 

認識Forms身份認證

HTTP是一個無狀態的協議, 在開發WEB應用程序時,咱們一般會使用Cookie來保存一些簡單的數據供服務端維持必要的狀態。如下是我用FireFox所看到的Cookie列表:

  細說ASP.NET Forms身份認證

  這個名字:LoginCookieName,是在web.config中指定的:

<authentication mode="Forms" >
<forms cookieless="UseCookies" name="LoginCookieName" loginUrl="~/Default.aspx"></forms>
</authentication>

理解Forms身份認證

爲了實現安全性,ASP.NET採用【Forms身份驗證憑據】(即FormsAuthenticationTicket對象)來表示一個Forms登陸用戶, 加密與解密由FormsAuthentication的Encrypt與Decrypt的方法來實現。

  用戶登陸的過程大體是這樣的:

  1. 檢查用戶提交的登陸名和密碼是否正確。

  2. 根據登陸名建立一個FormsAuthenticationTicket對象。

  3. 調用FormsAuthentication.Encrypt()加密。

  4. 根據加密結果建立登陸Cookie,並寫入Response。

  在登陸驗證結束後,通常會產生重定向操做, 那麼後面的每次請求將帶上前面產生的加密Cookie,供服務器來驗證每次請求的登陸狀態。

每次請求時的(認證)處理過程以下:

  1. FormsAuthenticationModule嘗試讀取登陸Cookie。

  2. 從Cookie中解析出FormsAuthenticationTicket對象。過時的對象將被忽略。

  3. 根據FormsAuthenticationTicket對象構造FormsIdentity對象並設置HttpContext.User

  4. UrlAuthorizationModule執行受權檢查。

  在登陸與認證的實現中,FormsAuthenticationTicket和FormsAuthentication是二個核心的類型, 前者能夠認爲是一個數據結構,後者可認爲是處理前者的工具類。

UrlAuthorizationModule是一個受權檢查模塊,其實它與登陸認證的關係較爲獨立, 所以,若是咱們不使用這種基於用戶名與用戶組的受權檢查,也能夠禁用這個模塊

  因爲Cookie自己有過時的特色,然而爲了安全,FormsAuthenticationTicket也支持過時策略, 不過,ASP.NET的默認設置支持FormsAuthenticationTicket的可調過時行爲,即:slidingExpiration=true 。 這兩者任何一個過時時,都將致使登陸狀態無效。

  FormsAuthenticationTicket的可調過時的主要判斷邏輯由FormsAuthentication.RenewTicketIfOld方法實現,代碼以下:

public static FormsAuthenticationTicket RenewTicketIfOld(FormsAuthenticationTicket tOld)
{
// 這段代碼是意思是:當指定的超時時間逝去大半時將更新FormsAuthenticationTicket對象。

if( tOld == null ) 
return null;

DateTime now = DateTime.Now;
TimeSpan span = (TimeSpan)(now - tOld.IssueDate);
TimeSpan span2 = (TimeSpan)(tOld.Expiration - now);
if( span2 > span ) 
return tOld;

return new FormsAuthenticationTicket(tOld.Version, tOld.Name,
now, now + (tOld.Expiration - tOld.IssueDate),
tOld.IsPersistent, tOld.UserData, tOld.CookiePath);
}
View Code

 

在多臺服務器之間使用Forms身份認證(Passport單點身份驗證)

  默認狀況下,ASP.NET 生成隨機密鑰並將其存儲在本地安全機構 (LSA) 中, 所以,當須要在多臺機器之間使用Forms身份認證時,就不能再使用隨機生成密鑰的方式,須要咱們手工指定,保證每臺機器的密鑰是一致的。

  用於Forms身份認證的密鑰能夠在web.config的machineKey配置節中指定,咱們還能夠指定加密解密算法:

<machineKey 
decryption="Auto" [Auto | DES | 3DES | AES]
decryptionKey="AutoGenerate,IsolateApps" [String]
/>

Passport單點身份驗證:(<machineKey validationKey="5029E82E1779497186D46F83D78FAD07" decryptionKey="82B8397DB5B4443FB035083EB662CD98" 

validation="SHA1" decryption="Auto" />)

             1:獲取機器key 生成密鑰

             2:在要實現單點登錄的項目根web.config中添加密鑰;

a) 兩個項目Web.cinfig的<machineKey> 節點確保如下幾個字段徹底同樣:validationKey 、decryptionKey 、validation 
b) 兩個項目的 Cookie 名稱必須相同,也就是 <forms> 中的 name 屬性,這裏咱們把它統一爲 name ="UserLogin" 
c) 注意區分大小寫 
d) 登錄頁面整合到統一登錄點登錄   好比:loginUrl="www.wf.com/login.aspx", 在登錄頁面發放驗證票    

 

在客戶端程序中訪問受限頁面

  有時咱們須要用代碼訪問某些頁面,好比:但願用代碼測試服務端的響應。

  若是是簡單的頁面,或者頁面容許全部客戶端訪問,這樣不會有問題, 可是,若是此時咱們要訪問的頁面是一個受限頁面,那麼就必須也要像人工操做那樣: 先訪問登陸頁面,提交登陸數據,獲取服務端生成的登陸Cookie, 接下來才能去訪問其它的受限頁面(但要帶上登陸Cookie)。

在前面的示例中,我已在web.config爲MyInfo.aspx設置過禁止匿名訪問,若是我用下面的代碼去調用:

private static readonly string MyInfoPageUrl = "http://localhost:51855/MyInfo.aspx";

static void Main(string[] args)
{
// 這個調用獲得的結果實際上是default.aspx頁面的輸出,並不是MyInfo.aspx
HttpWebRequest request = MyHttpClient.CreateHttpWebRequest(MyInfoPageUrl);
string html = MyHttpClient.GetResponseText(request);

if( html.IndexOf("<span>Fish</span>") > 0 )
Console.WriteLine("調用成功。");
else
Console.WriteLine("頁面結果不符合預期。");
}
View Code

  此時,輸出的結果將會是:頁面結果不符合預期。

  若是我用下面的代碼:

private static readonly string LoginUrl = "http://localhost:51855/default.aspx";
private static readonly string MyInfoPageUrl = "http://localhost:51855/MyInfo.aspx";

static void Main(string[] args)
{
// 建立一個CookieContainer實例,供屢次請求之間共享Cookie
CookieContainer cookieContainer = new CookieContainer();

// 首先去登陸頁面登陸
MyHttpClient.HttpPost(LoginUrl, "NormalLogin=aa&loginName=Fish", cookieContainer);

// 此時cookieContainer已經包含了服務端生成的登陸Cookie

// 再去訪問要請求的頁面。
string html = MyHttpClient.HttpGet(MyInfoPageUrl, cookieContainer);

if( html.IndexOf("<span>Fish</span>") > 0 )
Console.WriteLine("調用成功。");
else
Console.WriteLine("頁面結果不符合預期。");

// 若是還要訪問其它的受限頁面,能夠繼續調用。
}
View Code

  此時,輸出的結果將會是:調用成功。

  說明:在改進的版本中,我首先建立一個CookieContainer實例, 它能夠在HTTP調用過程當中接收服務器產生的Cookie,並能在發送HTTP請求時將已經保存的Cookie再發送給服務端。 在建立好CookieContainer實例以後,每次使用HttpWebRequest對象時, 只要將CookieContainer實例賦值給HttpWebRequest對象的CookieContainer屬性,便可實如今屢次的HTTP調用中Cookie的接收與發送, 最終能夠模擬瀏覽器的Cookie處理行爲,服務端也能正確識別客戶的身份。

相關文章
相關標籤/搜索