Bagaimana Anda membuat CustomizeAttribute kustom di ASP.NET Core?

428

Saya mencoba membuat atribut otorisasi khusus di ASP.NET Core. Dalam versi sebelumnya, dimungkinkan untuk mengganti bool AuthorizeCore(HttpContextBase httpContext). Tetapi ini tidak lagi ada di AuthorizeAttribute.

Apa pendekatan saat ini untuk membuat AuthorizeAttribute kustom?

Apa yang ingin saya capai: Saya menerima ID sesi di Header Authorization. Dari ID itu saya akan tahu apakah tindakan tertentu itu valid.

jltrem
sumber
Saya tidak yakin bagaimana melakukannya, tetapi MVC adalah open source. Anda dapat menarik repo github dan mencari implementasi IAuthorizationFilter. Jika saya punya waktu hari ini saya akan mencari Anda dan mengirim jawaban yang sebenarnya, tetapi tidak ada janji. github repo: github.com/aspnet/Mvc
bopapa_1979
OK, kehabisan waktu, tetapi cari penggunaan AuthorizationPolicy di Repo MVC, yang menggunakan AuthorizeAttribute, di repo aspnet / Keamanan, di sini: github.com/aspnet/Security . Sebagai alternatif, lihat di repo MVC untuk namespace di mana hal-hal keamanan yang Anda pedulikan tampaknya berada, yaitu Microsoft.AspNet.Authorization. Maaf saya tidak bisa lebih membantu. Semoga berhasil!
bopapa_1979

Jawaban:

446

Pendekatan yang direkomendasikan oleh tim ASP.Net Core adalah dengan menggunakan desain kebijakan baru yang sepenuhnya didokumentasikan di sini . Ide dasar di balik pendekatan baru ini adalah menggunakan atribut [Otorisasi] baru untuk menunjuk "kebijakan" (mis. Di [Authorize( Policy = "YouNeedToBe18ToDoThis")]mana kebijakan terdaftar di Startup.cs aplikasi untuk menjalankan beberapa blok kode (yaitu memastikan pengguna memiliki klaim usia) di mana usianya 18 tahun atau lebih).

Desain kebijakan adalah tambahan yang bagus untuk kerangka kerja dan tim Inti Keamanan ASP.Net harus dipuji karena pengenalannya. Yang mengatakan, itu tidak cocok untuk semua kasus. Kelemahan dari pendekatan ini adalah bahwa ia gagal memberikan solusi yang nyaman untuk kebutuhan yang paling umum hanya dengan menyatakan bahwa controller atau aksi yang diberikan memerlukan tipe klaim yang diberikan. Dalam kasus di mana aplikasi mungkin memiliki ratusan izin terpisah yang mengatur operasi CRUD pada sumber daya REST individual ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", dll.), Pendekatan baru ini membutuhkan pengulangan satu-ke- satu pemetaan antara nama kebijakan dan nama klaim (misoptions.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), atau menulis beberapa kode untuk melakukan pendaftaran ini pada waktu berjalan (mis. membaca semua jenis klaim dari database dan melakukan panggilan tersebut dalam satu lingkaran). Masalah dengan pendekatan ini untuk sebagian besar kasus adalah bahwa itu tidak perlu overhead.

Meskipun tim ASP.Net Core Security merekomendasikan untuk tidak pernah membuat solusi Anda sendiri, dalam beberapa kasus ini mungkin merupakan opsi yang paling bijaksana untuk memulai.

