Як створити власну AuthorizeAttribute в ASP.NET Core?


428

Я намагаюся створити спеціальний атрибут авторизації в ASP.NET Core. У попередніх версіях можна було перекрити bool AuthorizeCore(HttpContextBase httpContext). Але цього в Росії вже не існує AuthorizeAttribute.

Який сучасний підхід зробити власну AuthorizeAttribute?

Що я намагаюся досягти: я отримую ідентифікатор сеансу під час авторизації заголовка. З цього ідентифікатора я буду знати, чи дійсна певна дія.


Я не впевнений, як це зробити, але MVC є відкритим кодом. Ви можете витягнути ретро github і шукати реалізації IAuthorizationFilter. Якщо я встигну сьогодні, я буду шукати вас і розміщу фактичну відповідь, але ніяких обіцянок. github repo: github.com/aspnet/Mvc
bopapa_1979

Гаразд, поза часом, але шукайте використання AuthorizationPolicy в MVC Repo, який використовує AuthorizeAttribute, в репост aspnet / Security, тут: github.com/aspnet/Security . Крім того, загляньте у репортаж MVC на простір імен, де, схоже, розміщені матеріали безпеки, які вам цікаві, а саме Microsoft.AspNet.Authorization. Вибачте, я не можу бути кориснішою. Удачі!
bopapa_1979

Відповіді:


446

Підхід , рекомендований команда ASP.Net ядра є використання нової розробки політики , яка повністю документована тут . Основна ідея нового підходу полягає у використанні нового атрибуту [Авторизувати] для позначення "політики" (наприклад, [Authorize( Policy = "YouNeedToBe18ToDoThis")]коли політика зареєстрована у програмі Startup.cs програми для виконання деякого блоку коду (тобто переконатися, що користувач має вікову претензію) де вік 18 років і старше).

Розробка політики є чудовим доповненням до основи, і команду ASP.Net Security Core слід оцінити за її впровадження. Однак це не дуже підходить для всіх випадків. Недолік цього підходу полягає в тому, що він не дає зручного рішення для найпоширенішої потреби просто стверджувати, що певний контролер або дія вимагає заданого типу претензії. У випадку, коли програма може мати сотні дискретних дозволів, що керують операціями CRUD на окремих ресурсах REST ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder" та ін.), Новий підхід вимагає повторюваних режимів " одне відображення між назвою політики та назвою претензії (наприклад,options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), або записати якийсь код для виконання цих реєстрацій під час виконання (наприклад, прочитати всі типи претензій з бази даних та виконати вищезазначений виклик у циклі). Проблема такого підходу для більшості випадків полягає в тому, що це зайві накладні витрати.

Хоча команда ASP.Net Core Security рекомендує ніколи не створювати власне рішення, в деяких випадках це може бути найдоцільнішим варіантом, з якого почати.

Далі йде реалізація, яка використовує IAuthorizationFilter, щоб забезпечити простий спосіб виразити вимогу вимоги до даного контролера або дії:

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}


[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}

78
Це слід позначити як ПРАВИЛЬНИЙ ВІДПОВІДЬ. Тут ви бачите, як люди в Microsoft розглядають відгуки розробників. Я не розумію причини, з якою вони так «закриті», оскільки дуже часто існує ситуація, коли існує безліч різних дозволів, кодування однієї політики для кожного - це повне перевищення. Я шукав це так довго ... (я вже задавав це питання майже два роки тому, коли vNext був ще ставкою тут: stackoverflow.com/questions/32181400/…, але ми все ще застрягли там)
Vi100

3
Це хороші речі. Ми маємо посередницьке програмне забезпечення для автентифікації у веб-API, але чітка захищеність дозволів авторизації за ролями; тому потрібно просто кинути атрибут типу: [MyAuthorize (MyClaimTypes.Permission, MyClaimValueTypes.Write, MyPermission.E Employee)] виглядає дуже добре.
Маріано Пейнадор

4
@Derek Greer: Це найкраща відповідь. Однак ви реалізуєте ActionFilter, який працює після авторизації фільтра дій. Чи все-таки є імплементація та авторизація фільтрів дій?
Яків Фан

