Доступ к параметрам метода действия из пользовательского атрибута 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 проще, так как все, что вам нужно сделать, это переопределить только один метод, не сохраняя состояние для последующих методов.

Другие вопросы по тегам