Перенаправление неавторизованного контроллера в ASP.NET MVC
У меня есть контроллер в ASP.NET MVC, который я ограничен ролью администратора:
[Authorize(Roles = "Admin")]
public class TestController : Controller
{
...
Если пользователь, не имеющий роли администратора, переходит к этому контроллеру, его приветствует пустой экран.
То, что я хотел бы сделать, это перенаправить их в View, который говорит: "Вы должны быть в роли администратора, чтобы иметь доступ к этому ресурсу".
Один из способов сделать это, о котором я подумал, - проверить каждый метод действия в IsUserInRole() и, если он не в роли, вернуть это информационное представление. Тем не менее, я должен был бы указать это в каждом действии, которое нарушает принцип DRY и, очевидно, неудобно поддерживать.
9 ответов
Создайте пользовательский атрибут авторизации на основе AuthorizeAttribute и переопределите OnAuthorization, чтобы выполнить проверку так, как вы этого хотите. Обычно AuthorizeAttribute устанавливает результат фильтра равным HttpUnauthorizedResult, если проверка авторизации не удалась. Вы могли бы вместо этого установить его в ViewResult (вашего вида Error).
РЕДАКТИРОВАТЬ: У меня есть пара постов в блоге, которые более подробно:
- http://farm-fresh-code.blogspot.com/2011/03/revisiting-custom-authorization-in.html
- http://farm-fresh-code.blogspot.com/2009/11/customizing-authorization-in-aspnet-mvc.html
Пример:
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class MasterEventAuthorizationAttribute : AuthorizeAttribute
{
/// <summary>
/// The name of the master page or view to use when rendering the view on authorization failure. Default
/// is null, indicating to use the master page of the specified view.
/// </summary>
public virtual string MasterName { get; set; }
/// <summary>
/// The name of the view to render on authorization failure. Default is "Error".
/// </summary>
public virtual string ViewName { get; set; }
public MasterEventAuthorizationAttribute()
: base()
{
this.ViewName = "Error";
}
protected void CacheValidateHandler( HttpContext context, object data, ref HttpValidationStatus validationStatus )
{
validationStatus = OnCacheAuthorization( new HttpContextWrapper( context ) );
}
public override void OnAuthorization( AuthorizationContext filterContext )
{
if (filterContext == null)
{
throw new ArgumentNullException( "filterContext" );
}
if (AuthorizeCore( filterContext.HttpContext ))
{
SetCachePolicy( filterContext );
}
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole( "SuperUser" ))
{
// is authenticated and is in the SuperUser role
SetCachePolicy( filterContext );
}
else
{
ViewDataDictionary viewData = new ViewDataDictionary();
viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
}
}
protected void SetCachePolicy( AuthorizationContext filterContext )
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) );
cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */);
}
}
Вы можете работать с переопределенным HandleUnauthorizedRequest
внутри вашего обычая AuthorizeAttribute
Как это:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 by default - see HttpUnauthorizedResult.cs.
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "YourActionName" },
{ "controller", "YourControllerName" },
{ "parameterName", "YourParameterValue" }
});
}
Вы также можете сделать что-то вроде этого:
private class RedirectController : Controller
{
public ActionResult RedirectToSomewhere()
{
return RedirectToAction("Action", "Controller");
}
}
Теперь вы можете использовать его в своем HandleUnauthorizedRequest
метод так:
filterContext.Result = (new RedirectController()).RedirectToSomewhere();
Код от "tvanfosson" давал мне "Ошибка выполнения дочернего запроса".. Я изменил OnAuthorization следующим образом:
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (!_isAuthorized)
{
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("User") || filterContext.HttpContext.User.IsInRole("Manager"))
{
// is authenticated and is in one of the roles
SetCachePolicy(filterContext);
}
else
{
filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
filterContext.Result = new RedirectResult("~/Error");
}
}
Это хорошо работает, и я показываю TempData на странице ошибок. Спасибо "tvanfosson" за фрагмент кода. Я использую проверку подлинности Windows и _isAuthorized - это не что иное, как HttpContext.User.Identity.IsAuthenticated...
Я была такая же проблема. Вместо того, чтобы выяснить код MVC, я выбрал дешевый хак, который, кажется, работает. В моем классе Global.asax:
member x.Application_EndRequest() =
if x.Response.StatusCode = 401 then
let redir = "?redirectUrl=" + Uri.EscapeDataString x.Request.Url.PathAndQuery
if x.Request.Url.LocalPath.ToLowerInvariant().Contains("admin") then
x.Response.Redirect("/Login/Admin/" + redir)
else
x.Response.Redirect("/Login/Login/" + redir)
Эта проблема преследует меня уже несколько дней, поэтому, найдя ответ, который утвердительно работает с ответом tvanfosson выше, я подумал, что было бы целесообразно подчеркнуть основную часть ответа и рассмотреть некоторые связанные с этим вопросы.
Основной ответ такой, милый и простой:
filterContext.Result = new HttpUnauthorizedResult();
В моем случае я наследую от базового контроллера, поэтому в каждом контроллере, который наследует от него, я переопределяю OnAuthorize:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
YourAuth(filterContext); // do your own authorization logic here
}
Проблема заключалась в том, что в "YourAuth" я пробовал две вещи, которые, по моему мнению, не только работали, но и немедленно прекращали запрос. Ну, это не так, как это работает. Итак, во-первых, две вещи, которые НЕ работают, неожиданно:
filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work!
FormsAuthentication.RedirectToLoginPage(); // doesn't work!
Они не только не работают, но и не заканчивают запрос. Что означает следующее:
if (!success) {
filterContext.Result = new HttpUnauthorizedResult();
}
DoMoreStuffNowThatYouThinkYourAuthorized();
Ну, даже с правильным ответом выше, поток логики все еще продолжается! Вы все равно нажмете DoMoreStuff... в OnAuthorize. Так что имейте это в виду (DoMore... должно быть в другом, следовательно).
Но с правильным ответом, пока поток логики OnAuthorize продолжается до самого конца, после этого вы действительно получаете то, что ожидаете: перенаправление на вашу страницу входа (если у вас есть один установленный в Forms auth в вашем webconfig).
Но неожиданно, 1) Response.Redirect("/Login") не работает: метод Action все еще вызывается, и 2) FormsAuthentication.RedirectToLoginPage(); делает то же самое: метод Action по-прежнему вызывается!
Что мне кажется совершенно неправильным, особенно в отношении последнего: кто бы мог подумать, что FormsAuthentication.RedirectToLoginPage не завершает запрос или делает эквивалент выше того, что делает filterContext.Result = new HttpUnauthorizedResult()?
Оставил бы это в качестве комментария, но мне нужно больше представителя, в любом случае я просто хотел бы упомянуть Николаса Петерсона, что, возможно, передача второго аргумента в вызов Redirect, чтобы сказать ему, что конец ответа сработал бы. Не самый изящный способ справиться с этим, но он действительно работает.
Так
filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
вместо
filterContext.RequestContext.HttpContext.Response.Redirect("/Login);
Таким образом, у вас будет это в вашем контроллере:
protected override void OnAuthorization(AuthorizationContext filterContext)
{
if(!User.IsInRole("Admin")
{
base.OnAuthorization(filterContext);
filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true);
}
}
Возможно, вы получаете пустую страницу при запуске из Visual Studio на сервере разработки с использованием аутентификации Windows ( предыдущий раздел).
При развертывании в IIS вы можете настроить пользовательские страницы ошибок для определенных кодов состояния, в данном случае 401. Добавьте httpErrors в system.webServer:
<httpErrors>
<remove statusCode="401" />
<error statusCode="401" path="/yourapp/error/unauthorized" responseMode="Redirect" />
</httpErrors>
Затем создайте метод ErrorController.Unauthorized и соответствующий пользовательский вид.
Вы должны создать свой собственный атрибут Authorize-filter.
Вот мой учиться;)
Public Class RequiresRoleAttribute : Inherits ActionFilterAttribute
Private _role As String
Public Property Role() As String
Get
Return Me._role
End Get
Set(ByVal value As String)
Me._role = value
End Set
End Property
Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext)
If Not String.IsNullOrEmpty(Me.Role) Then
If Not filterContext.HttpContext.User.Identity.IsAuthenticated Then
Dim redirectOnSuccess As String = filterContext.HttpContext.Request.Url.AbsolutePath
Dim redirectUrl As String = String.Format("?ReturnUrl={0}", redirectOnSuccess)
Dim loginUrl As String = FormsAuthentication.LoginUrl + redirectUrl
filterContext.HttpContext.Response.Redirect(loginUrl, True)
Else
Dim hasAccess As Boolean = filterContext.HttpContext.User.IsInRole(Me.Role)
If Not hasAccess Then
Throw New UnauthorizedAccessException("You don't have access to this page. Only " & Me.Role & " can view this page.")
End If
End If
Else
Throw New InvalidOperationException("No Role Specified")
End If
End Sub
End Class
В вашем файле Startup.Auth.cs добавьте эту строку:
LoginPath = new PathString("/Account/Login"),
Пример:
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});