6
@JacobPhan Ви маєте рацію, це було б краще реалізовано за допомогою інтерфейсу IAuthorizationFilter. Я оновив код, щоб відобразити зміни.
Дерек Грір

3
тому new ForbidResult()не працює (викликає виняток / 500), оскільки не має асоційованої схеми авторизації. Що б я використав для цього випадку?
Сінестетик

252

Я людина з безпеки asp.net. По-перше, дозвольте мені попросити вибачення, що нічого з цього не зафіксовано за межами зразка музичного магазину чи тестів, і все це ще вдосконалюється з точки зору відкритих API. Детальна документація тут .

Ми не хочемо, щоб ви писали власні атрибути авторизації. Якщо вам потрібно зробити це, ми зробили щось не так. Натомість слід писати вимоги щодо авторизації .

Авторизація діє на посвідчення особи. Ідентифікатори створюються аутентифікацією.

Ви говорите в коментарях, що хочете перевірити ідентифікатор сеансу в заголовку. Ваш ідентифікатор сеансу буде основою для посвідчення особи. Якщо ви хочете використовувати Authorizeатрибут, ви б написали проміжне програмне забезпечення для автентифікації, щоб взяти цей заголовок і перетворити його в автентифікований ClaimsPrincipal. Потім ви перевірите це всередині вимоги авторизації. Вимоги до авторизації можуть бути такими ж складними, як вам подобається, наприклад, ось такий, який вимагає дати народження заявки на поточну особу та надасть дозвіл, якщо користувачеві старше 18 років;

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
        public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
            {
                context.Fail();
                return;
            }

            var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
            int age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            if (age >= 18)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
    }
}

Тоді у своїй ConfigureServices()функції ви підключите це

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});

І, нарешті, застосуйте його до контролера чи методу дії за допомогою

[Authorize(Policy = "Over18")]

84
Мені цікаво ... як би з цим реалізувати тонкозернистий контроль доступу? Скажімо, ManageStoreзразок Вимога з музичного магазину. Як і в вибірці, існує лише спосіб "дозволити все або нічого". Чи потрібно нам тоді створювати нову політику для кожної можливої ​​перестановки? тобто "Користувачі / Прочитайте", "Користувачі / Створіть", "Користувачі / Призначте Ролі", "Користувачі / Видаліть", якщо ми хочемо дрібні претензії? Здається, це велика робота з налаштування, щоб вона працювала і безліч політик просто керувала претензіями, а не [ClaimsAutzorization("User", "Read", "Create", "Delete", "Assign")]атрибутом?
Tseng

83
Я маю зауважити, що все це складніше, ніж реалізація користувальницького методу авторизації. Я знаю, як я хочу зробити авторизацію, я можу просто перейти і записати її в MVC 5, в MVC 6 вони додають багато "зробленого" коду, який насправді є складнішим для розуміння, ніж реалізація самої основної "речі". Дозволяє мені сидіти перед сторінкою, намагаючись щось розібратися, а не писати код прямо, що також є великим болем для людей, які використовують RDBMS, крім Microsoft (або No-Sql).
Феліпе

17
З моєї точки зору, це не вирішує всіх сценаріїв. До MVC 6 я використовував власні атрибути авторизації, щоб реалізувати власну "Систему дозволів". Я можу додати атрибут "Авторизувати" до всіх дій і передати один необхідний дозвіл (як Enum-Value). Сам дозвіл відображається на групи / користувачів в БД. Отже, я не бачу способу впоратися з цим за допомогою політики !?
Гервальд

43
Я, як і багато інших у цих коментарях, дуже розчарований тим, що використання атрибутів для авторизації було настільки сильно скасовано через те, що було можливо в Web API 2. Вибачте, хлопці, але ваша абстракція "вимога" не охоплює жодного випадку, коли ми раніше могли використовувати параметри конструктора атрибутів для інформування базового алгоритму авторизації. Раніше було просто мертвим, щоб зробити щось подібне [CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]. Я міг би використовувати один спеціальний атрибут нескінченною кількістю способів, просто змінюючи параметри конструктора.
NathanAldenSr

