ASP.NET表單驗證類型轉換

  • 1927
  • 0
  • 2018-07-22

在拜讀RM大大的 Forms AuthenticationTicket SlidingExpiration 過期問題 時

自己動手做了一下表單驗證, 結果意外踩到

無法將類型 'System.Security.Principal.WindowsIdentity' 的物件轉換為類型 'System.Web.Security.FormsIdentity'。

的問題

以下片段是我用ASP.NET的表單驗證, 去建立一個 FormsAuthenticationTicket 放到cookie給用戶端使用

private void Login_Click(Object sender, EventArgs e)
  {
    // Create a custom FormsAuthenticationTicket containing
    // application specific data for the user.

    string username     = UserNameTextBox.Text;
    string password     = UserPassTextBox.Text;
    bool   isPersistent = false;
    
    //若要使用Membership須設定連接的DB,會去驗證username及password是否為認可的帳密
    if (Membership.ValidateUser(username, password))
    {
      string userData = "ApplicationSpecific data for this user.";
      
      //建立FormsAuthenticationTicket ,表示表單驗證用它來識別已驗證的使用者的驗證票證
      FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
        username,
        DateTime.Now,
        DateTime.Now.AddMinutes(30),
        isPersistent,
        userData,
        FormsAuthentication.FormsCookiePath);

      // Encrypt the ticket.
      string encTicket = FormsAuthentication.Encrypt(ticket);

      // Create the cookie.
      Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

      // Redirect back to original URL.
      Response.Redirect(FormsAuthentication.GetRedirectUrl(username, isPersistent));
    }
    else
    {
      Msg.Text = "Login failed. Please check your user name and password and try again.";
    }
  }

在建立FormsAuthenticationTicket成功時,表示使用者為已驗證的狀態,

因此在Page下的Context就會被初始成功, Context是HttpContext 類別, 包含User的property

判斷使用者是否已經是登入的狀態, 可以用以下的片段

 if(User.Identity.IsAuthenticated)
 {
     Response.Write("您現在是已登入狀態。");
 }

 

而另一種讓使用者為已驗證的狀態的方法, 可以透過 FormsAuthentication 類別中的一個 RedirectFromLoginPage 靜態方法, 如下列片段:

FormsAuthentication.RedirectFromLoginPage(strUsername, false); //strUsername可為任意字串, 一般為使用者帳號

驗證後一樣取得Context下的任一property來使用, 可是在我載入RM大大的專案下來,

自己新增了一個Index.aspx來實作RedirectFromLoginPage 靜態方法並嘗試要取Context的東西時, 遇到了以下問題

無法將類型 'System.Security.Principal.WindowsIdentity' 的物件轉換為類型 'System.Web.Security.FormsIdentity'。

記得剛開始將專案載下來執行並沒有這個問題....於是想起因為在web.config好像有設定這麼一段

<authentication mode="Forms">
  <forms loginUrl="login.aspx"
         name=".rmau" 
         slidingExpiration="true" 
         timeout="4" />
</authentication>
<authorization>
  <deny users="?"/>
</authorization>

這段FormsAuthentication 類別有提到"表單驗證的啟用方法是:將 authentication 組態項目的 mode 屬性設定為 Forms。您可以要求所有應用程式的要求都包含有效的使用者驗證票證,方法是:使用 authorization 組態項目,以拒絕任何未知使用者的要求"

言下之意是任意頁面一定要通過驗證後才可以讀取, 而最一開始的專案因為有設定loginUrl="login.aspx"導致任一頁面只要未經驗證一定會導到login.aspx

為了實驗 RedirectFromLoginPage的靜態方法, 要讓IIS啟動可以進入我自己新增的Index.aspx, 所以先拿掉了上述的web.config片段,

而雖然成功讓使用者變為已驗證的狀態, 但其中一段

FormsAuthenticationTicket ticket = ((FormsIdentity)Context.User.Identity).Ticket;

卻造成該錯誤, 原因是此時的Context.User.Identity為WindowsIdentity的型態, 強制轉成FormsIdentity而導致

 

至於驗證通過後為什麼會變成WindowsIdentity的型態, 則一定跟web.config中的設定有關係,參考authentication 項目的mode屬性, 因為其預設值為 Windows, 所以驗證後取得的Context.User.Identity則變為WindowsIdentity的型態,為了證明是拿掉mode屬性導致該錯誤, 所以這次把mode設為None, 結果轉換的時候Context.User.Identity的型態果真不一樣了!

這次變為'Unable to cast object of type 'System.Security.Principal.GenericIdentity' to type 'System.Web.Security.FormsIdentity'.的錯誤

結論: 下次遇到驗證轉換的問題一定要先確認authentication 項目設定的mode屬性

 

引用參考: 

https://blog.miniasp.com/post/2008/02/20/Explain-Forms-Authentication-in-ASPNET-20.aspx

https://dotblogs.com.tw/rainmaker/2017/03/23/165147