Dot-Net

.net Forms Authentication - 手動設置 HttpContext.Current.User 在自定義 AuthorizeAttribute 中不起作用

  • December 16, 2015

我已經打了這個幾個小時了,我很難過。我正在向 MVC 5 控制器發出 ajax 發布請求,以嘗試自動登錄特定的預定義“超級”使用者。在控制器方法中,我試圖以程式方式設置 HttpContext.Current.User 並進行身份驗證,因此超級使用者可以跳過手動登錄的過程。對此的共識似乎在這裡,我實現了:

設置 HttpContext.Current.User

在我嘗試使用自定義 AuthorizeAttribute 查看任何其他控制器方法之前,這似乎有效。

控制器方法:

[HttpPost]
[AllowAnonymous]
public ActionResult Login(string username)
{
   string password = ConfigurationManager.AppSettings["Pass"];

   User user = service.Login(username, password);

   var name = FormsAuthentication.FormsCookieName;
   var cookie = Response.Cookies[name]; 
   if (cookie != null)
   {   
       var ticket = FormsAuthentication.Decrypt(cookie.Value);
       if (ticket != null && !ticket.Expired)
       {
           string[] roles = (ticket.UserData as string ?? "").Split(',');
           System.Web.HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), roles);
       }
   }

   //...processing result

   return Json(result);
}

上面的 service.Login 方法創建了 cookie:

FormsAuthentication.SetAuthCookie(cookieValue, false);

雖然我正在設置具有身份且 IsAuthenticated 為真的使用者,但下面的 filterContext.HttpContext.User 不是同一個使用者。它本質上是空的,就好像它從未被分配過,也沒有經過身份驗證。

public override void OnAuthorization(AuthorizationContext filterContext) 
{
   string[] userDetails = filterContext.HttpContext.User.Identity.Name.Split(char.Parse("|"));
}

我能找到的最接近的文章在這裡:IsAuthenticated 適用於瀏覽器 - 但不適用於 Air 客戶端!

但是,我已經為我解決了這個問題:

<authentication mode="Forms">
 <forms cookieless="UseCookies" timeout="60" loginUrl="~/Account/Login" />
</authentication>

為了使 AuthorizationContext.User 與我在控制器中進行身份驗證的 HttpContext.Current.User 匹配,我缺少什麼?

更新:

我意識到我需要一個重定向來正確設置 cookie,我只是無法通過 ajax 呼叫遠端使其工作。這是在站點 B 上執行控制器方法時站點 A 中的腳本的樣子。此重定向不會設置會話。在下一個控制器方法上對使用者進行身份驗證時,它仍然不存在。它只是將我重定向回登錄視圖。

function remoteLogin(id) {
   $.ajax({
       url: "/MyController/RemoteLogin",
       type: "POST",
       dataType: "json",
       data: { "id": id }
   }).done(function (data) {
       if (data) {
           if (data.user) {
               var user = data.user;
               $.ajax({
                   url: "http://siteB.xyz/Account/Login",
                   type: "POST",
                   dataType: "json",
                   data: { "username": user.username, "password": user.password }
               }).done(function (data) {
                   if (data) {
                       window.location.href = "http://siteB.xyz/Next"
                   } else {
                       alert("Fail.");
                   }
               }).fail(function (data) {
                   alert("Fail.");
               });
           } else {
               alert("Fail.");
           }
       }
   }).fail(function (data) {
       alert("Fail.");
   });
}

您遇到的問題是此時您只是設置身份驗證 cookie,在表單身份驗證模組中創建的 IPrincipal 在有新請求之前不會發生 - 所以此時 HttpContext.User 處於奇怪的狀態. 一旦發生重定向,因為它是來自瀏覽器的新請求,cookie 將在到達您的頁面並創建正確的使用者對象之前被讀取。

Cookie 僅在請求完成後在瀏覽器上設置。

該問題與您的操作程式碼相對於請求處理管道的執行位置有關。您的程式碼正在ProcessRequest步驟中執行(見下文)。

HttpContext.Current.User應該由AuthenticateRequest事件處理程序設置。 FormsAuthenticationModule負責處理此問題,將Request.CookiesFormsAuthenticationCookie重新轉換為FormsAuthenticationTicket,然後轉換為IPrincipal。該 Principal 被設置為Request.CurrentUser我相信Thread.CurrentPrincipal

這需要在AuthenticateRequest步驟中完成,因為請求記憶體會因使用者(ResolveRequestCache)而異,而會話狀態總是如此(AcquireRequestState)。此外, AuthenticateRequest 步驟決定在AuthenticateRequest步驟中設置的使用者主體是否具有通過 AuthorizeRequest 步驟所需的權限,而****無需獲得 401 或 300 級重定向到登錄頁面(我相信 MVC 和 WebAPI 的授權機制有效作為ProcessRequest的一部分)

  • 開始請求
  • AuthenticateRequest (FormsAuthenticationModule)
  • AuthorizeRequest(例如,UrlAuthorizationModule)
  • 解析請求記憶體
  • MapRequestHandler
  • 獲取請求狀態
  • PreRequestHandlerExecute
  • ProcessRequest (HttpHandler, Web Forms, MVC controller actions live here)

您可以嘗試通過將HttpContext.Current.User Thread.CurrentPrincipal設置為您的IPrincipal來強制執行此操作,但它僅適用於在ProcessRequest階段之後執行的程式碼……關於會話狀態、記憶體和授權的重要決定已經製作好了。

理論上,您可以通過編寫 HttpModule 並在AuthenticateRequest (或 global.asax)中/之前實現它來實現類似於您嘗試做的事情,但您還無法訪問更高級別的 MVC 控制器概念,Session狀態等。您可以檢查HttpContext.Request.QueryStringHttpContext.Request.FormHttpContext.Request.Cookies,但僅此而已。您必須從 HTTP 請求中的 AJAX 呼叫中獲取使用者名,然後呼叫FormsAuthentication.SetAuthCookie()或自己創建FormsAuthenticationTicketFormsAuthenticationCookie並將 cookie 填充到Request.CookiesResponse.Cookies。當您的控制器邏輯執行時,我很確定該請求將顯示為經過身份驗證。

引用自:https://stackoverflow.com/questions/34034423