电竞比分网-中国电竞赛事及体育赛事平台

分享

asp.net Forms表單驗(yàn)證 使用經(jīng)驗(yàn)及驗(yàn)證流程分析

 仰望//45度微笑 2012-03-31

      最近,要做一個登陸的頁面,就想到了安全性方面的問題。記得曾經(jīng)在邵志東老師講的關(guān)于asp.net安全性方面的課程中,提到asp.net提供了4個身份驗(yàn)證程序:1.表單身份驗(yàn)證;2.Windows身份驗(yàn)證;3.Passport身份驗(yàn)證;4.默認(rèn)身份驗(yàn)證。尤其講了表單身份驗(yàn)證,想想,正好自己以前也不曾使用過這個驗(yàn)證方式,那就拿來練練手吧。

      表單驗(yàn)證,可以根據(jù)用戶和角色來限制用戶訪問。比如,我們有以一個后臺管理系統(tǒng),只有通過后臺登陸頁面合法登陸的用戶才能訪問后臺管理系統(tǒng)中的任何頁面,這個時候我們就可以通過表單驗(yàn)證來實(shí)現(xiàn)(過去我都是在每一個頁面寫判斷邏輯。現(xiàn)在想起來,過去的那種方法真是不折不扣的體力勞動,而且如果哪個頁面忘記寫了,就麻煩了)。

      實(shí)驗(yàn)開始(因?yàn)橹挥涗浗?jīng)驗(yàn),所以有些知識點(diǎn)這里并沒有提到,需要大家多花點(diǎn)課外時間了。文末提供了些鏈接供大家參考)

      我接下來就來做一個Forms表單驗(yàn)證的例子。在該例子中,我建立了兩個文件夾分別為User和Admin,在每一個文件夾中又有l(wèi)ogin.aspx、index.aspx和web.config。我希望普通用戶訪問User文件夾需要首先要在用戶登陸界面進(jìn)行登陸,成功后才能訪問用戶的index.aspx。而管理員則首先要在Admin的登陸界面進(jìn)行登陸,才能訪問Admin中的index.aspx。而在網(wǎng)站根目錄有LoginRedirect.aspx、web.config和Global.asax。
      目錄結(jié)構(gòu)圖:

      

      如何才能實(shí)現(xiàn)表單驗(yàn)證呢?
      1.配置根目錄下的web.config (在網(wǎng)站根目錄下web.config文件中的system.web標(biāo)記中,修改原<authentication mode="Windows" />為如下代碼)


        <authentication mode="Forms">
            
<forms name="adminlogin" loginUrl="loginRedirect.aspx">
            
</forms>
        
</authentication>
        
<authorization>
            
<allow users="*"/>
        
</authorization>

      上述的配置是什么意思呢?
      首先,這里有兩個不同的配置節(jié),authentication和authorization看上去是不是很像? 你可千萬不要被眼睛欺騙了,這兩個是不同的意思,前者是“驗(yàn)證”,后者是“授權(quán)”。
      接著,我們來看下“驗(yàn)證”這個配置節(jié)中的東西。
      mode表示的就是驗(yàn)證方式,這里有四個選項(xiàng):Windows、Forms、Passport、None。默認(rèn)的是Windows。我們這里選擇Forms。
      而在forms元素里,設(shè)置了name和loginUrl。
      name表示cookie的名字,我們后面要通過cookie來保存一些用戶信息并將包含cookie信息的http請求發(fā)送到服務(wù)器。服務(wù)器端,則會根據(jù)cookie信息對用戶進(jìn)行標(biāo)識,從而進(jìn)行進(jìn)一步的驗(yàn)證。
      LoginUrl 顧名思義就是登陸頁面的地址。如果說用戶沒有權(quán)限訪問某一頁面,就會被重定向到這個頁面。

      還有其它諸多元素,請大家自己查找相關(guān)資料。我也會在文末給出一些我認(rèn)為比較不錯的鏈接。

      講完了“驗(yàn)證”節(jié),接著講講“授權(quán)”節(jié)。
      授權(quán),自然是要限制哪些用戶或角色可以訪問,哪些用戶或角色不能訪問了。設(shè)置的方式就是通過設(shè)置<allow>和<deny>。如上所示的<allow users="*">就是表示允許所有用戶訪問。你可能會奇怪不是要限制用戶訪問嗎,怎么全部允許了?那是因?yàn)?,我就是希望“根目錄下”的所有東西都可以被任何用戶訪問。

      再來看看兩個子文件夾內(nèi)的web.config。


