Bagikan melalui


Penyedia penyimpanan kustom untuk ASP.NET Core Identity

Oleh Steve Smith

ASP.NET Core Identity adalah sistem yang dapat diperluas yang memungkinkan Anda membuat penyedia penyimpanan kustom dan menghubungkannya ke aplikasi Anda. Topik ini menjelaskan cara membuat penyedia penyimpanan yang disesuaikan untuk ASP.NET Core Identity. Ini mencakup konsep penting untuk membuat penyedia penyimpanan Anda sendiri, tetapi bukan langkah demi langkah. Lihat Identity kustomisasi model untuk menyesuaikan Identity model.

Pendahuluan

Secara default, sistem ASP.NET Core Identity menyimpan informasi pengguna dalam database SQL Server menggunakan Entity Framework Core. Untuk banyak aplikasi, pendekatan ini berfungsi dengan baik. Namun, Anda mungkin lebih suka menggunakan mekanisme persistensi atau skema data yang berbeda. Contohnya:

  • Anda menggunakan Azure Table Storage atau penyimpanan data lain.
  • Tabel database Anda memiliki struktur yang berbeda.
  • Anda mungkin ingin menggunakan pendekatan akses data yang berbeda, seperti Dapper.

Dalam setiap kasus ini, Anda dapat menulis penyedia yang disesuaikan untuk mekanisme penyimpanan Anda dan menyambungkan penyedia tersebut ke aplikasi Anda.

ASP.NET Core Identity disertakan dalam templat proyek di Visual Studio dengan opsi "Akun Individual".

Saat menggunakan .NET CLI, tambahkan -au Individual:

dotnet new mvc -au Individual

Arsitektur ASP.NET Core Identity

ASP.NET Core Identity terdiri dari kelas yang disebut manajer dan toko. Manajer adalah kelas tingkat tinggi yang digunakan pengembang aplikasi untuk melakukan operasi, seperti membuat Identity pengguna. Toko adalah kelas tingkat bawah yang menentukan bagaimana entitas, seperti pengguna dan peran, dipertahankan. Toko mengikuti pola repositori dan digabungkan erat dengan mekanisme persistensi. Manajer dipisahkan dari penyimpanan, yang berarti Anda dapat mengganti mekanisme persistensi tanpa mengubah kode aplikasi Anda (kecuali untuk konfigurasi).

Diagram berikut menunjukkan bagaimana aplikasi web berinteraksi dengan manajer, saat penyimpanan berinteraksi dengan lapisan akses data.

ASP.NET Core Apps bekerja dengan Manajer (misalnya, UserManager, RoleManager). Manajer bekerja dengan Stores (misalnya, UserStore) yang berkomunikasi dengan Sumber Data menggunakan pustaka seperti Entity Framework Core.

Untuk membuat penyedia penyimpanan kustom, buat sumber data, lapisan akses data, dan kelas penyimpanan yang berinteraksi dengan lapisan akses data ini (kotak hijau dan abu-abu dalam diagram di atas). Anda tidak perlu menyesuaikan manajer atau kode aplikasi anda yang berinteraksi dengan mereka (kotak biru di atas).

Saat membuat instans UserManager baru atau RoleManager Anda menyediakan jenis kelas pengguna dan meneruskan instans kelas toko sebagai argumen. Pendekatan ini memungkinkan Anda untuk menyambungkan kelas yang disesuaikan ke ASP.NET Core.

Mengonfigurasi ulang aplikasi untuk menggunakan penyedia penyimpanan baru menunjukkan cara membuat instans UserManager dan RoleManager dengan penyimpanan yang disesuaikan.

ASP.NET Core Identity menyimpan jenis data

jenis data ASP.NET Core Identity dirinci di bagian berikut:

Pengguna

Pengguna terdaftar situs web Anda. Jenis IdentityUser dapat diperluas atau digunakan sebagai contoh untuk jenis kustom Anda sendiri. Anda tidak perlu mewarisi dari jenis tertentu untuk menerapkan solusi penyimpanan identitas kustom Anda sendiri.

Klaim Pengguna

Serangkaian pernyataan (atau Klaim) tentang pengguna yang mewakili identitas pengguna. Dapat mengaktifkan ekspresi identitas pengguna yang lebih besar daripada yang dapat dicapai melalui peran.

Login Pengguna

Informasi tentang penyedia autentikasi eksternal (seperti Facebook atau akun Microsoft) untuk digunakan saat masuk ke pengguna. Contoh

Peran

Grup otorisasi untuk situs Anda. Termasuk ID peran dan nama peran (seperti "Admin" atau "Karyawan"). Contoh

Lapisan akses data