61
Мене також шокує, що самопроголошений "хлопець із безпеки ASP.NET" насправді пропонує використовувати магічні рядки (хакерське значення IAuthorizeData.Policy) та спеціальні постачальники політики для подолання цього кричущого нагляду, а не звертатися до нього в рамках. Я думав, ми не повинні створювати власні реалізації? У вас не залишилось жодного вибору, крім повторної реалізації авторизації з нуля (знову ж таки), і цього разу навіть без переваги старого Authorizeатрибута Web API . Тепер ми маємо це зробити на рівні фільтра дій або на рівні проміжного програмного забезпечення.
NathanAldenSr

104

Схоже, що з ASP.NET Core 2 ви можете знову успадкувати AuthorizeAttribute, вам просто потрібно також реалізувати IAuthorizationFilter(або IAsyncAuthorizationFilter):

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}

4
Тож ви можете використовувати це лише для відмови в авторизації, а не для надання ?
ПАМ'ЯТ

1
@MEMark При наданні , ви маєте в виду перекриваючи ще один атрибут авторизації?
gius

2
AFAIK, доступ дозволений за замовчуванням, тому вам потрібно явно заборонити його (наприклад, додавши AuthorizeAttribute). Перевірте це питання для більш докладної інформації: stackoverflow.com/questions/17272422 / ...
Gius

16
Також зауважте, у запропонованому прикладі не потрібно успадковувати від AuthorizeAttribute. Ви можете успадковувати з Attribute та IAuthorizationFilter . Таким чином, ви б не отримали наступне виняток, якщо використовується якийсь нестандартний механізм аутентифікації: InvalidOperationException: Не було вказано схеми аутентифікації, і не було знайдено DefaultChallengeScheme.
Анатолійович

13
Зауважте, що якщо для вашої OnAuthorizationреалізації потрібно чекати методу асинхронізації, вам слід реалізувати IAsyncAuthorizationFilterзамість того, IAuthorizationFilterінакше ваш фільтр буде виконуватися синхронно, а дія контролера буде виконуватися незалежно від результату фільтра.
Codemunkie

34

На основі Дерек Грір ВЕЛИКОГО відповіді, я зробив це з перерахуваннями.

Ось приклад мого коду:

public enum PermissionItem
{
    User,
    Product,
    Contact,
    Review,
    Client
}

public enum PermissionAction
{
    Read,
    Create,
}


public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(PermissionItem item, PermissionAction action)
    : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { item, action };
    }
}

public class AuthorizeActionFilter : IAuthorizationFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

        if (!isAuthorized)
        {
            context.Result = new ForbidResult();
        }
    }
}

public class UserController : BaseController
{
    private readonly DbContext _context;

    public UserController( DbContext context) :
        base()
    {
        _logger = logger;
    }

    [Authorize(PermissionItem.User, PermissionAction.Read)]
    public async Task<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}

1
Дякую за це Я створив цей пост з дещо іншою реалізації і запит перевірки stackoverflow.com/questions/49551047 / ...
Антон Swanevelder

2
MumboJumboFunction <3
Marek

31

Ви можете створити власний AuthorizationHandler, який знайде власні атрибути у ваших контролерах та діях та передасть їх методу HandleRequirementAsync.

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
        if (action != null)
        {
            attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
            attributes.AddRange(GetAttributes(action.MethodInfo));
        }

        return HandleRequirementAsync(context, requirement, attributes);
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

Тоді ви можете використовувати його для будь-яких спеціальних атрибутів, необхідних вашим контролерам або діям. Наприклад, щоб додати вимоги дозволу. Просто створіть свій власний атрибут.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}

Потім створіть вимогу, яку потрібно додати до своєї політики

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}

Потім створіть AuthorizationHandler для власного атрибута, успадкувавши AttributeAuthorizationHandler, який ми створили раніше. Він буде переданий IEnumerable для всіх ваших користувальницьких атрибутів у методі HandleRequirementsAsync, накопичений з вашого Controller and Action.

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

