Вопросы о настройке Custom Security для MVC3 с использованием переопределенного AuthorizeAttribute, безопасности потоков, ChildActions и кэширования - PullRequest
12 голосов
/ 20 мая 2011

Итак, после поиска надежного решения безопасности для моего приложения MVC3, я наткнулся на это сообщение в блоге Рика Андерсона. В нем подробно описывается подход WhiteList, в котором пользовательская реализация AuthorizeAttribute применяется в качестве глобального фильтра, и вы декорируете действия / контроллеры, которым хотите разрешить анонимный доступ, с использованием фиктивного атрибута AllowAnonymousAttribute (я говорю, фиктивный, потому что внутри AllowAnonymousAttribute нет логики, это просто пустой класс атрибута)

bool allowAnnonymous = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
if (allowAnnonymous) return;

Это (наряду с другими рекомендациями по безопасности, упомянутыми в его блоге, такими как HTTPS) дает мне модель безопасности по умолчанию, при которой мне не нужно применять проверку безопасности для каждого отдельного действия, и не забудьте также добавить ее в будущую функцию дополнения.

Первая часть вопроса

Теперь я не использую свойства Users / Roles в AuthorizeAttribute, мне нужно извлечь этот материал из базы данных. Для меня это то, что было бы в AuthorizeCore, поскольку единственная ответственность - возвращать истинное ложное значение, имеет ли пользователь доступ. Однако у меня есть проблема, AuthorizeCore должен быть потокобезопасным, основываясь на моем чтении исходного кода для класса AuthorizeAttribute, и я не уверен, что лучший способ получить доступ к моей базе данных, чтобы определить пользовательские разрешения и придерживаться этого. Мое приложение использует IoC и в настоящее время позволяет моему контейнеру IoC внедрить мой репозиторий, обрабатывая все это в конструкторе AuthorizeAttribute, но, делая это и затем обращаясь к нему в AuthorizeCore, я не вызываю проблем с безопасностью потоков? Или реализация IoC и MVC3 DependencyResolver, который я использую для предоставления параметра в мой собственный конструктор AuthorizeAttribute, будут адекватно обрабатывать безопасность потоков? Обратите внимание, что в моих репозиториях используется шаблон UnitOfWork, который включает в себя мой nHibernate SessionFactory в качестве конструктора для репозитория, а класс Unit of Work предоставляется из моего контейнера IoC, реализованного StructureMap с использованием строки ниже, я правильно понял, что объем, используемый здесь будет обрабатывать проблемы с потоками?

For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();

Вторая часть вопроса

Моя модель данных (и, следовательно, модель безопасности) настроена таким образом, чтобы все мои основные бизнес-объекты были определены таким образом, чтобы это была одна модель большой иерархии, и чтобы при проверке разрешений я смотрел в этой модели иерархии, где Учетная запись пользователя была определена и по умолчанию предоставляет доступ ко всему, что находится под ней. Вторичная проверка разрешений - это проверка, которая использует административно определенные разрешения бизнес-логики, например, могут ли пользователи в роли X получать доступ к функции удаления виджета. Для этого я использую данные Route и извлекаю имена Controller и Action и использую их вместе с деталями от текущих пользователей. Основные детали, чтобы попасть в мою базу данных, чтобы разрешить разрешения для этого запроса. Однако эта логика повторяется для каждого ChildAction, используемого на странице, но, поскольку я использую имена Controller и Action из данных Route, на самом деле я не получаю информацию Child Action. Он остается в качестве имени родительского действия, а не дочернего действия, поскольку дочернее действие не выполняется с помощью запроса URL. Это приводит к избыточным проверкам безопасности в моей базе данных для получения сведений о родительском действии и ненужных попаданиях ресурсов. Исследуя это, я решил просто обойти проверку безопасности для дочерних действий и полагаться на родительское действие для этого.

bool bypassChildAction = filterContext.ActionDescriptor.IsDefined(typeof (ChildActionOnlyAttribute), true) || filterContext.IsChildAction;
if (bypassChildAction) return;

Имеет ли смысл делать это, и если да / нет, то почему?На мой взгляд, если действие декорировано с помощью ChildActionOnlyAttribute, оно все равно недоступно публично через URL.И если оно выполняется как дочернее действие, но не является исключительно дочерним действием, я могу обойти проверку безопасности только для этого выполнения, поскольку родительское действие будет обрабатывать разрешения.Была ли у вас ситуация, когда вам нужно было бы ограничить доступ к действиям ребенка?Зная, что дочерние действия, как правило, представляют собой очень маленькие частичные представления, я не ожидаю, что это станет проблемой, но я также увидел ссылку на строку в реализации OnAuthorization по умолчанию, в которой изложены некоторые проблемы, связанные с кэшированием.Кто-нибудь знает, влияет ли это на мое предлагаемое решение?

Резюме проблем:

  • Проблемы многопоточности для доступа к пользовательским разрешениям из базы данных в AuthorizeCore
  • Проблемы безопасности при обходеПроверка полномочий для дочерних действий
  • Кэширование проблем для дочерних действий объединяется с предыдущим пунктом

Будем весьма признательны за любые мнения или помощь по этим аспектам!

Ответы [ 2 ]

2 голосов
/ 30 мая 2011

Heya Yarx - часть 1 - кэшируйте все разрешения для пользователя при входе в систему. В этом случае многопоточный доступ не является проблемой, поскольку ваш AuthorizeCore просто получает роли из кэша, который в то время можно считать доступным только для чтения.