Berikut ini adalah implementasi yang menggunakan IAuthorizationFilter untuk menyediakan cara sederhana untuk mengekspresikan persyaratan klaim untuk kontroler atau tindakan yang diberikan:

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();
    }
}
Derek Greer
sumber
79
Ini harus ditandai sebagai JAWABAN YANG BENAR. Di sini Anda melihat bagaimana orang-orang di Microsoft mempertimbangkan umpan balik pengembang. Saya tidak mengerti alasan mengapa mereka "berpikiran tertutup" di sekitar ini, karena ini adalah situasi yang sangat umum untuk memiliki miriad dengan izin yang berbeda, harus kode satu kebijakan untuk masing-masing kebijakan adalah kerja keras total. Saya sudah lama mencari ini ... (Saya sudah menanyakan pertanyaan ini hampir dua tahun yang lalu, ketika vNext masih bertaruh di sini: stackoverflow.com/questions/32181400/... tapi kami masih terjebak di sana)
Vi100
3
Ini barang bagus. Kami memiliki middleware otentikasi di API Web tetapi memberikan keamanan pada izin otorisasi berdasarkan peran; jadi harus membuang atribut seperti: [MyAuthorize (MyClaimTypes.Permission, MyClaimValueTypes.Write, MyPermission.Employee)] terlihat sangat bagus.
Mariano Peinador
4
@Derek Greer: Ini adalah jawaban terbaik. Namun, Anda menerapkan ActionFilter yang berjalan setelah Otorisasi Tindakan Filter. Apakah ada cara menerapkan dan Otorisasi Tindakan Filter?
Jacob Phan
6
@ JacobPhan Anda benar, ini akan lebih baik diimplementasikan menggunakan antarmuka IAuthorizationFilter. Saya telah memperbarui kode untuk mencerminkan perubahan.
Derek Greer
3
jadi new ForbidResult()tidak berfungsi (menyebabkan pengecualian / 500) karena tidak memiliki skema otorisasi terkait. Apa yang akan saya gunakan untuk kasus ini?
Sinaesthetic
252

Saya orang keamanan asp.net. Pertama, izinkan saya meminta maaf bahwa semua ini belum didokumentasikan di luar sampel toko musik atau unit test, dan semuanya masih disempurnakan dalam hal API yang terbuka. Dokumentasi terperinci ada di sini .

Kami tidak ingin Anda menulis atribut otorisasi khusus. Jika Anda perlu melakukan itu, kami telah melakukan kesalahan. Sebaliknya, Anda harus menulis persyaratan otorisasi .

Otorisasi bertindak atas Identitas. Identitas dibuat dengan otentikasi.

Anda mengatakan dalam komentar Anda ingin memeriksa ID sesi di header. ID sesi Anda akan menjadi dasar untuk identitas. Jika Anda ingin menggunakan Authorizeatribut Anda akan menulis middleware otentikasi untuk mengambil header itu dan mengubahnya menjadi dikonfirmasi ClaimsPrincipal. Anda kemudian akan memeriksa di dalam persyaratan otorisasi. Persyaratan otorisasi dapat serumit yang Anda inginkan, misalnya di sini adalah yang mengambil tanggal klaim kelahiran pada identitas saat ini dan akan mengesahkan jika pengguna berusia di atas 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();
            }
        }
    }
}

Kemudian dalam ConfigureServices()fungsi Anda, Anda akan menghubungkannya

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

Dan akhirnya, terapkan ke metode kontrol atau tindakan dengan