І нарешті, у вашому методі Startup.cs ConfigureServices додайте до служб власний авторизаційний сервер і додайте свою політику.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Permission", policyBuilder =>
            {
                policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
            });
        });

Тепер ви можете просто прикрасити контролери та дії за допомогою власного атрибута.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}

1
Я погляну на це якнайшвидше.
NathanAldenSr

5
Це досить переосмислено ... Я вирішив те саме, використовуючи просту AuthorizationFilterAttribute, яка отримує параметр. Для цього вам не потрібні роздуми, це здається навіть більш вигадливим, ніж "офіційне" рішення (що я вважаю досить бідним).
Vi100

2
@ Vi100 Не вдалося знайти багато інформації про AuthorizationFilters в ASP.NET Core. На офіційній сторінці документації йдеться, що вони зараз працюють над цією темою. docs.microsoft.com/en-us/aspnet/core/security/authorization/…
Shawn

4
@ Vi100 Чи можете ви поділитися своїм рішенням, якщо є простіший спосіб досягти цього, я хотів би знати.
Шон

2
Одне зауважити, що використання UnderlyingSystemType вище не компілюється, але видалення, здається, працює.
Час

25

Який сучасний підхід зробити власну AuthorizeAttribute

Легко: не створюйте своїх AuthorizeAttribute.

Для чистого сценарію авторизації (наприклад, обмеження доступу лише до конкретних користувачів) рекомендується використовувати новий блок авторизації: https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L -L92

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

Для аутентифікації найкраще обробляти на рівні середнього програмного забезпечення.

Чого саме ви намагаєтесь досягти?


1
Я отримую ідентифікатор сеансу в розділі Авторизація заголовка. З цього ідентифікатора я буду знати, чи дійсна певна дія.
jltrem

1
Тоді це не стосується авторизації. Я думаю, що ваш "ідентифікатор сеансу" - це маркер, що містить особу абонента: це, безумовно, слід робити на рівні середнього програмного забезпечення.
Шале Кевін

3
Це не аутентифікація (встановлення того, хто такий користувач), але це авторизація (визначення, чи повинен користувач мати доступ до ресурсу). Тож де ти пропонуєш мені шукати, щоб вирішити це?
jltrem

3
@jltrem, погоджено, те, про що ви говорите, це авторизація, а не автентифікація.
bopapa_1979

2
@Pinpoint Я не є. Я запитую іншу систему для цієї інформації. Ця система перевіряє автентифікацію (визначає користувача) та авторизує (повідомляє мені, що користувач може отримати доступ). На даний момент у мене зламано роботу, викликаючи метод у кожній дії контролера, щоб інша система перевірила сеанс. Я хотів би, щоб це автоматично відбувалося через атрибут.
jltrem

4

Якщо хтось просто хоче перевірити маркер носія на етапі авторизації, використовуючи поточні практики безпеки,

додайте це до своїх програм запуску / налаштування

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

і це у вашій кодовій базі,

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}

public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{

    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}

Якщо код не дійде, context.Succeed(...)він все одно вийде з ладу (401).

А потім у своїх контролерах ви можете використовувати

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Чому ви вирішили виконати власну перевірку маркера, коли середнє програмне забезпечення JwtBearer вже переймається цим? Він також розміщує правильний вміст у заголовку відповіді WWW-Authenticate для помилки перевірки автентичності / лексеми / закінчення терміну дії. Якщо ви хочете отримати доступ до конвеєра автентифікації, є конкретні події, за допомогою яких ви можете скористатися параметрами AddJwtBearer (OnAuthenticationFailed, OnChallenge, OnMessageReceived та OnTokenValidated).
Даррен Льюїс

Це нескінченно простіше, ніж будь-яке інше рішення, яке я бачив. Особливо для простих випадків використання ключових програм. Одне оновлення: для 3.1 передача на AuthorizationFilterContext більше не діє, оскільки дані про маршрутизацію кінцевої точки. Вам потрібно схопити контекст через HttpContextAccessor.
JasonCoder

2

Сучасний спосіб - це AuthenticationHandlers

