Forms AuthenticationTicket SlidingExpiration 過期問題

最近同仁分享一個 Form Authentication Ticket 過期的問題(為什麼我的 APS.Net Form Authentication 在 timeout 時間還沒到前就失效了)。

如果 timeout 時間設定為 20 分鐘,而 Ticket 是 1:00:00 產生的,到期時間是 1:20:00。

如果設定 SlidingExpiration , 到期的時間會在每次回 Server 就更新嗎?

Timeout 時間跟你想的不一樣嗎?

我們可以看 MSDN 上的說明「FormsAuthentication.SlidingExpiration」,

如果提出要求,而且一半以上的逾時間隔已經經過滑動期限,重設為有效的驗證 cookie 的到期時間。

以下面圖來看(timeout設為4分鐘),就是使用者要在 紅色時間區段中有對 Server Request 過,才會更改「過期時間」。

如果使用者剛好只在綠色時間區段中 Request Server,

然後在 06:28:14 後去 Request Server , 就會 Timeout 了哦!

那有什麼解法呢? 同事的解法就是設定成 Double 的時間,

例如原本設定 20 分鐘 timeout,就設定成 40 分鐘 Timeout。

另一個方式,就是手動去更改 Ticket 的過期時間。

實作

1.建立一個 Web Forms 專案

1.1.建立 Login.aspx (登入後,切到 Default.aspx)

Login.aspx

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
             <asp:TextBox runat="server" ID="txtUserId"></asp:TextBox>
            <asp:Button runat="server" ID="btnLogin" Text="Login" OnClick="btnLogin_OnClick"/>
        </div>
    </form>
</body>
</html>

Login.aspx.cs

protected void btnLogin_OnClick(object sender, EventArgs e)
{
	FormsAuthentication.RedirectFromLoginPage(txtUserId.Text,false);
}

1.2.建立 Default.aspx (列出 Ticket 相關資訊,並更新 Ticket 的過期時間)

Default.aspx

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:ListBox ID="lsIdentityInfos" runat="server"></asp:ListBox>
            <hr/>
             <asp:Button runat="server" ID="btnAddIdentity" Text="Add Identity Info" OnClick="btnAddIdentity_OnClick"/>
        </div>
    </form>
</body>
</html>

Default.aspx.cs 

public partial class Default : System.Web.UI.Page
{
	protected void Page_Load(object sender, EventArgs e)
	{
		if (!Page.IsPostBack)
			FormsAuthenticationTicketInfo();
	}

	

	protected void btnAddIdentity_OnClick(object sender, EventArgs e)
	{
		FormsAuthenticationTicketInfo();
	}

	void FormsAuthenticationTicketInfo()
	{
		var identityInfos = new StringBuilder();
		FormsAuthenticationTicket ticket = ((FormsIdentity)Context.User.Identity).Ticket;
		var timeoutValue = (TimeSpan)(ticket.Expiration - ticket.IssueDate);
		identityInfos.AppendLine($"Timeout:{timeoutValue}");
		identityInfos.AppendLine($",IssueDate:{ticket.IssueDate.ToString("hh:mm:ss", null)}, Expiration:{ticket.Expiration.ToString("hh:mm:ss", null)}");
		var now = DateTime.Now;
		identityInfos.AppendLine($",Now:{now.ToString("hh:mm:ss", null)}");
		//var span = now - ticket.IssueDate;
		//var span2 = ticket.Expiration - now;
		//identityInfos.AppendLine($"now - ticket.IssueDate:{span}, Expiration - now:{span2}");
		lsIdentityInfos.Items.Add(identityInfos.ToString());
		//var newTicket = FormsAuthentication.RenewTicketIfOld(ticket);
		//Response.Write($"{newTicket.Expiration.ToString("hh:mm:ss", null)}");
	}

	protected override void OnPreRender(EventArgs e)
	{
		base.OnPreRender(e);
		if (Request.IsAuthenticated && Context.User.Identity is FormsIdentity)
		{
			//原本的 Ticket
			var orgTicket = ((FormsIdentity)Context.User.Identity).Ticket;
			//取出 Timeout 的值
			var timeoutValue = (TimeSpan)(orgTicket.Expiration - orgTicket.IssueDate);
			
			var now = DateTime.Now;
			//重新再計算新的過期時間
			var newExpiration = now + timeoutValue;
			//建立一個新的 Ticket 
			var newTicket = new FormsAuthenticationTicket(orgTicket.Version, orgTicket.Name, now, newExpiration, orgTicket.IsPersistent, orgTicket.UserData);
			
			if (newTicket.Expiration > orgTicket.Expiration)
			{
				// 建立 FormsAuthentication Cookie 
				HttpCookie objCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(newTicket));
				// 蓋掉舊的 Cookie
				Response.Cookies.Add(objCookie);
			}
		}
	}
}

註:我是在 Page 的 OnPreRender 去更新過期時間,您也可以將它放在 底層 Class 中,或是在 Global.asax 的 Application_AuthenticateRequest 中哦!

 

2.Web.config 設定使用 Forms Authentication

在 web.config system.web 區段中加入

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

當每次 Request Server 後,都會重新設定 Ticket 的過期時間哦! 如下,

測試專案: AuthenticationTicket-SlidingExpiration

參考資料

為什麼我的 APS.Net Form Authentication 在 timeout 時間還沒到前就失效了

FormsAuthentication.SlidingExpiration

Manually sliding Forms AuthenticationTicket expiration

Hi, 

亂馬客Blog已移到了 「亂馬客​ : Re:從零開始的軟體開發生活

請大家繼續支持 ^_^