星期二, 12月 11, 2012

ASP.Net 同時使用Windows Authentication與Forms Authentication混合認證 Mixed Security

最近主管又指示我寫一個小東西,要讓員工及有往來客戶都能使用。所以網站建置在內網,再利用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的方法加以改良,簡單整理後解釋如下:
  1. 在IIS應用程式的設定應該只允許『匿名存取』 "allow anonymous" ,其他的都不要。
  2. 該應用程式的Web.config設為 Forms Authentication,先假設為Login.aspx
    <authentication mode="Forms">
         <forms loginUrl="~/Login.aspx" timeout="2880" />
    </authentication>
  3. 另外加上一頁WinLogin.aspx,用IIS管理工具設為整合認證。WinLogin.aspx的輸出設為空白即可,所以只需留下 <%@ Page Language="C#" ...
  4. 在認證頁面(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");
                }
            }
  5. 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帳戶。

  6. 接下來就是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;
                    }
                }
            }
    
以下是混合認證的流程圖

2 則留言:

匿名 提到...

3.另外加上一頁WinLogin.aspx,用IIS管理工具設為整合認證。-->這一句就不懂了,因敞人是初學者,我不會從IIS 設定單頁整合認證?

Tseng Teng-Yi 提到...

初學者不建議使用這方法啦,而且你有沒有注意到,限Server 2003/R2使用,在Server 2008/2012/R2 上我還沒找到相同的設定。