в startup.cs додати

services.AddAuthentication("BasicAuthentication").AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IUserService userService)
            : base(options, logger, encoder, clock)
        {
            _userService = userService;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");

            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }

            if (user == null)
                return AuthenticateResult.Fail("Invalid User-name or Password");

            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }

IUserService - це послуга, яку ви робите там, де у вас є ім’я користувача та пароль. в основному він повертає клас користувача, який ви використовуєте для складання своїх претензій.

var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            }; 

Тоді ви можете запитувати ці претензії та її будь-які дані, які ви накреслили, їх досить багато, подивіться на клас ClaimTypes

Ви можете використовувати це в методі розширення і отримати будь-яке зі відображень

public int? GetUserId()
{
   if (context.User.Identity.IsAuthenticated)
    {
       var id=context.User.FindFirst(ClaimTypes.NameIdentifier);
       if (!(id is null) && int.TryParse(id.Value, out var userId))
            return userId;
     }
      return new Nullable<int>();
 }

Цей новий спосіб, я думаю, є кращим, ніж

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            var authToken = actionContext.Request.Headers.Authorization.Parameter;
            // decoding authToken we get decode value in 'Username:Password' format
            var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
            // spliting decodeauthToken using ':'
            var arrUserNameandPassword = decodeauthToken.Split(':');
            // at 0th postion of array we get username and at 1st we get password
            if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
            {
                // setting current principle
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
            }
            else
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }

    public static bool IsAuthorizedUser(string Username, string Password)
    {
        // In this method we can handle our database logic here...
        return Username.Equals("test") && Password == "test";
    }
}

Ця блискуча відповідь просто працює як шарм! Дякую за це, і я бажаю, щоб це було прийнято, оскільки це найкраща відповідь, яку я знайшов після шести годин пошуку через блоги, документацію та стек для базової аутентифікації та авторизації ролей.
Piotr Śródka

@ PiotrŚródka, ласкаво просимо, зауважте, що відповідь трохи "спрощена", перевіряйте, чи є у вас текст ":", оскільки зловмисник може спробувати зламати вашу послугу, просто не граючи приємне закінчення в індексі виняток діапазону як завжди перевіряйте те, що вам дають зовнішні джерела
Вальтер Веховен

2

З моменту написання цього повідомлення, я вважаю, що це можна досягти за допомогою інтерфейсу IClaimsTransformation в ядрі 2 і вище версії asp.net. Я щойно реалізував доказ концепції, яку можна розмістити тут.

public class PrivilegesToClaimsTransformer : IClaimsTransformation
{
    private readonly IPrivilegeProvider privilegeProvider;
    public const string DidItClaim = "http://foo.bar/privileges/resolved";

    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)
    {
        this.privilegeProvider = privilegeProvider;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is ClaimsIdentity claimer)
        {
            if (claimer.HasClaim(DidItClaim, bool.TrueString))
            {
                return principal;
            }

            var privileges = await this.privilegeProvider.GetPrivileges( ... );
            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));

            foreach (var privilegeAsRole in privileges)
            {
                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));
            }
        }

        return principal;
    }
}

Щоб використовувати це у своєму контролері, просто додайте відповідні [Authorize(Roles="whatever")]методи.

[HttpGet]
[Route("poc")]
[Authorize(Roles = "plugh,blast")]
public JsonResult PocAuthorization()
{
    var result = Json(new
    {
        when = DateTime.UtcNow,
    });

    result.StatusCode = (int)HttpStatusCode.OK;

    return result;
}

У нашому випадку кожен запит включає заголовок авторизації, який є JWT. Це прототип, і я вірю, що ми зробимо щось надзвичайно близьке до цього у нашій виробничій системі на наступному тижні.

Майбутні виборці, враховуйте дату написання під час голосування. На сьогоднішній день, це works on my machine.™ Ви, ймовірно, захочете більше обробляти помилки та входити в систему під час своєї реалізації.


А що з ConfigureServices? Чи потрібно щось додати?
Даниїл

Як обговорювалося в іншому місці, так.
Без повернення коштів не повертається