Topik ini mengasumsikan Anda terbiasa dengan mekanisme persistensi yang akan Anda gunakan dan cara membuat entitas untuk mekanisme tersebut. Topik ini tidak memberikan detail tentang cara membuat repositori atau kelas akses data; ini memberikan beberapa saran tentang keputusan desain saat bekerja dengan ASP.NET Core Identity.

Anda memiliki banyak kebebasan saat merancang lapisan akses data untuk penyedia penyimpanan yang disesuaikan. Anda hanya perlu membuat mekanisme persistensi untuk fitur yang ingin Anda gunakan di aplikasi Anda. Misalnya, jika Anda tidak menggunakan peran di aplikasi, Anda tidak perlu membuat penyimpanan untuk peran atau asosiasi peran pengguna. Teknologi Anda dan infrastruktur yang ada mungkin memerlukan struktur yang sangat berbeda dari implementasi default ASP.NET Core Identity. Di lapisan akses data, Anda menyediakan logika untuk bekerja dengan struktur implementasi penyimpanan Anda.

Lapisan akses data menyediakan logika untuk menyimpan data dari ASP.NET Core Identity ke sumber data. Lapisan akses data untuk penyedia penyimpanan yang disesuaikan mungkin menyertakan kelas berikut untuk menyimpan informasi pengguna dan peran.

Kelas konteks

Merangkum informasi untuk menyambungkan ke mekanisme persistensi Anda dan menjalankan kueri. Beberapa kelas data memerlukan instans kelas ini, biasanya disediakan melalui injeksi dependensi. Contoh.

Penyimpanan Pengguna

Menyimpan dan mengambil informasi pengguna (seperti nama pengguna dan hash kata sandi). Contoh

Penyimpanan Peran

Menyimpan dan mengambil informasi peran (seperti nama peran). Contoh

Penyimpanan Klaim Pengguna

Menyimpan dan mengambil informasi klaim pengguna (seperti jenis dan nilai klaim). Contoh

Penyimpanan UserLogins

Menyimpan dan mengambil informasi masuk pengguna (seperti penyedia autentikasi eksternal). Contoh

Penyimpanan Peran Pengguna

Menyimpan dan mengambil peran mana yang ditetapkan ke pengguna mana. Contoh

TIPS: Hanya terapkan kelas yang ingin Anda gunakan di aplikasi Anda.

Di kelas akses data, berikan kode untuk melakukan operasi data untuk mekanisme persistensi Anda. Misalnya, dalam penyedia kustom, Anda mungkin memiliki kode berikut untuk membuat pengguna baru di kelas toko :

public async Task<IdentityResult> CreateAsync(ApplicationUser user, 
    CancellationToken cancellationToken = default(CancellationToken))
{
    cancellationToken.ThrowIfCancellationRequested();
    if (user == null) throw new ArgumentNullException(nameof(user));

    return await _usersTable.CreateAsync(user);
}

Logika implementasi untuk membuat pengguna ada dalam metode , yang _usersTable.CreateAsync ditunjukkan di bawah ini.

Mengkustomisasi kelas pengguna

Saat menerapkan penyedia penyimpanan, buat kelas pengguna yang setara dengan kelas IdentityUser.

Minimal, kelas pengguna Anda harus menyertakan Id properti dan UserName .

Kelas IdentityUser menentukan properti yang dipanggil UserManager saat melakukan operasi yang diminta. Jenis Id default properti adalah string, tetapi Anda dapat mewarisi dari IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin, TUserToken> dan menentukan jenis yang berbeda. Kerangka kerja mengharapkan implementasi penyimpanan untuk menangani konversi jenis data.

Mengkustomisasi penyimpanan pengguna

Buat UserStore kelas yang menyediakan metode untuk semua operasi data pada pengguna. Kelas ini setara dengan UserStore<TUser> kelas . Di kelas Anda UserStore , terapkan IUserStore<TUser> dan antarmuka opsional diperlukan. Anda memilih antarmuka opsional mana yang akan diterapkan berdasarkan fungsionalitas yang disediakan di aplikasi Anda.

Antarmuka opsional

Antarmuka opsional mewarisi dari IUserStore<TUser>. Anda dapat melihat penyimpanan pengguna sampel yang diimplementasikan sebagian di aplikasi sampel.

UserStore Dalam kelas , Anda menggunakan kelas akses data yang Anda buat untuk melakukan operasi. Ini diteruskan menggunakan injeksi dependensi. Misalnya, di SQL Server dengan implementasi Dapper, UserStore kelas memiliki CreateAsync metode yang menggunakan instans DapperUsersTable untuk menyisipkan rekaman baru:

