Подтвердить что ты не робот

Вопросы о настройке пользовательской безопасности для MVC3 с использованием переопределенного атрибута AuthorizeAttribute, безопасности потоков, ChildActions и кеширования

Итак, после поиска надежного решения безопасности для моего приложения 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, так как единственная ответственность - вернуть true false, имеет ли пользователь доступ. Однако у меня есть проблема: AuthorizeCore должен быть потокобезопасным, исходя из моего чтения источника для класса AuthorizeAttribute, и я не уверен, что лучший способ обратиться к моей базе данных, чтобы определить права пользователя и придерживаться этого. Мое приложение использует IoC и в настоящее время разрешает моему контейнеру IoC мой репозиторий обрабатывать все это конструктору AuthorizeAttribute, но, делая это, а затем получая доступ к нему в AuthorizeCore, я не создаю проблем с безопасностью потоков? Или будет реализация IoC и MVC3 DependencyResolver, которые я использую для предоставления параметра моему настраиваемому конструктору AuthorizeAttribute, адекватно обрабатывают безопасность потока? Обратите внимание: в моих репозиториях используется шаблон UnitOfWork, который включает в себя мой nHibernate SessionFactory в качестве конструктора для репозитория, а класс Unit of Work предоставляется из моего контейнера IoC, реализованного StructureMap, используя приведенную ниже строку. Правильно ли я рассматриваю используемую здесь область будет обрабатывать проблемы с потоками?

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

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

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

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

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

Общие сведения:

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

Любые мнения или помощь в этих аспектах были бы очень благодарны!

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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