1

Для авторизації в нашому додатку. Нам довелося викликати службу на основі параметрів, переданих в атрибуті авторизації.

Наприклад, якщо ми хочемо перевірити, чи може користувач, який увійшов до лікаря, переглянув призначення пацієнтів, ми передамо «Перегляд_призначення» на спеціальний атрибут авторизації та перевіримо це право в службі БД, і на основі результатів ми надамо дозвол. Ось код цього сценарію:

    public class PatientAuthorizeAttribute : TypeFilterAttribute
    {
    public PatientAuthorizeAttribute(params PatientAccessRights[] right) : base(typeof(AuthFilter)) //PatientAccessRights is an enum
    {
        Arguments = new object[] { right };
    }

    private class AuthFilter : IActionFilter
    {
        PatientAccessRights[] right;

        IAuthService authService;

        public AuthFilter(IAuthService authService, PatientAccessRights[] right)
        {
            this.right = right;
            this.authService = authService;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var allparameters = context.ActionArguments.Values;
            if (allparameters.Count() == 1)
            {
                var param = allparameters.First();
                if (typeof(IPatientRequest).IsAssignableFrom(param.GetType()))
                {
                    IPatientRequest patientRequestInfo = (IPatientRequest)param;
                    PatientAccessRequest userAccessRequest = new PatientAccessRequest();
                    userAccessRequest.Rights = right;
                    userAccessRequest.MemberID = patientRequestInfo.PatientID;
                    var result = authService.CheckUserPatientAccess(userAccessRequest).Result; //this calls DB service to check from DB
                    if (result.Status == ReturnType.Failure)
                    {
                        //TODO: return apirepsonse
                        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
                    }
                }
                else
                {
                    throw new AppSystemException("PatientAuthorizeAttribute not supported");
                }
            }
            else
            {
                throw new AppSystemException("PatientAuthorizeAttribute not supported");
            }
        }
    }
}

І в API дії ми використовуємо це так:

    [PatientAuthorize(PatientAccessRights.PATIENT_VIEW_APPOINTMENTS)] //this is enum, we can pass multiple
    [HttpPost]
    public SomeReturnType ViewAppointments()
    {

    }

1
Зауважте, що IActionFilter буде проблемою, коли ви хочете використовувати той самий атрибут для методів Hub у SignalR.SignalR Hubs очікують IAuthorizationFilter
ilkerkaran

Дякую за інформацію. Я зараз не використовую SignalR у своїй програмі, тому я не перевіряв його з ним.
Абдулла

Я вважаю, що, як і раніше, доведеться використовувати авторизацію заголовка, реалізація буде відрізнятися
Walter Vehoeven

0