[Authorize(Policy = "Over18")]
blowdart
sumber
84
Saya bertanya-tanya ... bagaimana orang menerapkan kontrol akses berbutir halus dengan itu? Katakanlah ManageStorePersyaratan dari sampel Toko Musik. Seperti yang ada dalam sampel, hanya ada cara "mengizinkan semua atau tidak sama sekali" untuk melakukannya. Apakah kita kemudian harus membuat kebijakan baru untuk setiap permutasi yang mungkin? yaitu "Pengguna / Baca", "Pengguna / Buat", "Pengguna / AssignRole", "Pengguna / Hapus" jika kita ingin klaim berbutir halus? Kedengarannya seperti pengaturan kerja yang cukup banyak untuk membuatnya bekerja dan banyak kebijakan hanya untuk mengelola klaim daripada [ClaimsAutzorization("User", "Read", "Create", "Delete", "Assign")]atribut?
Tseng
84
Saya harus berkomentar, semua ini lebih kompleks daripada menerapkan metode otorisasi khusus. Saya tahu bagaimana saya ingin otorisasi dilakukan, saya bisa pergi dan menulisnya di MVC 5, di MVC 6 mereka menambahkan banyak kode "selesai" yang sebenarnya lebih kompleks untuk dipahami daripada mengimplementasikan inti "benda" itu sendiri. Membuat saya duduk di depan halaman mencoba mencari tahu alih-alih menulis kode, juga menyusahkan orang yang menggunakan RDBMS selain dari Microsoft (atau No-Sql).
Felype
17
Dari sudut pandang saya, ini tidak menyelesaikan semua skenario. Sebelum MVC 6, saya menggunakan Atribut Otorisasi kustom, untuk menerapkan "Sistem Izin" saya sendiri. Saya bisa menambahkan atribut Otorisasi ke semua tindakan, dan memberikan satu izin khusus yang diperlukan (sebagai Enum-Value). Izin itu sendiri dipetakan ke grup / pengguna dalam DB. Jadi, saya tidak melihat cara untuk menangani ini dengan kebijakan !?
Gerwald
43
Saya, seperti banyak orang lain dalam komentar ini, sangat kecewa karena menggunakan atribut untuk otorisasi telah sangat dikebiri atas apa yang mungkin terjadi di Web API 2. Maaf teman-teman, tetapi abstraksi "persyaratan" Anda gagal untuk menutupi setiap kasus di mana sebelumnya kami dapat menggunakan atribut konstruktor parameter untuk menginformasikan algoritma otorisasi yang mendasarinya. Dulu otak-sederhana mati untuk melakukan sesuatu seperti [CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]. Saya dapat menggunakan atribut khusus tunggal dalam jumlah tak terbatas hanya dengan memodifikasi parameter konstruktor.
NathanAldenSr
61
Saya juga terkejut bahwa memproklamirkan diri "Lead ASP.NET security guy" sebenarnya menyarankan untuk menggunakan string ajaib (peretasan makna IAuthorizeData.Policy) dan penyedia kebijakan kustom untuk mengatasi pengawasan terang-terangan ini, daripada mengatasinya dalam kerangka kerja. Saya pikir kami tidak seharusnya membuat implementasi kami sendiri? Anda telah membuat beberapa dari kami tidak punya pilihan selain menerapkan kembali otorisasi dari awal (lagi), dan kali ini bahkan tanpa manfaat dari Authorizeatribut lama Web API . Sekarang kita harus melakukannya pada filter tindakan atau level middleware.
NathanAldenSr
104

Tampaknya dengan ASP.NET Core 2, Anda dapat mewarisi lagi AuthorizeAttribute, Anda hanya perlu menerapkan IAuthorizationFilter(atau 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;
        }
    }
}
jenius
sumber
4
Jadi, Anda hanya dapat menggunakan ini untuk menolak otorisasi, bukan memberikannya ?
MEMark
1
@MEMark Dengan memberikan , maksud Anda mengganti atribut otorisasi lain?
gius
2
AFAIK, akses diizinkan secara default, jadi Anda harus menolaknya secara eksplisit (misalnya, dengan menambahkan AuthorizeAttribute). Periksa pertanyaan ini untuk lebih jelasnya: stackoverflow.com/questions/17272422/…
gius
16
Perhatikan juga, dalam contoh yang disarankan seseorang tidak harus mewarisi dari AuthorizeAttribute. Anda dapat mewarisi dari Atribut dan IAuthorizationFilter . Dengan cara ini Anda tidak akan mendapatkan pengecualian berikut jika beberapa mekanisme otentikasi non-standar digunakan: InvalidOperationException: Tidak ada otentikasiSkema yang ditentukan, dan tidak ditemukan DefaultChallengeScheme.
Anatolyevich
13
Perhatikan bahwa jika OnAuthorizationimplementasi Anda perlu menunggu metode async, Anda harus menerapkan IAsyncAuthorizationFilteralih-alih IAuthorizationFilterjika tidak filter Anda akan dijalankan secara sinkron dan tindakan pengontrol Anda akan mengeksekusi terlepas dari hasil filter.
Codemunkie
34

Berdasarkan jawaban Derek Greer HEBAT , saya melakukannya dengan enum.