public async Task<IdentityResult> CreateAsync(ApplicationUser user)
{
    string sql = "INSERT INTO dbo.CustomUser " +
        "VALUES (@id, @Email, @EmailConfirmed, @PasswordHash, @UserName)";

    int rows = await _connection.ExecuteAsync(sql, new { user.Id, user.Email, user.EmailConfirmed, user.PasswordHash, user.UserName });

    if(rows > 0)
    {
        return IdentityResult.Success;
    }
    return IdentityResult.Failed(new IdentityError { Description = $"Could not insert user {user.Email}." });
}

Antarmuka yang akan diterapkan saat menyesuaikan penyimpanan pengguna

  • IUserStore
    Antarmuka IUserStore<TUser> adalah satu-satunya antarmuka yang harus Anda terapkan di penyimpanan pengguna. Ini mendefinisikan metode untuk membuat, memperbarui, menghapus, dan mengambil pengguna.
  • IUserClaimStore
    Antarmuka IUserClaimStore<TUser> menentukan metode yang Anda terapkan untuk mengaktifkan klaim pengguna. Ini berisi metode untuk menambahkan, menghapus, dan mengambil klaim pengguna.
  • IUserLoginStore
    IUserLoginStore<TUser> menentukan metode yang Anda terapkan untuk mengaktifkan penyedia autentikasi eksternal. Ini berisi metode untuk menambahkan, menghapus, dan mengambil login pengguna, dan metode untuk mengambil pengguna berdasarkan informasi masuk.
  • IUserRoleStore
    Antarmuka IUserRoleStore<TUser> menentukan metode yang Anda terapkan untuk memetakan pengguna ke peran. Ini berisi metode untuk menambahkan, menghapus, dan mengambil peran pengguna, dan metode untuk memeriksa apakah pengguna ditetapkan ke peran.
  • IUserPasswordStore
    Antarmuka IUserPasswordStore<TUser> menentukan metode yang Anda terapkan untuk mempertahankan kata sandi yang di-hash. Ini berisi metode untuk mendapatkan dan mengatur kata sandi yang di-hash, dan metode yang menunjukkan apakah pengguna telah mengatur kata sandi.
  • IUserSecurityStampStore
    Antarmuka IUserSecurityStampStore<TUser> menentukan metode yang Anda terapkan untuk menggunakan stempel keamanan untuk menunjukkan apakah informasi akun pengguna telah berubah. Stempel ini diperbarui ketika pengguna mengubah kata sandi, atau menambahkan atau menghapus login. Ini berisi metode untuk mendapatkan dan mengatur stempel keamanan.
  • IUserTwoFactorStore
    Antarmuka IUserTwoFactorStore<TUser> menentukan metode yang Anda terapkan untuk mendukung autentikasi dua faktor. Ini berisi metode untuk mendapatkan dan mengatur apakah autentikasi dua faktor diaktifkan untuk pengguna.
  • IUserPhoneNumberStore
    Antarmuka IUserPhoneNumberStore<TUser> menentukan metode yang Anda terapkan untuk menyimpan nomor telepon pengguna. Ini berisi metode untuk mendapatkan dan mengatur nomor telepon dan apakah nomor telepon dikonfirmasi.
  • IUserEmailStore
    Antarmuka IUserEmailStore<TUser> menentukan metode yang Anda terapkan untuk menyimpan alamat email pengguna. Ini berisi metode untuk mendapatkan dan mengatur alamat email dan apakah email dikonfirmasi.
  • IUserLockoutStore
    Antarmuka IUserLockoutStore<TUser> menentukan metode yang Anda terapkan untuk menyimpan informasi tentang mengunci akun. Ini berisi metode untuk melacak upaya akses dan penguncian yang gagal.
  • IQueryableUserStore
    Antarmuka IQueryableUserStore<TUser> menentukan anggota yang Anda terapkan untuk menyediakan penyimpanan pengguna yang dapat dikueri.

Anda hanya mengimplementasikan antarmuka yang diperlukan di aplikasi Anda. Contohnya:

public class UserStore : IUserStore<IdentityUser>,
                         IUserClaimStore<IdentityUser>,
                         IUserLoginStore<IdentityUser>,
                         IUserRoleStore<IdentityUser>,
                         IUserPasswordStore<IdentityUser>,
                         IUserSecurityStampStore<IdentityUser>
{
    // interface implementations not shown
}

IdentityUserClaim, IdentityUserLogin, dan IdentityUserRole

