Доступ к параметрам метода действия из пользовательского атрибута Authorize в MVC 3
Я пишу приложение MVC 3, где пользователи смогут войти и управлять своими данными. Я хочу, чтобы пользователи не могли просматривать или изменять данные других пользователей. Моим первым инстинктом было просто проверить доступ к соответствующему объекту в каждом методе действия следующим образом:
public ActionResult ShowDetails(int objectId)
{
DetailObject detail = _repo.GetById(objectId);
if (detail.User.UserID != (Guid)Membership.GetUser().ProviderUserKey)
{
return RedirectToAction("LogOff", "Account");
}
}
Это прекрасно работает, но я подумал, что было бы лучше поместить код авторизации объекта в пользовательский атрибут Authorize, полученный из AuthorizeAttribute, который я затем мог бы применить к контроллеру. К сожалению, мне не удалось найти способ доступа к параметрам метода действия из моего пользовательского атрибута Authorize. Вместо этого я нашел единственный способ получить доступ к входящему objectId - изучить httpContext.Request или filterContext.RequestContext.RouteData.Values:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private int _objectId = 0;
private IUnitOfWork _unitOfWork;
public MyAuthorizeAttribute(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
int.TryParse((string) filterContext.RequestContext.RouteData.Values["id"], out _objectId);
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
int objectId = 0;
if (httpContext.Request.Params.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
{
int.TryParse(httpContext.Request[idKey], out objectId);
}
if (objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
if (_objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
return base.AuthorizeCore(httpContext);
}
private bool IsAuthorized(int objectId, string userName)
{
DetailObject detail;
detail = _unitOfWork.ObjectRepository.GetById(objectId);
if (detail == null)
{
return false;
}
if (userName != detail.User.UserName)
{
return false;
}
return true;
}
}
Я считаю этот подход очень неуклюжим. Я действительно не хочу возиться с объектами RouteData или Request; было бы намного чище иметь доступ к параметрам метода действия, поскольку привязка модели уже извлекла бы соответствующие данные из RouteData и Request.
Я знаю, что могу получить доступ к параметрам метода действия из пользовательского фильтра действий (как подробно описано здесь), но не следует ли поместить код авторизации данных в фильтр авторизации? Чем больше примеров я вижу фильтров авторизации, тем больше у меня складывается впечатление, что они предназначены только для обработки ролей.
Мой главный вопрос: как мне получить доступ к параметрам метода действия из моего пользовательского атрибута Authorize?
1 ответ
Ответ на ваш главный вопрос: нет, к сожалению AuthorizationContext
не обеспечивает доступ к параметрам действия.
Во-первых, вы могли бы использовать ValueProvider
чтобы не иметь дело с тем, является ли идентификатор частью маршрута, параметром запроса или отправленным HTTP, следующим образом:
public override void OnAuthorization(AuthorizationContext filterContext)
{
string id = filterContext.Controller.ValueProvider.GetValue("id").AttemptedValue;
...
}
Это работает для простых типов данных и вносит небольшие накладные расходы. Однако, как только вы начнете использовать пользовательские связыватели моделей для параметров действий, вам придется наследовать свой фильтр от ActionFilterAttribute
чтобы избежать двойного связывания:
[MyFilter]
public ActionResult MyAction([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
...
}
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyModel;
...
}
}
В то время как семантически наследуется от AuthorizeAttribute
для целей авторизации звучит лучше, других причин для этого нет. Более того, я нахожу использование ActionFilterAttribute
проще, так как все, что вам нужно сделать, это переопределить только один метод, не сохраняя состояние для последующих методов.