Ini contoh kode saya:

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());
    }
}
bruno.almeida
sumber
1
Terima kasih untuk ini. Saya membuat posting ini dengan implementasi yang sedikit berbeda dan permintaan untuk validasi stackoverflow.com/questions/49551047/…
Anton Swanevelder
2
Fungsi MumboJumbo <3
Marek Urbanowicz
31

Anda dapat membuat AuthorizationHandler Anda sendiri yang akan menemukan atribut khusus pada Pengontrol dan Tindakan Anda, dan meneruskannya ke metode 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>();
    }
}

Kemudian Anda dapat menggunakannya untuk atribut khusus apa pun yang Anda butuhkan pada pengontrol atau tindakan Anda. Misalnya menambahkan persyaratan izin. Cukup buat atribut khusus Anda.

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

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

Kemudian buat Persyaratan untuk ditambahkan ke Kebijakan Anda

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

Lalu buat OtorisasiHandler untuk atribut khusus Anda, mewarisi AttributeAuthorizationHandler yang kami buat sebelumnya. Itu akan melewati IEnumerable untuk semua atribut khusus Anda dalam metode HandleRequirementsAsync, diakumulasikan dari Controller dan Action Anda.

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
    }
}

Dan akhirnya, dalam metode Startup.cs ConfigureServices Anda, tambahkan AuthorizationHandler kustom Anda ke layanan, dan tambahkan Kebijakan Anda.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

Sekarang Anda cukup menghias Pengendali dan Tindakan Anda dengan atribut khusus Anda.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}
Shawn
sumber
1
Saya akan melihat ASAP ini.
NathanAldenSr
5
Ini sangat overengineered ... Saya diselesaikan dengan menggunakan AuthorizationFilterAttribute yang menerima parameter. Anda tidak perlu refleksi untuk ini, tampaknya bahkan lebih artifisial daripada solusi "resmi" (yang saya temukan sangat buruk).
Vi100
2
@ Vi100 Saya tidak dapat menemukan banyak informasi tentang AuthorizationFilters di ASP.NET Core. Halaman dokumentasi resmi mengatakan mereka sedang mengerjakan topik ini. docs.microsoft.com/en-us/aspnet/core/security/authorization/…
Shawn
4
@ Vi100 Bisakah Anda membagikan solusi Anda, jika ada cara yang lebih sederhana untuk mencapai ini, saya ingin tahu.
Shawn
2
Satu hal yang perlu diperhatikan penggunaan UnderlyingSystemType di atas tidak dapat dikompilasi, tetapi menghapusnya sepertinya berfungsi.
minum
25

Apa pendekatan saat ini untuk membuat AuthorizeAttribute kustom

Mudah: jangan buat sendiri AuthorizeAttribute.

Untuk skenario otorisasi murni (seperti membatasi akses hanya ke pengguna tertentu), pendekatan yang disarankan adalah menggunakan blok otorisasi baru: https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startupcs## -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() { ... }
}

Untuk otentikasi, sebaiknya ditangani di tingkat middleware.

Apa yang sebenarnya ingin Anda capai?

Kévin Chalet
sumber
1
Saya menerima ID sesi di Otorisasi Header. Dari ID itu saya akan tahu apakah tindakan tertentu itu valid.
jltrem
1
Maka itu bukan masalah otorisasi. Saya kira "ID sesi" Anda sebenarnya adalah token yang berisi identitas penelepon: ini pasti harus dilakukan di tingkat middleware.
Kévin Chalet
3
Ini bukan otentikasi (menetapkan siapa pengguna) tetapi otorisasi (menentukan apakah pengguna harus memiliki akses ke sumber daya). Jadi di mana Anda menyarankan saya mencari untuk menyelesaikan ini?
jltrem
3
@ jltrem, setuju, yang Anda bicarakan adalah otorisasi, bukan otentikasi.
bopapa_1979
2
@Poinpoint saya tidak. Saya meminta sistem lain untuk info itu. Sistem itu mengotentikasi (menentukan pengguna) dan memberikan otorisasi (memberi tahu saya apa yang dapat diakses pengguna itu). Saat ini saya telah meretasnya untuk bekerja dengan memanggil metode di setiap tindakan pengontrol untuk meminta sistem lain memverifikasi sesi. Saya ingin ini terjadi secara otomatis melalui atribut.
jltrem
4