Часть 2: Снова перейдем к пункту 1 выше:) - если ваши проверки безопасности настолько тяжелы, почему бы не загружать все разрешения для пользователя при входе в систему и кэшировать их. После выполнения действий вашего ребенка вы можете запросить разрешения и в то же время проверить их в кэше.

Определенно есть лучший способ справиться с этим, который не такой тяжелый. Если вы нажимаете db несколько раз в одном запросе исключительно на разрешения, вам необходимо либо кэшировать свой набор разрешений с помощью какого-либо механизма (пользовательского или реализации другой системы, основанной на утверждениях и т. Д.)

Я не на 100% слежу за вашим механизмом, хотя для авторизации на основе маршрута. Вы упомянули, что извлекаете информацию из маршрута. Можете ли вы привести пример здесь?

Абсолютно имеет смысл защищать действия вашего ребенка. Что, если два представления вызывают Html.Action - одно определенно как администратор, а другое ошибочно копируется и вставляется в другое представление? Действия вашего ребенка всегда должны быть защищены, не думайте, что они в порядке, поскольку они вызываются только из другого представления.

Также, если вы не можете кэшировать все дерево для пользователя, вы, безусловно, можете кэшировать проверки безопасности при первом обращении к AuthorizeCore. Последующие звонки будут просто проверить на ex. кэшированные роли - если они есть, они используются, в противном случае обращайтесь к базе данных.

0 голосов
/ 01 июня 2011

Вот где я сейчас нахожусь. Я чувствую, что убрал это, но я не уверен, как еще сделать это с требованиями, которые я ищу, чтобы удовлетворить. Я все делаю неправильно или это просто у меня в голове?

public class LogonAuthorize : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
        {
            // If a child action cache block is active, we need to fail immediately, even if authorization
            // would have succeeded. The reason is that there's no way to hook a callback to rerun
            // authorization before the fragment is served from the cache, so we can't guarantee that this
            // filter will be re-run on subsequent requests.
            throw new InvalidOperationException("AuthorizeAttribute cannot be used within a child action caching block."); //Text pulled from System.Web.Mvc.Resources
        }
        // Bypass authorization on any action decorated with AllowAnonymousAttribute, indicationg the page allows anonymous access and
        // does not restrict access anyone (Similar to a WhiteList security model).
        bool allowAnnonymous = filterContext.ActionDescriptor.IsDefined(typeof (AllowAnonymousAttribute), true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof (AllowAnonymousAttribute), true);
        if (allowAnnonymous) return;

        if (CustomAuthorizeCore(filterContext))
        {
            HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge(new TimeSpan(0));
            cachePolicy.AddValidationCallback(CacheValidateHandler, filterContext); //CacheValidateHandler doesn't have access to our AuthorizationContext, so we pass it in using the data object.
        }

        HandleUnauthorizedRequest(filterContext);
    }

    private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
    {
        var filterContext = (AuthorizationContext)data;
        validationStatus = CustomOnCacheAuthorization(filterContext);
    }

    protected HttpValidationStatus CustomOnCacheAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext == null)
        {
            throw new ArgumentNullException("filterContext.HttpContext");
        }

        bool isAuthorized = CustomAuthorizeCore(filterContext);
        return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
    }

    protected bool CustomAuthorizeCore(AuthorizationContext filterContext)
    {
        HttpContextBase httpContext = filterContext.HttpContext;

        if (httpContext == null)
            throw new ArgumentNullException("filterContext.HttpContext");

        Trace.WriteLine("Current User: " + (httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "Anonymous"));
        if (!httpContext.User.Identity.IsAuthenticated)
            return false;

        string objectId = (httpContext.Request.RequestContext.RouteData.Values["id"] ?? Guid.Empty).ToString();
        Trace.WriteLine("Hierarchy Permissions check for Object: " + objectId);

        string controllerName = httpContext.Request.RequestContext.RouteData.GetRequiredString("controller");
        string actionName = httpContext.Request.RequestContext.RouteData.GetRequiredString("action");
        Trace.WriteLine("Policy Permissions check for Controller: " + controllerName + ", and Action: " + actionName);
        //if(!CheckHierarchyPermissions  || (!CheckHierarchyPermissions && !CheckBusinessLogicPermissions))
        //{
        //    //Check database permissions by getting DB reference from DependancyResolver
        //    DependencyResolver.Current.GetService(typeof (SecurityService)); //change this to an interface later
        //    return false;
        //}

        return true;
    }

    #region Old methods decorated with Obsolete() attributes to track down unintended uses
    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the Users collection.", true)]
    public new string Users { get; set; }

    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the Roles collection.", true)]
    public new string Roles { get; set; }

    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the AuthorizeCore method.", true)]
    protected new bool AuthorizeCore(HttpContextBase httpContext)
    {
        return false;
    }

    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the OnCacheAuthorization method.", true)]
    protected new virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
    {
        return HttpValidationStatus.Invalid;
    }
    #endregion
}

ОБНОВЛЕНИЕ: Просто быстрое обновление этого, я так и не нашел способ динамически создать имя роли, для которой я проверял, из комбинации имени действия и имени контроллера, и все еще работаю в пределах ограничений способа выполнения запросов и кеширования и т. д. Однако шаблон подхода WhiteList к авторизации, подробно описанный в блоге, на который я ссылался выше, включен в MVC4. MVC4 является бета-версией только на данный момент, но я не ожидаю, что они удалят его до настоящего момента и до финальной версии.

...