如何在 ASP.NET MVC 中重定向到动态登录 URL

我正在创建一个多租户网站,主机为客户的网页。URL 的第一部分将是一个字符串,用于标识客户端,该字符串在 Global.asax 中使用以下 URL 路由方案定义:

"{client}/{controller}/{action}/{id}"

使用/foo/Home/Index 这样的 URL 可以很好地工作。

但是,在使用[ Authorize ]属性时,我想重定向到一个也使用相同映射方案的登录页面。因此,如果客户机是 foo,登录页面将是/foo/Account/Login,而不是 web.config 中定义的固定/Account/Login 重定向。

MVC 使用 HttpUnauthorizedResult 返回一个401未授权状态,我推测这会导致 ASP.NET 重定向到 web.config 中定义的页面。

那么有人知道如何覆盖 ASP.NET 登录重定向行为吗?或者通过创建一个自定义授权属性在 MVC 中重定向会更好?

编辑-回答: 。我认为自定义身份验证属性是最好的解决方案:

public class ClientAuthorizeAttribute: AuthorizeAttribute
{
public override void OnAuthorization( AuthorizationContext filterContext )
{
base.OnAuthorization( filterContext );


if (filterContext.Cancel && filterContext.Result is HttpUnauthorizedResult )
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "client", filterContext.RouteData.Values[ "client" ] },
{ "controller", "Account" },
{ "action", "Login" },
{ "ReturnUrl", filterContext.HttpContext.Request.RawUrl }
});
}
}
}
47028 次浏览

I think the main issue is that if you're going to piggyback on the built-in ASP.NET FormsAuthentication class (and there's no good reason you shouldn't), something at the end of the day is going to call FormsAuthentication.RedirectToLoginPage() which is going to look at the one configured URL. There's only one login URL, ever, and that's just how they designed it.

My stab at the problem (possibly a Rube Goldberg implementation) would be to let it redirect to a single login page at the root shared by all clients, say /account/login. This login page wouldn't actually display anything; it inspects either the ReturnUrl parameter or some value I've got in the session or a cookie that identifies the client and uses that to issue an immediate 302 redirect to the specific /client/account/login page. It's an extra redirect, but likely not noticeable and it lets you use the built in redirection mechanisms.

The other option is to create your own custom attribute as you describe and avoid anything that calls the RedirectToLoginPage() method on the FormsAuthentication class, since you'll be replacing it with your own redirection logic. (You might create your own class that is similar.) Since it's a static class, I'm not aware of any mechanism by which you could just inject your own alternative interface and have it magically work with the existing [Authorize] attribute, which blows, but people have done similar things before.

Hope that helps!

In the RTM version of ASP.NET MVC, the Cancel property is missing. This code works with ASP.NET MVC RTM:

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Resources;


namespace ePegasus.Web.ActionFilters
{
public class CustomAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary
{
{ "langCode", filterContext.RouteData.Values[ "langCode" ] },
{ "controller", "Account" },
{ "action", "Login" },
{ "ReturnUrl", filterContext.HttpContext.Request.RawUrl }
});
}
}
}
}

Edit: You may want to disable the default forms authentication loginUrl in web.config - in case somebody forgets you have a custom attribute and uses the built in [Authorize] attribute by mistake.

Modify the value in web.config:

 <forms loginUrl="~/Account/ERROR" timeout="2880" />

Then make an action method 'ERROR' that logs an error and redirects the user to the most generic login page you have.

My solution to this problem was a custom ActionResult class:

    sealed public class RequiresLoginResult : ActionResult
{
override public void ExecuteResult (ControllerContext context)
{
var response = context.HttpContext.Response;


var url = FormsAuthentication.LoginUrl;
if (!string.IsNullOrWhiteSpace (url))
url += "?returnUrl=" + HttpUtility.UrlEncode (ReturnUrl);


response.Clear ();
response.StatusCode = 302;
response.RedirectLocation = url;
}


public RequiresLoginResult (string returnUrl = null)
{
ReturnUrl = returnUrl;
}


string ReturnUrl { get; set; }
}

Still, if one decides to use the built-in ASP.NET FormsAuthentication, one can overide Application_AuthenticateRequest in Global.asax.cs as follows:

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
string url = Request.RawUrl;


if (url.Contains(("Account/Login"))
{
return;
}


if (Context.User == null)
{
// Your custom tenant-aware logic
if (url.StartsWith("/foo"))
{
// Your custom login page.
Response.Redirect("/foo/Account/Login");
Response.End();
return;
}
}
}