چگونه می توان هویت اصلی ASP.NET را با EF Core برای نیازهای پروژه خود تنظیم کرد

امنیت و احراز هویت یکی از مهمترین جنبه های هر برنامه است.
درک و استفاده از ابزارهای اثبات شده برای جلوگیری از آسیب پذیری های مشترک مانند دسترسی غیرمجاز و نشت داده ها بسیار مهم است.
هویت اصلی Asp.net به توسعه دهندگان روشی قدرتمند برای مدیریت کاربران ، نقش ها ، ادعاها و انجام احراز هویت کاربر برای برنامه های وب ارائه می دهد.
هویت راه حل های آماده برای استفاده و API را از جعبه فراهم می کند.
اما اغلب شما باید تنظیماتی را متناسب با نیازها یا نیازهای خاص خود انجام دهید.
امروز می خواهم رویکردهای عملی را برای شخصی سازی هویت ASP.NET به مرحله به مرحله نشان دهم.
ما کاوش خواهیم کرد:
- نحوه تطبیق جداول هویت داخلی با طرح پایگاه داده خود
- نحوه ثبت و ورود کاربران با نشانه های JWT
- نحوه به روزرسانی نقش ها و ادعاهای کاربر با هویت
- نحوه بذر نقش ها و ادعاهای اولیه با استفاده از هویت.
بیایید شیرجه بزنیم!
در وب سایت من: antondevtips.com من بهترین روش های .NET و معماری را به اشتراک می گذارم.
برای بهبود مهارت های دات نت خود در خبرنامه من مشترک شوید.
کد منبع را برای این خبرنامه به صورت رایگان بارگیری کنید.
شروع با هویت ASP.NET
هویت اصلی Asp.net مجموعه ای از ابزارهایی است که عملکرد ورود به سیستم را به برنامه های اصلی ASP.NET اضافه می کند.
این وظایف مانند ایجاد کاربران جدید ، عبور رمزهای عبور ، اعتبار اعتبارنامه کاربر و مدیریت نقش ها یا ادعاها را انجام می دهد.
بسته های زیر را به پروژه خود اضافه کنید تا با هویت اصلی ASP.NET شروع به کار کنید:
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
در اینجا نحوه پیکربندی هویت پیش فرض آمده است:
builder.Services.AddDefaultIdentity<IdentityUser>(options => {})
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.Configure<IdentityOptions>(options =>
{
// Password settings.
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User settings.
options.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
options.User.RequireUniqueEmail = false;
});
در اینجا لیست موجودات موجود ارائه شده توسط هویت وجود دارد:
- کاربر
- نقش
- ادعا کردن
- ادعای کاربر
- ارباب
- باسله
- متمایز
- کارگردان
با این حال ، در بسیاری از برنامه ها ممکن است نیاز به ایجاد موارد زیر داشته باشید:
- زمینه های سفارشی خود را به
User
موجودیت - زمینه های سفارشی خود را به
Role
موجودیت - کاربر و نقش کودک در هنگام استفاده مرجع
IdentityDbContext
- از نشانه های JWT به جای نشانه های تحمل استفاده کنید (بله ، هویت هسته ASP.NET هنگام استفاده از API های وب آماده ، نشانه های تحمل را برمی گرداند)
بیایید بررسی کنیم که چگونه می توانید طرح پایگاه داده را برای هویت با EF Core سفارشی کنید.
سفارشی سازی هویت ASP.NET با EF Core
یکی از ویژگی های عالی هویت اصلی ASP.NET این است که می توانید اشخاص و جداول پایگاه داده مربوطه را سفارشی کنید.
شما می توانید کلاس خود را ایجاد کنید و از IdentityUser
برای افزودن زمینه های جدید (نام FullName و JobTitle):
public class User : IdentityUser
{
public string FullName { get; set; }
public string JobTitle { get; set; }
}
بعد باید از IdentityDbContext
و نوع کلاس کاربر سفارشی خود را مشخص کنید:
public class BooksDbContext : IdentityDbContext<User>
{
}
شما می توانید به طور معمول یک نهاد کاربر را در سایر نهادها ارجاع دهید:
public class Author
{
public required Guid Id { get; set; }
public required string Name { get; set; }
public List<Book> Books { get; set; } = [];
public string? UserId { get; set; }
public User? User { get; set; }
}
public class AuthorConfiguration : IEntityTypeConfiguration<Author>
{
public void Configure(EntityTypeBuilder<Author> builder)
{
builder.ToTable("authors");
builder.HasKey(x => x.Id);
// ...
builder.HasOne(x => x.User)
.WithOne()
.HasForeignKey<Author>(x => x.UserId);
}
}
بیایید گسترش دهیم User
نهاد بیشتر برای اینکه بتوانید به ادعاهای کاربر ، نقش ها ، ورود به سیستم و نشانه ها بروید:
public class User : IdentityUser
{
public ICollection<UserClaim> Claims { get; set; }
public ICollection<UserRole> UserRoles { get; set; }
public ICollection<UserLogin> UserLogins { get; set; }
public ICollection<UserToken> UserTokens { get; set; }
}
ما باید نقشه برداری را برای User
موجودی برای مشخص کردن روابط:
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
// Each User can have many UserClaims
builder.HasMany(e => e.Claims)
.WithOne(e => e.User)
.HasForeignKey(uc => uc.UserId)
.IsRequired();
// Each User can have many UserLogins
builder.HasMany(e => e.UserLogins)
.WithOne(e => e.User)
.HasForeignKey(ul => ul.UserId)
.IsRequired();
// Each User can have many UserTokens
builder.HasMany(e => e.UserTokens)
.WithOne(e => e.User)
.HasForeignKey(ut => ut.UserId)
.IsRequired();
// Each User can have many entries in the UserRole join table
builder.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
}
}
اگر می خواهید تمام روابط بین نهادهای هویت را داشته باشید ، باید کلاس های دیگری را نیز گسترش دهید:
public class Role : IdentityRole
{
public ICollection<UserRole> UserRoles { get; set; }
public ICollection<RoleClaim> RoleClaims { get; set; }
}
public class RoleClaim : IdentityRoleClaim<string>
{
public Role Role { get; set; }
}
public class UserRole : IdentityUserRole<string>
{
public User User { get; set; }
public Role Role { get; set; }
}
public class UserClaim : IdentityUserClaim<string>
{
public User User { get; set; }
}
public class UserLogin : IdentityUserLogin<string>
{
public User User { get; set; }
}
public class UserToken : IdentityUserToken<string>
{
public User User { get; set; }
}
در اینجا نقشه برداری برای اشخاص است:
public class RoleConfiguration : IEntityTypeConfiguration<Role>
{
public void Configure(EntityTypeBuilder<Role> builder)
{
builder.ToTable("roles");
// Each Role can have many entries in the UserRole join table
builder.HasMany(e => e.UserRoles)
.WithOne(e => e.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
// Each Role can have many associated RoleClaims
builder.HasMany(e => e.RoleClaims)
.WithOne(e => e.Role)
.HasForeignKey(rc => rc.RoleId)
.IsRequired();
}
}
public class RoleClaimConfiguration : IEntityTypeConfiguration<RoleClaim>
{
public void Configure(EntityTypeBuilder<RoleClaim> builder)
{
builder.ToTable("role_claims");
}
}
public class UserRoleConfiguration : IEntityTypeConfiguration<UserRole>
{
public void Configure(EntityTypeBuilder<UserRole> builder)
{
builder.HasKey(x => new { x.UserId, x.RoleId });
}
}
public class UserClaimConfiguration : IEntityTypeConfiguration<UserClaim>
{
public void Configure(EntityTypeBuilder<UserClaim> builder)
{
builder.ToTable("user_claims");
}
}
public class UserLoginConfiguration : IEntityTypeConfiguration<UserLogin>
{
public void Configure(EntityTypeBuilder<UserLogin> builder)
{
builder.ToTable("user_logins");
}
}
public class UserTokenConfiguration : IEntityTypeConfiguration<UserToken>
{
public void Configure(EntityTypeBuilder<UserToken> builder)
{
builder.ToTable("user_tokens");
}
}
ممکن است خیلی به نظر برسد ، اما شما باید یک بار این کار را انجام دهید و سپس در هر پروژه از آن استفاده کنید.
PS: می توانید کد منبع کامل را در انتهای مقاله بارگیری کنید.
در اینجا آمده است که چگونه BooksDbContext
تغییرات:
public class BooksDbContext : IdentityDbContext<User, Role, string,
UserClaim, UserRole, UserLogin,
RoleClaim, UserToken>
{
}
سرانجام ، شما باید هویت را در DI ثبت کنید:
services.AddDbContext<BooksDbContext>((provider, options) =>
{
options
.UseNpgsql(connectionString, npgsqlOptions =>
{
npgsqlOptions.MigrationsHistoryTable(DatabaseConsts.MigrationTableName,
DatabaseConsts.Schema);
})
.UseSnakeCaseNamingConvention();
});
services
.AddIdentity<User, Role>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<BooksDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
به طور پیش فرض ، تمام جداول و ستون های موجود در پایگاه داده در Pascalcase قرار می گیرند.
اگر الگوهای نامگذاری سازگار و اتوماتیک را ترجیح می دهید ، بسته را در نظر بگیرید EFCore.NamingConventions
:
dotnet add package EFCore.NamingConventions
پس از نصب ، شما به سادگی باید پشتیبانی از کنوانسیون نامگذاری را به پیکربندی DBContext خود وصل کنید.
در ثبت نام DBContext من در بالا استفاده کردم UseSnakeCaseNamingConvention()
برای پایگاه داده Postgres من.
بنابراین نام جدول و ستون به نظر می رسد: user_claims
با user_id
، و غیره
ثبت کاربران با هویت
اکنون که هویت سیم کشی شده است ، می توانید یک نقطه پایانی را در معرض نمایش بگذارید تا کاربران جدید ثبت نام کنند.
در اینجا یک مثال حداقل API آورده شده است:
public record RegisterUserRequest(string Email, string Password);
app.MapPost("/api/register", async (
[FromBody] RegisterUserRequest request,
UserManager<User> userManager) =>
{
var existingUser = await userManager.FindByEmailAsync(request.Email);
if (existingUser != null)
{
return Results.BadRequest("User already exists.");
}
var user = new User
{
UserName = request.Email,
Email = request.Email
};
var result = await userManager.CreateAsync(user, request.Password);
if (!result.Succeeded)
{
return Results.BadRequest(result.Errors);
}
result = await userManager.AddToRoleAsync(user, "DefaultRole");
if (!result.Succeeded)
{
return Results.BadRequest(result.Errors);
}
var response = new UserResponse(user.Id, user.Email);
return Results.Created($"/api/users/{user.Id}", response);
});
شما می توانید از UserManager
کلاس برای مدیریت کاربران.
ثبت نام شامل مراحل زیر است:
- شما باید بررسی کنید که آیا کاربر از قبل با تماس وجود دارد
FindByEmailAsync
روش - اگر کاربر وجود نداشته باشد ، می توانید با تماس با یک کاربر جدید ایجاد کنید
CreateAsync
روش - با فراخوانی می توانید کاربر را به نقش اضافه کنید
AddToRoleAsync
روش - در صورت خطا در هنگام ثبت نام – می توانید a را برگردانید
BadRequest
پاسخ با پیام خطا از هویت.
نحوه ورود کاربران با هویت
پس از ایجاد کاربر ، آنها می توانند تأیید اعتبار کنند.
در اینجا نحوه اجرای احراز هویت با استفاده از هویت آمده است:
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
public record LoginUserRequest(string Email, string Password);
app.MapPost("/api/login", async (
[FromBody] LoginUserRequest request,
IOptions<AuthConfiguration> authOptions,
UserManager<User> userManager,
SignInManager<User> signInManager,
RoleManager<Role> roleManager) =>
{
var user = await userManager.FindByEmailAsync(request.Email);
if (user is null)
{
return Results.NotFound("User not found");
}
var result = await signInManager.CheckPasswordSignInAsync(user, request.Password, false);
if (!result.Succeeded)
{
return Results.Unauthorized();
}
var roles = await userManager.GetRolesAsync(user);
var userRole = roles.FirstOrDefault() ?? "user";
var role = await roleManager.FindByNameAsync(userRole);
var roleClaims = role is not null ? await roleManager.GetClaimsAsync(role) : [];
var token = GenerateJwtToken(user, authOptions.Value, userRole, roleClaims);
return Results.Ok(new { Token = token });
});
روند احراز هویت مراحل زیر را شامل می شود:
- شما باید بررسی کنید که آیا کاربر با تماس با شما وجود دارد
FindByEmailAsync
روش - با تماس می توانید رمز عبور کاربر را بررسی کنید
CheckPasswordSignInAsync
روش ازSignInManager
بشر - با تماس می توانید نقش های کاربر را بدست آورید
GetRolesAsync
روش ازUserManager
بشر - با فراخوانی می توانید ادعاهای این نقش را دریافت کنید
GetClaimsAsync
روش ازRoleManager
بشر - در صورت بروز خطا در هنگام ورود – می توانید a را برگردانید
BadRequest
پاسخ با پیام خطا از هویت.
می توانید پس از ورود به سیستم موفقیت آمیز ، یک نشانه JWT صادر کنید:
private static string GenerateJwtToken(User user,
AuthConfiguration authConfiguration,
string userRole,
IList<Claim> roleClaims)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authConfiguration.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
List<Claim> claims = [
new(JwtRegisteredClaimNames.Sub, user.Email!),
new("userid", user.Id),
new("role", userRole)
];
foreach (var roleClaim in roleClaims)
{
claims.Add(new Claim(roleClaim.Type, roleClaim.Value));
}
var token = new JwtSecurityToken(
issuer: authConfiguration.Issuer,
audience: authConfiguration.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
هر نقش در برنامه من مجموعه ای از ادعاها را دارد ، من این ادعاها را به نشانه JWT اضافه می کنم.
در اینجا چیزی است که یک نشانه JWT صادر شده ممکن است به نظر برسد:
{
"sub": "admin@test.com",
"userid": "dc233fac-bace-4719-9a4f-853e199300d5",
"role": "Admin",
"users:create": "true",
"users:update": "true",
"users:delete": "true",
"books:create": "true",
"books:update": "true",
"books:delete": "true",
"exp": 1739481834,
"iss": "DevTips",
"aud": "DevTips"
}
برای مثال می توانید از ادعاها برای محدود کردن دسترسی به نقاط پایانی استفاده کنید:
app.MapPost("/api/books", Handle)
.RequireAuthorization("books:create");
app.MapDelete("/api/books/{id}", Handle)
.RequireAuthorization("books:delete");
app.MapPost("/api/users", Handle)
.RequireAuthorization("users:create");
app.MapDelete("/api/users/{id}", Handle)
.RequireAuthorization("users:delete");
من نحوه تنظیم احراز هویت و مجوز را در هسته ASP.NET در اینجا توضیح دادم.
نحوه بذر داده های هویت: نقش ها و ادعاها را آغاز کنید
هنگامی که می خواهید یک برنامه را با نقش ها و ادعاهای پیش فرض تنظیم کنید ، بذر مفید است.
یک رویکرد مشترک به داده های بذر یک بار در راه اندازی برنامه است:
var app = builder.Build();
// Register middlewares...
// Create and seed database
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<BooksDbContext>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<Role>>();
await DatabaseSeedService.SeedAsync(dbContext, userManager, roleManager);
}
await app.RunAsync();
public static class DatabaseSeedService
{
public static async Task SeedAsync(BooksDbContext dbContext, UserManager<User> userManager,
RoleManager<Role> roleManager)
{
await dbContext.Database.MigrateAsync();
if (await dbContext.Users.AnyAsync())
{
return;
}
// Seed roles and claims here
await dbContext.SaveChangesAsync();
}
}
شما می توانید از RoleManager
برای مدیریت نقش ها و ادعاهای آنها:
var adminRole = new Role { Name = "Admin" };
var authorRole = new Role { Name = "Author" };
var result = await roleManager.CreateAsync(adminRole);
result = await roleManager.CreateAsync(authorRole);
result = await roleManager.AddClaimAsync(adminRole, new Claim("users:create", "true"));
result = await roleManager.AddClaimAsync(adminRole, new Claim("users:update", "true"));
result = await roleManager.AddClaimAsync(adminRole, new Claim("users:delete", "true"));
result = await roleManager.AddClaimAsync(adminRole, new Claim("books:create", "true"));
result = await roleManager.AddClaimAsync(adminRole, new Claim("books:update", "true"));
result = await roleManager.AddClaimAsync(adminRole, new Claim("books:delete", "true"));
result = await roleManager.AddClaimAsync(authorRole, new Claim("books:create", "true"));
result = await roleManager.AddClaimAsync(authorRole, new Claim("books:update", "true"));
result = await roleManager.AddClaimAsync(authorRole, new Claim("books:delete", "true"));
در اینجا نحوه ایجاد یک کاربر پیش فرض در برنامه خود آورده شده است:
var adminUser = new User
{
Id = Guid.NewGuid().ToString(),
Email = "admin@test.com",
UserName = "admin@test.com"
};
result = await userManager.CreateAsync(adminUser, "Test1234!");
result = await userManager.AddToRoleAsync(adminUser, "Admin");
تغییر رمز عبور پیش فرض پس از ورود به سیستم برای اولین بار مهم است.
در وب سایت من: antondevtips.com من بهترین روش های .NET و معماری را به اشتراک می گذارم.
برای بهبود مهارت های دات نت خود در خبرنامه من مشترک شوید.
کد منبع را برای این خبرنامه به صورت رایگان بارگیری کنید.