Прийнята відповідь ( https://stackoverflow.com/a/41348219/4974715 ) не є реально можливою або підходящою, оскільки "CanReadResource" використовується як претензія (але по суті повинна бути політикою в реальності, IMO). Підхід у відповіді не в порядку в тому, як він використовувався, тому що якщо метод дії вимагає безлічі різних налаштувань претензій, то з цією відповіддю вам доведеться повторно написати щось на кшталт ...

[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")] 
[ClaimRequirement(MyClaimTypes.AnotherPermision, "AnotherClaimVaue")]
//and etc. on a single action.

Отже, уявіть, скільки кодування знадобиться. В ідеалі "CanReadResource" повинен бути політикою, яка використовує багато претензій, щоб визначити, чи може користувач читати ресурс.

Що я роблю, це я створювати свою політику як перерахування, а потім перебирати та встановлювати такі вимоги, як, таким чином ...

services.AddAuthorization(authorizationOptions =>
        {
            foreach (var policyString in Enum.GetNames(typeof(Enumerations.Security.Policy)))
            {
                authorizationOptions.AddPolicy(
                    policyString,
                    authorizationPolicyBuilder => authorizationPolicyBuilder.Requirements.Add(new DefaultAuthorizationRequirement((Enumerations.Security.Policy)Enum.Parse(typeof(Enumerations.Security.Policy), policyWrtString), DateTime.UtcNow)));

      /* Note that thisn does not stop you from 
          configuring policies directly against a username, claims, roles, etc. You can do the usual.
     */
            }
        }); 

Клас DefaultAuthorizationRequirement виглядає як ...

public class DefaultAuthorizationRequirement : IAuthorizationRequirement
{
    public Enumerations.Security.Policy Policy {get; set;} //This is a mere enumeration whose code is not shown.
    public DateTime DateTimeOfSetup {get; set;} //Just in case you have to know when the app started up. And you may want to log out a user if their profile was modified after this date-time, etc.
}

public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
{
    private IAServiceToUse _aServiceToUse;

    public DefaultAuthorizationHandler(
        IAServiceToUse aServiceToUse
        )
    {
        _aServiceToUse = aServiceToUse;
    }

    protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
    {
        /*Here, you can quickly check a data source or Web API or etc. 
           to know the latest date-time of the user's profile modification...
        */
        if (_aServiceToUse.GetDateTimeOfLatestUserProfileModication > requirement.DateTimeOfSetup)
        {
            context.Fail(); /*Because any modifications to user information, 
            e.g. if the user used another browser or if by Admin modification, 
            the claims of the user in this session cannot be guaranteed to be reliable.
            */
            return;
        }

        bool shouldSucceed = false; //This should first be false, because context.Succeed(...) has to only be called if the requirement specifically succeeds.

        bool shouldFail = false; /*This should first be false, because context.Fail() 
        doesn't have to be called if there's no security breach.
        */

        // You can do anything.
        await doAnythingAsync();

       /*You can get the user's claims... 
          ALSO, note that if you have a way to priorly map users or users with certain claims 
          to particular policies, add those policies as claims of the user for the sake of ease. 
          BUT policies that require dynamic code (e.g. checking for age range) would have to be 
          coded in the switch-case below to determine stuff.
       */

        var claims = context.User.Claims;

        // You can, of course, get the policy that was hit...
        var policy = requirement.Policy

        //You can use a switch case to determine what policy to deal with here...
        switch (policy)
        {
            case Enumerations.Security.Policy.CanReadResource:
                 /*Do stuff with the claims and change the 
                     value of shouldSucceed and/or shouldFail.
                */
                 break;
            case Enumerations.Security.Policy.AnotherPolicy:
                 /*Do stuff with the claims and change the 
                    value of shouldSucceed and/or shouldFail.
                 */
                 break;
                // Other policies too.

            default:
                 throw new NotImplementedException();
        }

        /* Note that the following conditions are 
            so because failure and success in a requirement handler 
            are not mutually exclusive. They demand certainty.
        */

        if (shouldFail)
        {
            context.Fail(); /*Check the docs on this method to 
            see its implications.
            */
        }                

        if (shouldSucceed)
        {
            context.Succeed(requirement); 
        } 
     }
}

Зауважте, що наведений вище код також може включати попереднє відображення користувача до політики у вашому сховищі даних. Таким чином, складаючи претензії для користувача, ви в основному отримуєте політику, попередньо відображену на карті безпосередньо користувачеві (наприклад, тому, що користувач має певне значення претензії, і це значення претензії було визначено та відображено до політики, наприклад, що воно забезпечує автоматичне відображення для користувачів, які мають і це значення претензії), і зараховує політику як претензії, так що в обробнику авторизації ви можете просто перевірити, чи містяться вимоги користувача. претензії. Це стосується статичного способу задоволення вимог політики, наприклад, вимога "Ім'я" має досить статичний характер. Тому,

[Authorize(Policy = nameof(Enumerations.Security.Policy.ViewRecord))] 

Динамічна вимога може стосуватися перевірки вікового діапазону тощо. Політики, які використовують такі вимоги, не можуть бути попередньо відображені для користувачів.

Приклад динамічної перевірки претензій на політику (наприклад, щоб перевірити, чи користувачеві старше 18 років), вже є відповідь, надана @blowdart ( https://stackoverflow.com/a/31465227/4974715 ).

PS: Я набрав це на своєму телефоні. Пробачте про помилки друку і відсутність форматування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.