.net Forms 身份验证 - 手动设置 HttpContext.Current.User 在自定义 AuthorizeAttribute 中不起作用
.net Forms Authentication - manually setting HttpContext.Current.User not working in custom AuthorizeAttribute
我已经敲了好几个小时了,我被难住了。我正在向 MVC 5 控制器发出 ajax post 请求,以尝试自动登录特定的预定义 "super" 用户。在控制器方法中,我试图以编程方式设置 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("|"));
}
我能找到的最接近的 post 在这里:IsAuthenticated works on browser - but not with Air client!
但是,我已经准备好解决这个问题了:
<authentication mode="Forms">
<forms cookieless="UseCookies" timeout="60" loginUrl="~/Account/Login" />
</authentication>
要使 AuthorizationContext.User 与我在控制器中验证的 HttpContext.Current.User 匹配,我缺少什么?
更新:
我意识到我需要一个重定向来正确设置 cookie,我只是无法通过 ajax 调用远程实现它。这是站点 A 中的脚本在站点 B 上执行控制器方法时的样子。此重定向不设置会话。在下一个控制器方法上对用户进行身份验证时,它仍然不存在。它只是将我重定向回登录视图。
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.Cookies' FormsAuthenticationCookie 变回 FormsAuthenticationTicket 然后进入一个 IPrincipal。该校长被设置为 Request.CurrentUser 我相信 Thread.CurrentPrincipal
这需要在 AuthenticateRequest 步骤中完成,因为请求缓存可能因用户而异 (ResolveRequestCache) 并且会话状态始终如此(AcquireRequestState)。此外,AuthorizeRequest 步骤决定 AuthenticateRequest 步骤中设置的用户主体是否具有通过 所需的权限AuthorizeRequest 步骤没有获得 401 或 300 级重定向到登录页面(我相信 MVC 和 WebAPI 的授权机制作为 ProcessRequest 的一部分工作)
- 开始请求
- AuthenticateRequest (FormsAuthenticationModule)
- AuthorizeRequest(例如,UrlAuthorizationModule)
- 解析请求缓存
- MapRequestHandler
- 获取请求状态
- PreRequestHandlerExecute
- ProcessRequest(HttpHandler、Web 窗体、MVC 控制器操作位于此处)
您可以尝试通过设置 HttpContext.Current.User and Thread.CurrentPrincipal 到您的 IPrincipal,但它仅适用于 ProcessRequest 阶段之后的代码 运行...关于会话状态、缓存和授权的重要决定已经做出。
从理论上讲,您可以通过编写 HttpModule 并实现它来实现类似于您尝试做的事情 in/before AuthenticateRequest(或 global.asax),但您还不能访问更高级别的 MVC 控制器概念、会话状态等。您可以检查 HttpContext.Request.QueryString、HttpContext.Request.Form 和 HttpContext.Request.Cookies,但除此之外别无其他。您必须从 HTTP 请求的 AJAX 调用中获取用户名,然后调用 FormsAuthentication.SetAuthCookie() 或创建一个 FormsAuthenticationTicket 和 FormsAuthenticationCookie 自己把 cookie 塞进 Request.Cookies 和 Response.Cookies。当您的控制器逻辑运行时,我很确定请求会显示为已通过身份验证。
我已经敲了好几个小时了,我被难住了。我正在向 MVC 5 控制器发出 ajax post 请求,以尝试自动登录特定的预定义 "super" 用户。在控制器方法中,我试图以编程方式设置 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("|"));
}
我能找到的最接近的 post 在这里:IsAuthenticated works on browser - but not with Air client!
但是,我已经准备好解决这个问题了:
<authentication mode="Forms">
<forms cookieless="UseCookies" timeout="60" loginUrl="~/Account/Login" />
</authentication>
要使 AuthorizationContext.User 与我在控制器中验证的 HttpContext.Current.User 匹配,我缺少什么?
更新:
我意识到我需要一个重定向来正确设置 cookie,我只是无法通过 ajax 调用远程实现它。这是站点 A 中的脚本在站点 B 上执行控制器方法时的样子。此重定向不设置会话。在下一个控制器方法上对用户进行身份验证时,它仍然不存在。它只是将我重定向回登录视图。
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.Cookies' FormsAuthenticationCookie 变回 FormsAuthenticationTicket 然后进入一个 IPrincipal。该校长被设置为 Request.CurrentUser 我相信 Thread.CurrentPrincipal
这需要在 AuthenticateRequest 步骤中完成,因为请求缓存可能因用户而异 (ResolveRequestCache) 并且会话状态始终如此(AcquireRequestState)。此外,AuthorizeRequest 步骤决定 AuthenticateRequest 步骤中设置的用户主体是否具有通过 所需的权限AuthorizeRequest 步骤没有获得 401 或 300 级重定向到登录页面(我相信 MVC 和 WebAPI 的授权机制作为 ProcessRequest 的一部分工作)
- 开始请求
- AuthenticateRequest (FormsAuthenticationModule)
- AuthorizeRequest(例如,UrlAuthorizationModule)
- 解析请求缓存
- MapRequestHandler
- 获取请求状态
- PreRequestHandlerExecute
- ProcessRequest(HttpHandler、Web 窗体、MVC 控制器操作位于此处)
您可以尝试通过设置 HttpContext.Current.User and Thread.CurrentPrincipal 到您的 IPrincipal,但它仅适用于 ProcessRequest 阶段之后的代码 运行...关于会话状态、缓存和授权的重要决定已经做出。
从理论上讲,您可以通过编写 HttpModule 并实现它来实现类似于您尝试做的事情 in/before AuthenticateRequest(或 global.asax),但您还不能访问更高级别的 MVC 控制器概念、会话状态等。您可以检查 HttpContext.Request.QueryString、HttpContext.Request.Form 和 HttpContext.Request.Cookies,但除此之外别无其他。您必须从 HTTP 请求的 AJAX 调用中获取用户名,然后调用 FormsAuthentication.SetAuthCookie() 或创建一个 FormsAuthenticationTicket 和 FormsAuthenticationCookie 自己把 cookie 塞进 Request.Cookies 和 Response.Cookies。当您的控制器逻辑运行时,我很确定请求会显示为已通过身份验证。