在拜讀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。
而雖然成功讓使用者變為已驗證的狀態, 但其中一段
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