<configuration>
  
<location path="login.aspx">
    
<system.web>
      
<authorization>
        <allow users="*"/>
      
</authorization>
    
</system.web>
  
</location>
  
<system.web>
    
<authorization>
      
<allow roles="user"/>
      
<deny users="*"/>
    
</authorization>
  
</system.web>
</configuration>


      在這個配置文件中,不能配置“驗(yàn)證”節(jié)的內(nèi)容(該內(nèi)容只能在虛擬目錄的根目錄web.config中配置),我們只能對“授權(quán)”節(jié)進(jìn)行配置。上述的location的作用是表示該路徑不需要進(jìn)行授權(quán)檢查,因?yàn)槲蚁M魏斡脩舳伎梢赃M(jìn)到登陸頁面,原因大家應(yīng)該都想得到吧。
      而其他路徑,則不希望未登陸的用戶或網(wǎng)站管理員登陸,因此使用<allow roles="user">來允許只有角色為user的用戶訪問,而其他任何用戶都拒絕訪問。
      同理,來看下管理員目錄的web.config,請大家自己分析。


<configuration>
  
<location path="login.aspx">
    
<system.web>
      
<authorization>
        
<allow users="*"/>
      
</authorization>
    
</system.web>
  
</location>
  
<system.web>
    
<authorization>
      
<allow roles="Manager"/>
      
<deny users="*"/>
    
</authorization>
  
</system.web>
</configuration>

      配置好了之后,我們還需要寫一些cs代碼。首先我們來看一下loginRedirect.aspx.cs。因?yàn)?,我們現(xiàn)在訪問上述任何一個子文件的頁面時,都會先跳轉(zhuǎn)到這個頁面里來,而其實(shí)我則希望可以跳到相信子目錄的登陸頁面中去。因此,需要在這個文件中進(jìn)行一些判斷。


