ASP.NET Single Sign-On

1 comments
如果在相同網域下,建置多個跨伺服器的 ASP.NET 應用程式時,你可以啟用跨應用程式的表單驗證(Forms Authentication),提供單一登入(Single Sign-On)驗證,使用者就不需在切換應用程式時重新驗證。


如果使用表單驗證,則參與共用表單驗證的所有應用程式都要使用相同的機器金鑰(Machine Key)。機器金鑰會是用於檢視狀態(ViewState)及表單驗證票證(Forms Authentication Tickets)的加密、解密以及驗證,在 .NET Framework 1.1 版是定義在 Machine.config 中的 <machineKey> 項目,其預設組態如下:
<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey="AutoGenerate,IsolateApps" validation="SHA1"/>

在 .NET Framework 2.0 則是定義在 Machine.config.comments,其預設組態如下:
<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey="AutoGenerate,IsolateApps" validation="SHA1" decryption="Auto" />

validationKey 屬性值用於建立驗證表單票證的 HMAC 碼(Hashed Message Authentication Code)。decryptionKey 屬性值是用於加密和解密表單驗證票證的金鑰。validation 屬性則是表示產生 HMAC 碼時是使用何種演算法。decryption 屬性是 .NET Framework 2.0 新增的屬性,AES 是解密資料的預設演算法。

所以,如果你沒有在 <machineKey> 項目指定金鑰,預設的組態設定就會使 ASP.NET 為不同的應用程式產生唯一的加密金鑰。因此,若要達到跨應用程式的表單驗證的目的,你就必須覆寫 <machineKey> 項目,為所有 Web 伺服器設定相同的驗證金鑰及相同的驗證演算法。如果組態不一致,Cookie 就不能共用。你可以使用 RNGCryptoServiceProvider 類別自行產生給你所有應用程式通用的驗證金鑰 (validationKey)和解密金鑰(decryptionKey)。

如果你需要 ASP.NET 2.0 的應用程式可以與 ASP.NET 1.1 共用表單驗證票證資訊,則必須在每個 ASP.NET 2.0 應用程式的 <machineKey> 項目組態中加入 decryption="3DES",這是因為在 ASP.NET 1.1 中,是使用 3DES 演算法來加密和解密表單驗證票證。以下是組態設定範例:
<system.web>
<machineKey validationKey="F40C0FF602CF4181C15AA3E494EBC04B8D2C6654A54DD6C12CE39643D0D864CD685C701A655B622C04DCED8B36A1E2B7B5BDDB9F769BEB040FE31974E6FBFBC1" decryptionKey="8CF1335D3E93BBA2F6C3F6A6BF88F37685F4E3979B3A8511" validation="SHA1" decryption="3DES" />
<system.web>

登入驗證
在登入網頁建立自訂的驗證程式碼,檢查使用者名稱與密碼,如果驗證成功,就為該使用者建構 Principal 物件,並將它存入 HttpContext.User 中。在傳送表單驗證 Cookie 到用戶端前,你必須先設定 Cookie 的 Domain 屬性。以下是實作登入網頁的範例程式碼:
protected void _btnLogin_Click(object sender, EventArgs e)
{
SitePrincipal principal = SitePrincipal.ValidateLogin(_txtUserName.Text, _txtUserPass.Text);
if(principal != null)
{
Context.User = principal;
HttpCookie cookie =
FormsAuthentication.GetAuthCookie(principal.Identity.Name, _chkAutoLogin.Checked);
cookie.Domain = "yourwebsite.com";
Response.AppendCookie(cookie);
Response.Redirect(
FormsAuthentication.GetRedirectUrl(principal.Identity.Name, _chkAutoLogin.Checked));
}
else
{
_lblMessage.Text = "Wrong username or password";
}
}

程式碼所使用的 SitePrincipal 類別是實作 System.Security.Principal.IPrincipal 介面的自訂類別,請自行下載原始碼引用到你的應用程式。

啟用表單驗證
<system.web>
<authentication mode="Forms">
<forms name=".YourAppName" loginUrl="Login.aspx" protection="All" timeout="30" path="/" />
</authentication>
</system.web>

你應該在 <forms> 項目指定自己的 name 及 path 屬性,並且必須確定在你的所有的應用程式中保持一致的組態設定。protection 屬性是用來指定保護 Cookie 的方法,其預設值為 All(也是建議值),這會使 ASP.NET 同時使用驗證和加密金鑰來保護 Cookie。