Namespace Microsoft.AspNet.Identity.EntityFramework berisi implementasi kelas IdentityUserClaim, IdentityUserLogin, dan IdentityUserRole . Jika Anda menggunakan fitur-fitur ini, Anda mungkin ingin membuat versi kelas ini sendiri dan menentukan properti untuk aplikasi Anda. Namun, terkadang lebih efisien untuk tidak memuat entitas ini ke dalam memori saat melakukan operasi dasar (seperti menambahkan atau menghapus klaim pengguna). Sebagai gantinya, kelas penyimpanan backend dapat menjalankan operasi ini langsung pada sumber data. Misalnya, UserStore.GetClaimsAsync metode dapat memanggil userClaimTable.FindByUserId(user.Id) metode untuk menjalankan kueri pada tabel tersebut secara langsung dan mengembalikan daftar klaim.

Mengkustomisasi kelas peran

Saat menerapkan penyedia penyimpanan peran, Anda dapat membuat jenis peran kustom. Ini tidak perlu menerapkan antarmuka tertentu, tetapi harus memiliki Id dan biasanya akan memiliki Name properti.

Berikut ini adalah contoh kelas peran:

using System;

namespace CustomIdentityProviderSample.CustomProvider
{
    public class ApplicationRole
    {
        public Guid Id { get; set; } = Guid.NewGuid();
        public string Name { get; set; }
    }
}

Mengkustomisasi penyimpanan peran

Anda dapat membuat RoleStore kelas yang menyediakan metode untuk semua operasi data pada peran. Kelas ini setara dengan kelas RoleStore<TRole> . RoleStore Di kelas , Anda mengimplementasikan IRoleStore<TRole> dan secara IQueryableRoleStore<TRole> opsional antarmuka .

  • IRoleStore<TRole>
    Antarmuka IRoleStore<TRole> mendefinisikan metode yang akan diterapkan di kelas penyimpanan peran. Ini berisi metode untuk membuat, memperbarui, menghapus, dan mengambil peran.
  • RoleStore<TRole>
    Untuk menyesuaikan RoleStore, buat kelas yang mengimplementasikan IRoleStore<TRole> antarmuka.

Mengonfigurasi ulang aplikasi untuk menggunakan penyedia penyimpanan baru

Setelah menerapkan penyedia penyimpanan, Anda mengonfigurasi aplikasi untuk menggunakannya. Jika aplikasi Anda menggunakan penyedia default, ganti dengan penyedia kustom Anda.

  1. Microsoft.AspNetCore.EntityFramework.Identity Hapus paket NuGet.
  2. Jika penyedia penyimpanan berada dalam proyek atau paket terpisah, tambahkan referensi ke dalamnya.
  3. Ganti semua referensi ke Microsoft.AspNetCore.EntityFramework.Identity dengan pernyataan penggunaan untuk namespace penyedia penyimpanan Anda.
  4. AddIdentity Ubah metode untuk menggunakan jenis kustom. Anda dapat membuat metode ekstensi Anda sendiri untuk tujuan ini. Lihat IdentityServiceCollectionExtensions misalnya.
  5. Jika Anda menggunakan Peran, perbarui RoleManager untuk menggunakan kelas Anda RoleStore .
  6. Perbarui string koneksi dan kredensial ke konfigurasi aplikasi Anda.

Peringatan

Artikel ini memperlihatkan penggunaan string koneksi. Dengan database lokal, pengguna tidak perlu diautentikasi, tetapi dalam produksi, string koneksi terkadang menyertakan kata sandi untuk mengautentikasi. Kredensial kata sandi pemilik sumber daya (ROPC) adalah risiko keamanan yang harus dihindari dalam database produksi. Aplikasi produksi harus menggunakan alur autentikasi paling aman yang tersedia. Untuk informasi selengkapnya tentang autentikasi untuk aplikasi yang disebarkan untuk menguji atau lingkungan produksi, lihat Mengamankan alur autentikasi.

Contoh:

public void ConfigureServices(IServiceCollection services)
{
    // Add identity types
    services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddDefaultTokenProviders();

    // Identity Services
    services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
    services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
    string connectionString = Configuration.GetConnectionString("DefaultConnection");
    services.AddTransient<SqlConnection>(e => new SqlConnection(connectionString));
    services.AddTransient<DapperUsersTable>();

    // additional configuration
}
var builder = WebApplication.CreateBuilder(args);

// Add identity types
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddDefaultTokenProviders();

// Identity Services
builder.Services.AddTransient<IUserStore<ApplicationUser>, CustomUserStore>();
builder.Services.AddTransient<IRoleStore<ApplicationRole>, CustomRoleStore>();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddTransient<SqlConnection>(e => new SqlConnection(connectionString));
builder.Services.AddTransient<DapperUsersTable>();

// additional configuration

builder.Services.AddRazorPages();

var app = builder.Build();

Referensi