string from = Request.QueryString["ReturnUrl"];//每個跳轉(zhuǎn)過來的頁面都會帶有ReturnUrl值,以此來獲取跳轉(zhuǎn)之前的頁面
//獲取子目錄名稱
string fromFilePath = from.Substring(from.IndexOf('/'+ 1, from.IndexOf('/', from.IndexOf('/'+ 1- from.IndexOf('/')-1);
//根據(jù)子目錄名稱來判斷跳轉(zhuǎn)的鏈接
switch (fromFilePath.ToLower())
{
    
case "admin": Response.Redirect("/admin/login.aspx"); break;
    
case "user": Response.Redirect("/user/login.aspx"); break;
}

       有些人可能奇怪了,這么麻煩,既然可以在“驗(yàn)證”節(jié)中配置loginUrl,難道就不能對每個目錄實(shí)現(xiàn)直接跳轉(zhuǎn)到本目錄相應(yīng)登陸頁面嗎?很遺憾,目前為止,我還沒有找到直接的解決辦法。如果您有什么辦法,請不吝賜教。
       跳轉(zhuǎn)到登陸頁面后,那我們就應(yīng)該對用戶的登陸時間進(jìn)行處理了。


protected void Page_Load(object sender, EventArgs e)
{
    
//判斷用戶是否已經(jīng)登陸,且角色為user
    if (User.Identity.IsAuthenticated&&User.IsInRole("user"))
     {
//如果通過驗(yàn)證,則直接跳轉(zhuǎn)到index.aspx
        Response.Redirect("index.aspx");
     }
}

//登陸按鈕事件,這里簡單起見,我直接以用戶名"user",密碼"1"來判斷,當(dāng)然你也可以從數(shù)據(jù)庫讀取。
protected void btnLogin_Click(object sender, EventArgs e)
{
    
if (tbUserName.Text == "user" && tbPwd.Text == "1")
     {
        
//生成驗(yàn)證票據(jù),其中包括用戶名、生效時間、過期時間、是否永久保存和用戶數(shù)據(jù)等。而關(guān)于用戶角色的信息,我們保存在用戶數(shù)據(jù)中。
        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, tbUserName.Text, DateTime.Now, DateTime.Now.AddMinutes(30), true"User");
        
string cookieStr = FormsAuthentication.Encrypt(ticket);//對票據(jù)進(jìn)行加密
        HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieStr);
/*保存到cookie中。cookie的名字要與我們前面在配置文件中所寫的name值一樣。因?yàn)?,?dāng)cookie保留在本地后,下次再檢查用戶權(quán)限的時候就會自動查找與forms名稱相同的cookie,并傳送給服務(wù)器端進(jìn)行檢驗(yàn)。如果在本地找不到cookie,就自然無法通過驗(yàn)證。*/
        cookie.Expires 
= ticket.Expiration;
         cookie.Path 
= FormsAuthentication.FormsCookiePath;
         Response.Cookies.Add(cookie);
         Response.Redirect(
"index.aspx");//登陸成功后跳轉(zhuǎn)到index.aspx
    }
}
/*
這里突然冒出一個票據(jù),有些朋友是不是很奇怪呀?票據(jù)什么用呢?
票據(jù)其實(shí)也可以理解為憑據(jù)(只有有憑據(jù)的用戶才能通過檢查),它包括了上面注釋中所寫的一些與用戶相關(guān)的信息。但是票據(jù)不能直接傳送給服務(wù)器必須通過cookie來承載。而服務(wù)器端在接受到cookie之后,會從中取出票據(jù)的數(shù)據(jù),并進(jìn)行相關(guān)操作。
*/

      在Admin文件夾下的login.aspx.cs也是類似。就不再贅述了。
      差點(diǎn)忘了最后的一個東西了,就是為我們的用戶配置角色。上面我們在創(chuàng)建票據(jù)的時候發(fā)現(xiàn)沒有提供直接對角色賦值的功能,那我們就只能利用grobal.asax來實(shí)現(xiàn)了。
      在global.asax中有一個Application_AuthenticateRequest事件,該事件會在服務(wù)器決定該用戶瀏覽器是否應(yīng)該跳轉(zhuǎn)前發(fā)生。因此,我們只要在這里對用戶角色進(jìn)行配置,就可以達(dá)到目的。


protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    
if (HttpContext.Current.User != null)
     {
//如果用戶通過驗(yàn)證,則該項(xiàng)不為null
        if (HttpContext.Current.User.Identity.IsAuthenticated)
         {
            
if (HttpContext.Current.User.Identity is FormsIdentity)
             {
                 FormsIdentity id 
= (FormsIdentity)HttpContext.Current.User.Identity;
                 FormsAuthenticationTicket ticket 
= id.Ticket;

                
string userData = ticket.UserData;//取出角色數(shù)據(jù)
                string[] roles = userData.Split(',');
                 HttpContext.Current.User 
= new GenericPrincipal(id, roles);//重新分配角色
            }
         }
     }
}

      大家可以下載整個工程來看。
      【注意:不能在同一臺電腦上即登陸用戶界面又登陸管理員界面。因?yàn)槭褂肍orms名稱所能保留的cookie只可能是一個,所以如果登陸了管理員界面后,接著登陸用戶界面,就會覆蓋原來的cookie值。有些朋友使用session來保存用戶數(shù)據(jù)的方法,為實(shí)驗(yàn)。】
      實(shí)驗(yàn)結(jié)束


      Forms驗(yàn)證流程
      大致頁面處理流程如下:(介紹這個是為了引出FormsAuthenticationModule,因此不詳細(xì)介紹,大家可以參考:ASP.NET頁面生成流程
      客戶端的http請求到達(dá)服務(wù)器端后,首先IIS會接收到該消息,然后調(diào)用asp.net isapi擴(kuò)展,并由該擴(kuò)展將請求等信息傳送給.net運(yùn)行時。.Net Runtime會調(diào)用ProcessRequest方法根據(jù)客戶端發(fā)來的請求信息創(chuàng)建并初始化一個HttpWorkerRequest對象,并根據(jù)該對象創(chuàng)建HttpContext對象,該對象中包含HttpRequest、HttpResponse對象,其中HttpRequest對象中又包含cookie和瀏覽器信息。最終會生成并調(diào)用HttpApplication對象的Init方法。在Init方法中會調(diào)用HttpApplicaion.InitInternal方法。而該方法又會調(diào)用InitModules方法。InitModules方法則會初始化Web.Config中注冊的所有模塊(HttpModule)。

      我們的主角FormsAuthenticationModule也就是在這里上場了。
      初始化FormsAuthenticationModule的過程中,會先調(diào)用該Module的init方法。我們來看一下該方法中有些什么:

public void Init(HttpApplication app)
{
     app.AuthenticateRequest 
+= new EventHandler(this.OnEnter);
     app.EndRequest 
+= new EventHandler(this.OnLeave);
}

       Init方法注冊了兩個事件:OnEnter和OnLeave。分別在HttpApplication.AuthenticateRequest和EndRequst事件被觸發(fā)時執(zhí)行。
      這兩個事件具體做了些什么呢? 

private void OnEnter(object source, EventArgs eventArgs)
{
    
if (!_fAuthChecked || _fAuthRequired)
     {
         HttpApplication application 
= (HttpApplication) source;
         HttpContext context 
= application.Context;
         Trace(
"*******************Request path: " + context.Request.PathWithQueryString);
     
//從Web.Config中獲取authentication配置信息
        AuthenticationSection authentication = RuntimeConfig.GetAppConfig().Authentication;
         authentication.ValidateAuthenticationMode();
        
if (!_fAuthChecked)
         {

            
//設(shè)置是否為Forms驗(yàn)證,如果是_fAuthRequired 設(shè)為true
            _fAuthRequired = authentication.Mode == AuthenticationMode.Forms;
             _fAuthChecked 
= true;
         }
        
if (_fAuthRequired)
         {
            
if (!this._fFormsInit)
             {
                 Trace(
"Initializing Forms Auth Manager");
                
//初始化驗(yàn)證信息,Initialize方法主要是通過讀取配置文件的authentication節(jié)來初始化FormsName、LoginUrl等
                FormsAuthentication.Initialize();
                
this._FormsName = authentication.Forms.Name;
                
if (this._FormsName == null)
                 {
                    
this._FormsName = ".ASPXAUTH";
                 }
                 Trace(
"Forms name is: " + this._FormsName);
                
this._LoginUrl = authentication.Forms.LoginUrl;
                
if (this._LoginUrl == null)
                 {
                    
this._LoginUrl = "login.aspx";
                 }
                
this._fFormsInit = true;
             }
            
//以下方法用于設(shè)置通過驗(yàn)證的用戶標(biāo)識[重要]
            this.OnAuthenticate(new FormsAuthenticationEventArgs(context));
             CookielessHelperClass cookielessHelper 
= context.CookielessHelper;
            
//下面的語句,應(yīng)該是為了修改_skipAuthorization.該值指定 UrlAuthorizationModule 對象是否應(yīng)跳過對當(dāng)前請求的授權(quán)檢查。
     
//Forms 身份驗(yàn)證模塊和 Passport 身份驗(yàn)證模塊在重定向到已配置的登錄頁時都設(shè)置 SkipAuthorization。[MSDN]
     
//如果為false則要進(jìn)行授權(quán)檢查,否則就跳過檢查。[我的體會:如果不在代碼上進(jìn)行控制,一般該值都為false。]
            if (AuthenticationConfig.AccessingLoginPage(context, this._LoginUrl))
             {   
                 context._skipAuthorization 
= true;
                 cookielessHelper.RedirectWithDetectionIfRequired(
null, FormsAuthentication.CookieMode);
             }
            
if (!context.SkipAuthorization)
             {
                 context._skipAuthorization 
= AssemblyResourceLoader.IsValidWebResourceRequest(context);
             }
         }
     }
}

      在OnEnter事件中,我們提到一個重要的方法就是OnAuthenticate:

//通過這個方法,我們就可以得到一個通過驗(yàn)證User標(biāo)識
private void OnAuthenticate(FormsAuthenticationEventArgs e)
{
     HttpCookie cookie 
= null;
   
//_eventHandler是一個FormsAuthenticationModule類的Authenticate事件??梢酝ㄟ^在asp.net應(yīng)用程序的Global.asax文件中進(jìn)行處理
    if (this._eventHandler != null)
     {
        
this._eventHandler(this, e);
     }
  
//判斷用戶是否已經(jīng)通過驗(yàn)證,如果已經(jīng)通過驗(yàn)證則方法結(jié)束。通過驗(yàn)證的用戶,其User標(biāo)識不為Null。
    if ((e.Context.User != null|| (e.User != null))
     {
        
if (e.Context.User == null)
         {
             e.Context._user 
= e.User;
         }
     }
    
else
     {   
         FormsAuthenticationTicket tOld 
= null;
        
bool cookielessTicket = false;
        
try
         {  
            
//從Cookie數(shù)據(jù)中提取驗(yàn)證票據(jù)的數(shù)據(jù)
            tOld = ExtractTicketFromCookie(e.Context, this._FormsName, out cookielessTicket);
         }
        
catch
         {
             tOld 
= null;
         }
        
if ((tOld != null&& !tOld.Expired)
         {  
            FormsAuthenticationTicket ticket 
= tOld;
            
if (FormsAuthentication.SlidingExpiration)
             {

              
//更新驗(yàn)證票據(jù),根據(jù)所設(shè)置的過期時間來判斷
                ticket = FormsAuthentication.RenewTicketIfOld(tOld);
             }
            
//根據(jù)票據(jù)信息來創(chuàng)建用戶標(biāo)識。第二個參數(shù)是用于對用戶授于某種角色用的,但是從new string[0]可以看出此處不含角色數(shù)據(jù)。
     
//如果我們需要對用戶的角色進(jìn)行配置,可以在FormsAuthenticationModule類的Authenticate事件中配置
            e.Context._user = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);
            
if (!cookielessTicket && !ticket.CookiePath.Equals("/"))
             {
                 cookie 
= e.Context.Request.Cookies[this._FormsName];
                
if (cookie != null)
                 {
                     cookie.Path 
= ticket.CookiePath;
                 }
             }
     
//如果票據(jù)是新的,則生成一個新的Cookie給客戶端
            if (ticket != tOld)
             {
                
if ((cookielessTicket && (ticket.CookiePath != "/")) && (ticket.CookiePath.Length > 1))
                 {
                     ticket 
= new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, ticket.UserData, "/");
                 }
                
string cookieValue = FormsAuthentication.Encrypt(ticket);
                
if (cookielessTicket)
                 {
                     e.Context.CookielessHelper.SetCookieValue(
'F', cookieValue);
                     e.Context.Response.Redirect(e.Context.Request.PathWithQueryString);
                 }
                
else
                 {
                    
if (cookie != null)
                     {
                         cookie 
= e.Context.Request.Cookies[this._FormsName];
                     }
                    
if (cookie == null)
                     {
                         cookie 
= new HttpCookie(this._FormsName, cookieValue);
                         cookie.Path 
= ticket.CookiePath;
                     }
                    
if (ticket.IsPersistent)
                     {
                         cookie.Expires 
= ticket.Expiration;
                     }
                     cookie.Value 
= cookieValue;
                     cookie.Secure 
= FormsAuthentication.RequireSSL;
                     cookie.HttpOnly 
= true;
                    
if (FormsAuthentication.CookieDomain != null)
                     {
                         cookie.Domain 
= FormsAuthentication.CookieDomain;
                     }
                     e.Context.Response.Cookies.Add(cookie);
                 }
             }
         }
     }
}

      在執(zhí)行了這個module之后,還有一個重要的module我們不得不提的就是UrlAuthorizationModule。在這個module中,對上面所設(shè)置的用戶進(jìn)行了授權(quán)檢查,來確定該用戶是否可以訪問所請求的頁面。如果用戶沒有權(quán)限,則跳轉(zhuǎn)到loginUrl中所指定的頁面。主要方法就是OnEnter:

private void OnEnter(object source, EventArgs eventArgs)
{
     HttpApplication application 
= (HttpApplication) source;
     HttpContext context 
= application.Context;
    
if (context.SkipAuthorization)
     {
        
if (!context.User.Identity.IsAuthenticated)
         {
             PerfCounters.IncrementCounter(AppPerfCounter.ANONYMOUS_REQUESTS);
         }
     }
    
else
     {
        
//讀取web.config中配置的授權(quán)信息
        AuthorizationSection authorization = RuntimeConfig.GetConfig(context).Authorization;
        
//IsUserAllowed便是對用戶進(jìn)行授權(quán)檢查
        if (!authorization.EveryoneAllowed && !authorization.IsUserAllowed(context.User, context.Request.RequestType))
         {
             context.Response.StatusCode 
= 0x191;//用戶沒有被授權(quán),[記住這個標(biāo)識]
            this.WriteErrorMessage(context);
            
if (context.User.Identity.IsAuthenticated)
             {
                 WebBaseEvent.RaiseSystemEvent(
this0xfa7);
             }
             application.CompleteRequest();
         }
        
else
         {
            
if (!context.User.Identity.IsAuthenticated)
             {
                 PerfCounters.IncrementCounter(AppPerfCounter.ANONYMOUS_REQUESTS);
             }
             WebBaseEvent.RaiseSystemEvent(
this0xfa3);
         }
     }
}

      前面我們介紹了,F(xiàn)ormsAuthenticationModule中的2個主要事件中注冊了2個方法。這里來說第二個OnLeave方法。在這個方法中才真正設(shè)置了跳轉(zhuǎn)的頁面。

private void OnLeave(object source, EventArgs eventArgs)
{
    
if (_fAuthChecked && _fAuthRequired)
     {
         HttpApplication application 
= (HttpApplication) source;
         HttpContext context 
= application.Context;
        
//如果標(biāo)識為0x191,則跳轉(zhuǎn)到loginUrl
        if (context.Response.StatusCode == 0x191)
         {
            
string str3;
            
string strUrl = null;
            
if (!string.IsNullOrEmpty(this._LoginUrl))
             {
                 strUrl 
= AuthenticationConfig.GetCompleteLoginUrl(context, this._LoginUrl);
             }
            
if ((strUrl == null|| (strUrl.Length <= 0))
             {
                
throw new HttpException(SR.GetString("Auth_Invalid_Login_Url"));
             }
             CookielessHelperClass cookielessHelper 
= context.CookielessHelper;
            
string pathWithQueryString = context.Request.PathWithQueryString;
            
if (strUrl.IndexOf('?'>= 0)
             {
                 str3 
= FormsAuthentication.RemoveQueryStringVariableFromUrl(strUrl, "ReturnUrl"+ "&ReturnUrl=" + HttpUtility.UrlEncode(pathWithQueryString, context.Request.ContentEncoding);
             }
            
else
             {
                 str3 
= strUrl + "?ReturnUrl=" + HttpUtility.UrlEncode(pathWithQueryString, context.Request.ContentEncoding);
             }
            
int index = pathWithQueryString.IndexOf('?');
            
if ((index >= 0&& (index < (pathWithQueryString.Length - 1)))
             {
                 pathWithQueryString 
= FormsAuthentication.RemoveQueryStringVariableFromUrl(pathWithQueryString, "ReturnUrl");
             }
             index 
= pathWithQueryString.IndexOf('?');
            
if ((index >= 0&& (index < (pathWithQueryString.Length - 1)))
             {
                 str3 
= str3 + "&" + pathWithQueryString.Substring(index + 1);
             }
             cookielessHelper.SetCookieValue(
'F'null);
             cookielessHelper.RedirectWithDetectionIfRequired(str3, FormsAuthentication.CookieMode);
             context.Response.Redirect(str3, 
false);
         }
     }
}

      根據(jù)這個流程,結(jié)合前面的實(shí)驗(yàn),我們來模擬以下這個執(zhí)行過程。
      下述a~o這些步驟大致描述了整個驗(yàn)證的執(zhí)行流程。其中涉及到一些Http協(xié)議的知識:(這里介紹幾個)
      每次我們請求一個頁面,就是在發(fā)送一個http請求。在這個請求中包含了我們請求的方式(主要是Get、Post)、請求的頁面地址、客戶端的瀏覽器信息、Cookie等。而發(fā)送一次請求之后,服務(wù)器都會返回一個Http應(yīng)答包。在這個包中會有一些表示狀態(tài)的代碼,比如我們常見的404、200等,而表示跳轉(zhuǎn)的狀態(tài)代碼則是302。另外,如果服務(wù)器需要在客戶端設(shè)置cookie的話會在應(yīng)答包的包頭中加入set-cookie。具體例子如下:



上圖為一次Http請求 

 


上圖為一次Http應(yīng)答


      對Http協(xié)議有所了解后,現(xiàn)在來假設(shè)是第一次訪問用戶文件夾中的index.aspx頁面。那這個過程就如下所示:
      a.由于是第一次訪問這個站點(diǎn),瀏覽器無法在本地找到與這個網(wǎng)站對應(yīng)的cookie,因此會發(fā)送一個cookie為空的http請求到服務(wù)器端。
      b.服務(wù)器的IIS在接受到此請求后,會交給asp.net isapi擴(kuò)展進(jìn)行處理。.net 運(yùn)行時會為這個請求創(chuàng)建一個HttpApplication對象。
      c.HttpApplication對象創(chuàng)建后,會執(zhí)行Init()方法,從而執(zhí)行了FormsAutheticationModule。
      d.在執(zhí)行該模塊的過程中首先會讀取web.config中的配置信息配置loginUrl等數(shù)據(jù)。
      e.接著會觸發(fā)OnAuthenticate方法,該方法首先執(zhí)行g(shù)loabl.aspx中的Application_AuthenticateRequest事件,不過往往由于此時用戶還并未通過驗(yàn)證,所以在這次請求中,前面global.asax所寫的內(nèi)容執(zhí)行不到。接著就會檢查用戶是否通過驗(yàn)證,本次請求顯然是沒有通過驗(yàn)證。
      f.在UrlAuthorizationModule中,首先從web.config中讀取配置信息,然后核對用戶。由于e中用戶沒有通過驗(yàn)證,因此這里肯定核對失敗。于是會執(zhí)行context.Response.StatusCode = 0x191
      g.最后執(zhí)行到FormsAutheticationModule的OnLeave事件,因?yàn)閏ontext.Response.StatusCode == 0x191所以設(shè)置用戶瀏覽器需要執(zhí)行跳轉(zhuǎn)。這個實(shí)現(xiàn)的方式,是通過http應(yīng)答包返回302指令(跳轉(zhuǎn)指令),并設(shè)置了http頭的Location為目的頁面。
      h.瀏覽器在接受到返回的http應(yīng)答包后,執(zhí)行跳轉(zhuǎn)。此時目標(biāo)為LoginRedirect.aspx。(同樣有是一次對loginredirect.aspx的請求,過程省略)。
      i .由于我們在LoginRedirect.aspx.cs中也是一個跳轉(zhuǎn),因此請求這個頁面所返回的Http應(yīng)答包同樣是一個跳轉(zhuǎn)指令(跳轉(zhuǎn)到用戶登陸的界面Login.aspx)。
      j .輸入用戶名,密碼單擊登陸,又發(fā)送了一次http請求到服務(wù)器端。但是這次包含了用戶的登陸數(shù)據(jù)是按Post方式發(fā)送的。
      k.由于此時用戶并沒有審核通過,所以仍舊執(zhí)行上面的a~d,由于login.aspx這個頁面在配置文件中我們是設(shè)置為[location]節(jié)中,因此在后面的驗(yàn)證中就是一路綠燈呀。執(zhí)行完了所有module后的,此時的http應(yīng)答包中并不包含跳轉(zhuǎn)指令。
      l.接著執(zhí)行在login.aspx.cs中定義的事件,在該事件中,對用戶的登陸信息進(jìn)行驗(yàn)證,如果驗(yàn)證通過,則設(shè)置驗(yàn)證票據(jù),并將其值保存在cookie中,同時在http應(yīng)答包中加入cookie。也就是執(zhí)行Response.Cookie.Add(); 并且,還在事件最后要求瀏覽器跳轉(zhuǎn)到index.aspx。這便又在http應(yīng)答包中加入了跳轉(zhuǎn)指令
      m.瀏覽器在接收到這個http應(yīng)答包后,提取其中的cookie,并保存在本地。同時跳轉(zhuǎn)到相應(yīng)頁面(又發(fā)起一次對index.aspx頁面的請求)。
      n.在新的請求發(fā)出之前,瀏覽器由于找到了該站點(diǎn)對應(yīng)的cookie,會把該cookie值加入到請求中發(fā)送到服務(wù)器。
      o.后續(xù)的步驟其實(shí)又執(zhí)行了一遍上述過程,但是這次在驗(yàn)證的時候,因?yàn)橛辛藗魅氲腸ookie,可以得到合法用戶的信息,也就通過了驗(yàn)證。

      流程介紹結(jié)束

      寫的不當(dāng)之處,希望大家直言不諱~~

      整個工程下載http://files.cnblogs.com/stg609/LoginAuthentication.rar(開發(fā)平臺:VS 2008. 不過,基本沒用到.net 3.0的東西,可能可以順利轉(zhuǎn)換到VS 2005上使用)

      推薦閱讀http://www.cnblogs.com/cuihongyu3503319/archive/2008/09/11/1288956.html
                     http://book.csdn.net/bookfiles/406/10040614811.shtml
                     http://www./KB/web-security/formsroleauth.aspx
                     http://blog.csdn.net/virone/archive/2008/04/12/2284173.aspx
                     手把手教你HTTP協(xié)議之Session和Cookie

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多