處理驗證要求
在應用程式初始驗證要求時,你還必須檢查使用者是否已經過驗證。如果順利取得 Cookie,接著就可進行票證解密,取得使用者資訊,建立 Principal 物件。請在所有應用程式的 Global.asax 實作 Application_AuthenticateRequest 事件處理常式:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string userName = authTicket.Name;
SitePrincipal principal = new SitePrincipal(userName);
Context.User = principal;
}
}

當呼叫 FormsAuthentication.Decrypt 方法擷取表單驗證 Cookie 時,會參考 <machineKey> 項目中的指定的 decryptionKey 值解密票證值。

登出機制
從應用程式中登出該使用者,通常只要使用 System.Web.Security.FormsAuthentication.SignOut() 就可以從瀏覽器移除表單驗證 Cookie。但是 SignOut() 方法無法處理有網域關聯的 Cookie,所以你必須手動移除表單驗證 Cookie。以下是實作登出網頁的範例程式碼:
private void Page_Load(object sender, System.EventArgs e)
{
System.Web.HttpCookie cookie =
Request.Cookies[System.Web.Security.FormsAuthentication.FormsCookieName];

if (cookie!=null)
{
cookie.Domain = "yourwebsite.com";
cookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(cookie);
}

if (Request.QueryString["ReturnURL"]!=null)
{
Response.Redirect(Request.QueryString["ReturnURL"]));
}
else
{
Response.Redirect("~/Default.aspx");
}
}

完成後,你就可以在頁面程式碼中,使用 Request.IsAuthenticated 屬性檢查目前的連線要求是否驗證成功。以下是在 Page_Load 事件處理常式的範例程式碼:
private void Page_Load(object sender, System.EventArgs e)
{
if (!Request.IsAuthenticated)
{
Response.Redirect(string.Format("~/Login.aspx?ReturnURL={0}",
Server.UrlEncode(Request.PathInfo)));
}
}

相關資源:
Understanding the Forms Authentication Ticket and Cookie by Microsoft

參考資料:
Single sign-on across multiple applications in ASP.NET by Michal Altair Valasek
建置安全的 ASP.NET 應用程式: 驗證、授權和安全通訊 by Microsoft
How To: Configure MachineKey in ASP.NET 2.0 by Microsoft
How To: Protect Forms Authentication in ASP.NET 2.0 by Microsoft
Explained: Forms Authentication in ASP.NET 2.0 by Microsoft
驗證與授權 (探索 .NET) by Microsoft

繼續閱讀...

ASP.NET 上傳檔案的限制

4 comments
當你在 ASP.NET 2.0 使用 FileUpload 控制項上傳超過 4MB 的檔案時,可能會面臨無法上傳的問題。這是因為上傳內容的長度超過了 maxRequestLength 參數在 Machine.config 檔案所定義的預設上限值 4096 (4 MB),這個限制是 ASP.NET 為了要預防可能的「拒絕服務」攻擊(Denial of Service attacks)。你可以在 Web.config 檔案中,覆寫應用程式 maxRequestLength 的值。例如,下列的 Web.config 設定會允許最大 10 MB 的檔案上傳:
<System.Web>
<httpRuntime maxRequestLength="10240" />
</System.Web>

但是,只要 POST 請求的內容(例如上傳檔案)超過 Web.config 的 maxRequestLength 設定,用戶端就會收到以下的錯誤訊息:「無法顯示網頁:找不到伺服器或 DNS 錯誤」。如果你試圖要在 Page 層級處理這個錯誤將會徒勞無功,因為這是屬於 Application 層級的錯誤,也就是 ASP.NET 早在執行你的網頁程式碼之前,就已經拋出「超出最大的要求長度」的例外。比較好的處理方式是在 Global.asax 加入 Application_BeginRequest 事件處理常式,在檔案未上傳前,先檢查 HTTP 標頭內容的長度,如果超過 maxRequestLength 的設定値,便將用戶端重新導向導引到自訂的錯誤頁面。以下是在 Global.asax 檔案中建立錯誤處理程序的程式碼:
<%@ Application Language="C#" %>
<%@ Import Namespace="System.Web.Configuration" %>
<script runat="server">

void Application_BeginRequest(object sender, EventArgs e)
{
HttpRuntimeSection section = (HttpRuntimeSection)ConfigurationManager.GetSection("system.web/httpRuntime");
int maxFileSize = section.MaxRequestLength*1024;

if (Request.ContentLength > maxFileSize)
{
Response.Redirect("~/FileTooLarge.aspx");
}
}
</script>

繼續閱讀...