跳到主要內容

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" ,其他的都不要。
    在Server 2016或更新版除『匿名驗證』"Anonymous Authentication"還要加上『表單驗證』"Forms Authtication",否則會無法寫入Cookie造成錯誤。


  2. 該應用程式的Web.config設為 Forms Authentication,先假設為Login.aspx
    <authentication mode="Forms">
         <forms loginUrl="~/Login.aspx" timeout="2880" />
    </authentication>
  3. 另外加上一頁WinLogin.aspx,用IIS管理工具啟用『Windows驗證』"Windows Authentication"。



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

留言

匿名表示…
3.另外加上一頁WinLogin.aspx,用IIS管理工具設為整合認證。-->這一句就不懂了,因敞人是初學者,我不會從IIS 設定單頁整合認證?
鳥毅寫道…
初學者不建議使用這方法啦,而且你有沒有注意到,限Server 2003/R2使用,在Server 2008/2012/R2 上我還沒找到相同的設定。

這個網誌中的熱門文章

自然人憑證讀卡機驅動程式

鳥毅用的是第一代的自然人憑證讀卡機,EZ100PU(後來有同事買EZmini可以讀SIM卡似乎更好),每年報稅時用一次。 本來只是要申請些政府業務,一時之間找不到光碟,沒想到在 驅動程式下載 居然看到Linux和Mac的驅動程式,剩下的就是政府單位的網頁和程式應該改版了吧!!!

DBeaver 介面語言

DBeaver是我個人頗常用的一套跨平台Database管理工具,最近升級後發現Windows版本居然變成簡體中文,而且無法切換為英文。

如何將較高版本SQL Server複製到低版本SQL Server (降級為舊版)並保留權限及資料庫圖表

一般若是要將SQL Server裡的Database轉往其他Server時,最簡單的方式就是備份(Backup)後再還原(Restore),或者是䣃離(detach)後附加(attach)。 但是很不幸地,若是由較低版本(e.g. 2008)到較高版本(e.g. 2012)要怎麼辦呢?