最近主管又指示我寫一個小東西,要讓員工及有往來客戶都能使用。所以網站建置在內網,再利用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;
}
}
}



留言