奇技淫巧 - ASP.NET 同時支援 Form 及 Windows 驗證
2 |
跟同事討論到 ASP.NET/IIS 有沒可能同時支援 Windows 驗證跟 Form 驗證?依我的理解,每個 Web Application 只能二者擇一,要嘛走 Windows 驗證、要嘛用 Form 驗證。討論到後來,我對自己發出靈魂拷問:ASP.NET 應用程式真的沒辦法做到使用 Form 驗證又能用 Windows 整合式驗證嗎?
過去我能想到的方法是開兩個 Web Appliation 分別跑 Form 驗證及 Windows 驗證,再自己寫 SSO 機制串接;或是 Form 接入 AD 帳號密碼再走 LDAP 或其他方式驗證帳號密碼(缺點是程式端會拿到 AD 帳密,需面對帳號密碼可能被儲存或外流的質疑,我個人偏好「閃開! 讓專業的瀏覽器跟 IIS 來處理」)。沒法像下面這樣,在原本使用 Form 驗證的 Web Applicaion 加一個鈕讓使用者改用 Windows 驗證登入:
這... 所以,現在大家知道答案了,是的! 這是有可能做到的。
上面的範例是我參考網路文章實做出來的,整理重點如下:
- 使用 Form 或 Windows 驗證,由 web.config 的 system.web/authentiation 與 application.config system.webServer/security/authentication 控制 (延伸閱讀:【答客問】IIS 與 web.config 的 Windows 驗證設定),預設我們無法切換特定網址的 Windows 驗證,像是全站用 Form 驗證,只有 LoginAD.aspx 用 Windows 驗證:
將導致 503.19 錯誤:<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.7.2"/> <authentication mode="Forms"> <forms loginUrl="Login.aspx"/> </authentication> <authorization> <deny users="?"/> </authorization> <pages controlRenderingCompatibilityVersion="4.0"/> </system.web> <location path="LoginAD.aspx"> <system.webServer> <security> <authentication> <windowsAuthentication enabled="true" /> </authentication> </security> </system.webServer> </location> </configuration>
但我們調整 IIS 設定:
或修改 applicationhost.config 可解除此一限制:(衍生風險為無管理者權限者可以修改 web.config 避開 Windows 驗證,但前題要拿到更改網站檔案的權限)
(用 Visual Studio/IISExpress 測試的話,要改 .vs\ProjName\config\applicationhost.conf) - 由於全站開 Form 驗證,預設連到 LoginAD.aspx 時也會被導向 Login.aspx,要靠 Global.asax 加入 Application_EndRequest() 發現 LoginAD.aspx 被導走時改傳 401:
<%@ Application Language="C#" %> <%@ Import Namespace="System.Security.Principal" %> <script RunAt="server"> void Application_EndRequest(object sender, EventArgs e) { if (Response.StatusCode == 302 && Response.RedirectLocation.Contains("/Login.aspx") && Response.RedirectLocation.ToLower().Contains("loginad") && Request.Browser.Win32) { System.Diagnostics.Debug.WriteLine(Response.RedirectLocation); Response.ClearContent(); Response.Write(string.Empty); Response.TrySkipIisCustomErrors = true; Response.Status = "401 Unauthorized"; Response.StatusCode = 401; //.NET 4.5+ Response.SuppressFormsAuthenticationRedirect = true; } } </script>
- LoginAD.aspx 的任務是取得 Windows 登入身分將其轉成 FormAuthentiation Cookie:
<%@ Page Language="C#" %> <%@ Import Namespace="System.Security.Principal" %> <script runat="server"> void Page_Load(object sender, EventArgs e) { Response.Cache.SetCacheability(HttpCacheability.NoCache); Response.Cache.SetNoStore(); Response.SuppressFormsAuthenticationRedirect = true; if (!Request.IsAuthenticated || User.Identity is FormsIdentity) { FormsAuthentication.SignOut(); Response.StatusCode = 401; } else { var userId = User.Identity.Name.Split('\\').Last(); FormsAuthentication.SetAuthCookie(userId, false); Response.Redirect("~/"); } } </script>
總之透過以上精巧安排,我們就能在 Form 驗證加個按鈕,按下去改用 AD 帳號登入,感覺挺酷的,可適用除了 AD 帳號還有自訂帳號的應用場合。
範例為求簡便是用 WebForm 實作,但搬到 ASP.NET MVC 使用也是 OK 的。老樣子,範例程式已放上 Github,有需要的朋友請自取參考。
【參考資料】
Tips of how to mix Forms and Windows authentication in ASP.NET.
Comments
# by 鳥毅
我以前有做過類似的,最近也會有相同的需求,要 .net core上的實作 https://blog.tenyi.com/2012/12/aspnet-windows-authenticationforms.html
# by Jeffrey
to 鳥毅,感謝分享。.NET Core 我初步想到的是 Application_EndRequest 部分改用 Middleware 實現 (但還沒細想)