最近主管又指示我寫一個小東西,要讓員工及有往來客戶都能使用。所以網站建置在內網,再利用Proxy導向對外網站。但是內網(Intranet)的用戶仍然要使用Windows Authentication(aka NTLM Authentication),而且Windows認證不支援proxy,除非使用Windows 的特殊proxy或丟到Windows Azure雲端。
這下我就很頭大了,只有找到Asp.net 1.1 的 Mixing Forms and Windows Security in ASP.NET,下載安裝還是不成功。幸好帥學弟之前就有Survey過,提供兩個參考資料:How I Made Windows Authentication and Forms Authentication Work Together 與 Combining Windows Authentication with Forms Authentication in ASP.NET 。
看完這兩篇大概有個方向,雖然他們用的原理不太一樣。我個人覺得Richard Dudley寫得比較清楚,我的作法再結合Aaron Milam的方法加以改良,簡單整理後解釋如下:
這下我就很頭大了,只有找到Asp.net 1.1 的 Mixing Forms and Windows Security in ASP.NET,下載安裝還是不成功。幸好帥學弟之前就有Survey過,提供兩個參考資料:How I Made Windows Authentication and Forms Authentication Work Together 與 Combining Windows Authentication with Forms Authentication in ASP.NET 。
看完這兩篇大概有個方向,雖然他們用的原理不太一樣。我個人覺得Richard Dudley寫得比較清楚,我的作法再結合Aaron Milam的方法加以改良,簡單整理後解釋如下:
- 在IIS應用程式的設定
應該只允許『匿名存取』 "allow anonymous" ,其他的都不要。在Server 2016或更新版除『匿名驗證』"Anonymous Authentication"還要加上『表單驗證』"Forms Authtication",否則會無法寫入Cookie造成錯誤。 - 該應用程式的Web.config設為 Forms Authentication,先假設為Login.aspx
<authentication mode="Forms"> <forms loginUrl="~/Login.aspx" timeout="2880" /> </authentication>
- 另外加上一頁WinLogin.aspx,用IIS管理工具啟用『Windows驗證』"Windows Authentication"。
WinLogin.aspx的輸出設為空白即可,所以只需留下 <%@ Page Language="C#" ... - 在認證頁面(Login.aspx)的Page_Load檢查User的IP,若在內部網路,則導向WinLogin.aspx
protected void Page_Load(object sender, EventArgs e) { string tClientIP = Request.ServerVariables["REMOTE_ADDR"].ToString(); if (tClientIP.StartsWith("192.168")) // Your intranet IP 這裡用你內網的rule { Response.Redirect("WinLogin.aspx"); } }
- WinLogin.aspx.cs的Page_Load使用System.Web.Security.ActiveDirectoryMembershipProvider檢查,因為.Net與IIS的版本變化多,奇奇怪怪的設定可能會失效(Aaron Milam的方法我就試不出來),用標準官方API比較保險。
所以在Web.config設成:<membership> <providers> <clear/> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" /> <add name="AspNetADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="MembershipADServer" attributeMapUsername="sAMAccountName" connectionUsername="使用者@網域名" connectionPassword="密碼" /> </providers> </membership>
connectionUsername和connectionPassword是在另一本書學的,萬一你的IIS是用本機帳號執行(大多數情況),會無法驗證AD帳戶正確性。
接下來就是WinLogin.aspx.cs本體:
protected void Page_Load(object sender, EventArgs e) { if (!System.Web.HttpContext.Current.User.Identity.IsAuthenticated) { string tClientIP = Request.ServerVariables["REMOTE_ADDR"].ToString(); if (tClientIP.StartsWith("192.168")) //Your intranet IP 這裡用你內網的rule { string returnUrl = Request["ReturnURL"]; Response.Redirect("WinLogin.aspx?ReturnURL=" + returnUrl); } } else //已認證 { string returnUrl = Request["ReturnURL"]; if (string.IsNullOrEmpty(returnUrl)) { returnUrl = "~/Default.aspx"; } Response.Redirect(returnUrl); } } protected void Login_Click(object sender, EventArgs e) { if (!string.IsNullOrEmpty(UserName.Text) && !string.IsNullOrEmpty(Password.Text)) { string returnUrl = Request["ReturnURL"]; //真正驗證 if (Membership.Providers["AspNetADMembershipProvider"].ValidateUser(UserName.Text, Password.Text)) { FormsAuthentication.SetAuthCookie(UserName.Text, true); if (returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\")) { Response.Redirect(returnUrl); } else { Response.Redirect("~/Default.aspx"); } } else if (Membership.ValidateUser(UserName.Text, Password.Text)) { FormsAuthentication.SetAuthCookie(UserName.Text, true); if (returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\")) { Response.Redirect(returnUrl); } else { Response.Redirect("~/Default.aspx"); } } } } //若使用新的Login Control protected void Login1_Authenticate(object sender, AuthenticateEventArgs e) { if (!string.IsNullOrEmpty(Login1.UserName) && !string.IsNullOrEmpty(Login1.Password)) { string returnUrl = Request["ReturnURL"]; //真正驗證 if (Membership.Providers["AspNetADMembershipProvider"].ValidateUser(Login1.UserName, Login1.Password)) { e.Authenticated = true; } else if (Membership.ValidateUser(Login1.UserName, Login1.Password)) { e.Authenticated = true; } else { e.Authenticated = false; } } }
留言