Jika ada yang hanya ingin memvalidasi token pembawa di fase otorisasi menggunakan praktik keamanan saat ini yang Anda bisa,

tambahkan ini ke Startup / ConfigureServices Anda

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

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

dan ini di basis kode Anda,

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);
            }
        }
    }
}

Jika kode tidak mencapai context.Succeed(...) itu akan gagal (401).

Dan kemudian di controller Anda dapat Anda gunakan

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Gabriel P.
sumber
Mengapa Anda memilih untuk melakukan validasi token Anda sendiri ketika middleware JwtBearer sudah menangani ini? Itu juga menempatkan konten yang benar di header respons WWW-Otentikasi untuk kegagalan validasi / validasi token / kedaluwarsa. Jika Anda ingin akses ke pipa otentikasi ada beberapa peristiwa khusus yang dapat Anda manfaatkan pada opsi AddJwtBearer (OnAuthenticationFailed, OnChallenge, OnMessageRivedived dan OnTokenValidated).
Darren Lewis
Ini jauh lebih sederhana daripada solusi lain yang pernah saya lihat. Khusus untuk kasus penggunaan kunci api sederhana. Satu pembaruan: untuk 3.1 yang dilemparkan ke AuthorizationFilterContext tidak lagi valid karena hal-hal perutean titik akhir. Anda perlu mengambil konteksnya melalui HttpContextAccessor.
JasonCoder
2

Cara modern adalah AuthenticationHandlers

di startup.cs tambahkan

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 adalah layanan yang Anda buat di mana Anda memiliki nama pengguna dan kata sandi. pada dasarnya ia mengembalikan kelas pengguna yang Anda gunakan untuk memetakan klaim Anda.

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

Kemudian Anda dapat menanyakan klaim ini dan data apa pun yang Anda petakan, ada beberapa, lihat kelas ClaimTypes

Anda dapat menggunakan ini dalam metode ekstensi dan dapatkan pemetaan

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>();
 }

Cara baru ini, saya pikir lebih baik daripada

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";
    }
}
Walter Vehoeven
sumber
Jawaban cemerlang ini hanya berfungsi seperti pesona! Terima kasih untuk itu dan saya berharap Anda akan ter-upvoted, karena ini adalah jawaban terbaik yang saya temukan setelah enam jam mencari melalui blog, dokumentasi, dan tumpukan untuk otentikasi Dasar plus otorisasi Peran.
Piotr Śródka
@ PiotrŚródka, Anda dipersilakan, harap dicatat bahwa jawabannya sedikit "disederhanakan", tes jika Anda memiliki ':' dalam teks sebagai pengguna jahat dapat mencoba dan merusak layanan Anda dengan hanya tidak bermain bagus berakhir dengan indeks keluar pengecualian rentang. seperti biasa menguji apa yang diberikan kepada Anda oleh sumber eksternal
Walter Vehoeven
2

Pada tulisan ini saya percaya ini dapat dicapai dengan antarmuka IClaimsTransformation di asp.net core 2 dan di atas. Saya baru saja menerapkan bukti konsep yang cukup sharable untuk dikirim ke sini.

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;
    }
}

Untuk menggunakan ini di Kontroler Anda cukup tambahkan yang sesuai [Authorize(Roles="whatever")]dengan metode Anda.

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

    result.StatusCode = (int)HttpStatusCode.OK;

    return result;
}

Dalam kasus kami, setiap permintaan menyertakan tajuk Otorisasi yang merupakan JWT. Ini adalah prototipe dan saya percaya kami akan melakukan sesuatu yang sangat dekat dengan ini dalam sistem produksi kami minggu depan.

Pemilih masa depan, pertimbangkan tanggal penulisan saat Anda memberikan suara. Mulai hari ini, works on my machine.™ ini Anda mungkin ingin lebih banyak penanganan kesalahan dan masuk ke implementasi Anda.

Tanpa Pengembalian Uang Tanpa Pengembalian
sumber
Bagaimana dengan ConfigureServices? Apakah perlu menambahkan sesuatu?
Daniel
Seperti yang dibahas di tempat lain, ya.
Tanpa Pengembalian Uang Tanpa Pengembalian
1

Untuk otorisasi di aplikasi kami. Kami harus memanggil layanan berdasarkan parameter yang diteruskan dalam atribut otorisasi.

Misalnya, jika kami ingin memeriksa apakah dokter yang masuk dapat melihat janji pasien, kami akan memberikan "View_Appointment" untuk atribut otorisasi khusus dan memeriksa hak itu dalam layanan DB dan berdasarkan hasil yang kami akan athorize. Berikut ini kode untuk skenario ini:

    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");
            }
        }
    }
}

Dan pada tindakan API kami menggunakannya seperti ini:

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

    }
Abdullah
sumber
1
Harap perhatikan bahwa IActionFilter akan menjadi masalah ketika Anda ingin menggunakan atribut yang sama untuk metode Hub di SignalR.SignalR Hubs mengharapkan IAuthorizationFilter
ilkerkaran
Terimakasih atas infonya. Saya tidak menggunakan SignalR dalam aplikasi saya sekarang jadi saya belum mengujinya dengan itu.
Abdullah
Prinsip yang sama saya kira karena Anda masih harus menggunakan entri otorisasi header, implementasinya akan berbeda
Walter Vehoeven
0

Jawaban yang diterima ( https://stackoverflow.com/a/41348219/4974715 ) tidak dapat dipertahankan atau cocok secara realistis karena "CanReadResource" digunakan sebagai klaim (tetapi pada dasarnya harus merupakan kebijakan dalam kenyataannya, IMO). Pendekatan pada jawaban tidak OK dalam cara itu digunakan, karena jika metode tindakan memerlukan banyak pengaturan klaim yang berbeda, maka dengan jawaban itu Anda harus berulang kali menulis sesuatu seperti ...

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

Jadi, bayangkan berapa banyak pengkodean yang akan diambil. Idealnya, "CanReadResource" seharusnya merupakan kebijakan yang menggunakan banyak klaim untuk menentukan apakah pengguna dapat membaca sumber daya.

Apa yang saya lakukan adalah saya membuat kebijakan saya sebagai enumerasi dan kemudian loop melalui dan mengatur persyaratan seperti ...

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.
     */
            }
        }); 

Kelas DefaultAuthorizationRequirement terlihat seperti ...

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); 
        } 
     }
}

Perhatikan bahwa kode di atas juga dapat memungkinkan pra-pemetaan pengguna ke kebijakan di penyimpanan data Anda. Jadi, ketika membuat klaim untuk pengguna, Anda pada dasarnya mengambil kebijakan yang telah dipetakan sebelumnya ke pengguna secara langsung atau tidak langsung (misalnya karena pengguna memiliki nilai klaim tertentu dan bahwa nilai klaim telah diidentifikasi dan dipetakan ke suatu kebijakan, seperti bahwa ia menyediakan pemetaan otomatis untuk pengguna yang memiliki nilai klaim juga), dan mendaftar kebijakan sebagai klaim, sehingga dalam penangan otorisasi, Anda dapat dengan mudah memeriksa apakah klaim pengguna mengandung persyaratan. Kebijakan sebagai Nilai dari item Klaim dalam klaim. Itu untuk cara statis untuk memenuhi persyaratan kebijakan, misalnya persyaratan "Nama depan" cukup statis. Begitu,

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

Persyaratan dinamis dapat tentang memeriksa rentang usia, dll. Dan kebijakan yang menggunakan persyaratan tersebut tidak dapat dipetakan sebelumnya kepada pengguna.

Contoh pemeriksaan klaim kebijakan dinamis (mis. Untuk memeriksa apakah pengguna berusia di atas 18 tahun) sudah ada di jawaban yang diberikan oleh @blowdart ( https://stackoverflow.com/a/31465227/4974715 ).

PS: Saya mengetik ini di ponsel saya. Maafkan kesalahan ketik dan kurangnya format.

Olumide
sumber