ASP.NET Core: Giới thiệu về Identity (Nhận dạng) trên ASP.NET Core
ASP.NET Core Identity:
- Là một API hỗ trợ chức năng đăng nhập giao diện người dùng (UI).
- Quản lý người dùng, mật khẩu, dữ liệu hồ sơ, vai trò, xác nhận quyền sở hữu, mã thông báo, xác nhận email, v.v.
Người dùng có thể tạo tài khoản với thông tin đăng nhập được lưu trữ trong Danh tính (Identity) hoặc họ có thể sử dụng nhà cung cấp thông tin đăng nhập bên ngoài. Các nhà cung cấp đăng nhập bên ngoài được hỗ trợ bao gồm Facebook, Google, Tài khoản Microsoft và Twitter.
Để biết thông tin về cách yêu cầu tất cả người dùng phải được chứng thực trên toàn cầu, hãy xem Yêu cầu người dùng được chứng thực.
Mã nguồn Identity có sẵn trên GitHub. Scaffold Identity và xem các tệp được tạo để xem lại sự tương tác của mẫu với Identity.
Danh tính thường được định cấu hình bằng cơ sở dữ liệu SQL Server để lưu trữ tên người dùng, mật khẩu và dữ liệu hồ sơ. Ngoài ra, có thể sử dụng một kho lưu trữ liên tục khác, ví dụ: Azure Table Storage.
Trong bài viết này, bạn tìm hiểu cách sử dụng Danh tính để đăng ký, đăng nhập và đăng xuất người dùng. Lưu ý: các mẫu coi tên người dùng và email giống nhau đối với người dùng. Để biết hướng dẫn chi tiết hơn về cách tạo ứng dụng sử dụng Danh tính, hãy xem Các bước tiếp theo.
ASP.NET Core Identity không liên quan đến nền tảng nhận dạng của Microsoft. Nền tảng nhận dạng của Microsoft là:
- Sự phát triển của nền tảng nhà phát triển Azure Active Directory (Azure AD).
- Một giải pháp nhận dạng thay thế để chứng thực và ủy quyền trong ứng dụng ASP.NET Core.
ASP.NET Core Identity bổ sung chức năng đăng nhập giao diện người dùng (UI) cho các ứng dụng web ASP.NET Core. Để bảo mật Web API và SPA, hãy sử dụng một trong các cách sau:
Máy chủ nhận dạng Duende là một frame OpenID Connect và OAuth 2.0 cho ASP.NET Core. Máy chủ nhận dạng Duende kích hoạt các tính năng bảo mật sau:
- Chứng thực dưới dạng dịch vụ (AaaS)
- Đăng nhập một lần/tắt (SSO) trên nhiều loại ứng dụng
- Kiểm soát quyền truy cập cho API
- Cổng Federation
Quan trọng
Phần mềm Duende có thể yêu cầu bạn trả phí giấy phép để sử dụng Máy chủ Nhận dạng Duende trong môi trường production. Để biết thêm thông tin, hãy xem Migration từ ASP.NET Core 5.0 sang 6.0.
Để biết thêm thông tin, hãy xem tài liệu về Máy chủ nhận dạng Duende (trang web Phần mềm Duende).
Xem hoặc tải xuống mã mẫu (cách tải xuống).
Tạo một ứng dụng web có chứng thực
Tạo dự án ASP.NET Core Web Application với Tài khoản người dùng cá nhân.
- Chọn mẫu ASP.NET Core Web App. Đặt tên cho dự án là WebApp1 để có cùng namespace với bản tải xuống dự án. Bấm vào OK.
- Trong mục nhập Authentication type, chọn Individual User Accounts.
Dự án được tạo cung cấp ASP.NET Core Identity dưới dạng Thư viện lớp Razor. Thư viện lớp Razor nhận dạng hiển thị các điểm cuối cùng với vùng Identity
. Ví dụ:
- /Identity/Account/Login
- /Identity/Account/Logout
- /Identity/Account/Manage
Áp dụng migration
Áp dụng các migration để khởi tạo cơ sở dữ liệu.
Chạy lệnh sau trong Package Manager Console (PMC):
Update-Database
Kiểm tra Đăng ký và đăng nhập
Chạy ứng dụng và đăng ký người dùng. Tùy thuộc vào kích thước màn hình của bạn, bạn có thể cần chọn nút chuyển đổi điều hướng để xem các liên kết Register và Login.
Xem cơ sở dữ liệu Nhận dạng
- Từ menu View, chọn SQL Server Object Explorer (SSOX).
- Điều hướng đến (localdb)MSSQLLocalDB(SQL Server 13). Nhấp chuột phải vào dbo.AspNetUsers > View Data:
Định cấu hình dịch vụ Nhận dạng
Các dịch vụ được thêm vào Program.cs
. Mẫu điển hình là gọi các phương thức theo thứ tự sau:
Add{Service}
builder.Services.Configure{Service}
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
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;
});
builder.Services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Đoạn code trên định cấu hình Danh tính với các giá trị tùy chọn mặc định. Các dịch vụ được cung cấp cho ứng dụng thông qua tính năng Dependency Injection.
Danh tính được kích hoạt bằng cách gọi UseAuthentication. UseAuthentication
thêm middleware chứng thực vào đường dẫn yêu cầu.
Ứng dụng tạo mẫu không sử dụng ủy quyền. app.UseAuthorization
được đưa vào để đảm bảo nó được thêm vào theo đúng thứ tự nếu ứng dụng thêm ủy quyền. UseRouting
, UseAuthentication
, và UseAuthorization
phải được gọi theo thứ tự hiển thị trong đoạn mã trước.
Để biết thêm thông tin về IdentityOptions
, hãy xem IdentityOptions và Khởi động ứng dụng.
Scaffold Register, Login, Logout và RegisterConfirmation
Thêm các file Register
, Login
, LogOut
và RegisterConfirmation
. Thực hiện Scaffold Nhận dạng vào dự án Razor vớ ủy quyền để hướng dẫn tạo mã được hiển thị trong phần này.
Kiểm tra Đăng ký (Register)
Khi người dùng nhấp vào nút Register trên trang Register
, thì action RegisterModel.OnPostAsync
sẽ được thực hiện. Khi này, người dùng sẽ được tạo bởi CreateAsync(TUser) trên đối tượng _userManager
:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.ToList();
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation",
new { email = Input.Email });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
Tắt xác minh tài khoản mặc định
Với các mẫu mặc định, người dùng sẽ được chuyển hướng đến Account.RegisterConfirmation
nơi họ có thể chọn liên kết để xác nhận tài khoản. Mặc định thì Account.RegisterConfirmation chỉ được sử dụng để thử nghiệm, xác minh tài khoản tự động sẽ bị tắt trong ứng dụng production.
Để yêu cầu một tài khoản được xác nhận và ngăn chặn việc đăng nhập ngay lập tức khi đăng ký, hãy đặt DisplayConfirmAccountLink = false
vào /Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
:
[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IEmailSender _sender;
public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
{
_userManager = userManager;
_sender = sender;
}
public string Email { get; set; }
public bool DisplayConfirmAccountLink { get; set; }
public string EmailConfirmationUrl { get; set; }
public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
{
if (email == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound($"Unable to load user with email '{email}'.");
}
Email = email;
// Once you add a real email sender, you should remove this code that lets you confirm the account
DisplayConfirmAccountLink = false;
if (DisplayConfirmAccountLink)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
EmailConfirmationUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
}
return Page();
}
}
Log in (Đăng nhập)
Form Đăng nhập được hiển thị khi:
- Liên kết Đăng nhập được chọn.
- Người dùng cố gắng truy cập một trang bị hạn chế mà họ không được phép truy cập hoặc khi họ chưa được hệ thống chứng thực.
Khi biểu mẫu trên trang Đăng nhập được gửi, action OnPostAsync
sẽ được gọi. PasswordSignInAsync
được gọi trên đối tượng _signInManager
.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout,
// set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(Input.Email,
Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new
{
ReturnUrl = returnUrl,
RememberMe = Input.RememberMe
});
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
Để biết thông tin về cách đưa ra quyết định ủy quyền, hãy xem Giới thiệu về ủy quyền trong ASP.NET Core.
Log out (Đăng xuất)
Liên kết Đăng xuất sẽ gọi action LogoutModel.OnPost
.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace WebApp1.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LogoutModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LogoutModel> _logger;
public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPost(string returnUrl = null)
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
return RedirectToPage();
}
}
}
}
Trong đoạn mã trên, lệnh return RedirectToPage();
cần phải chuyển hướng để trình duyệt thực hiện yêu cầu mới và danh tính của người dùng được cập nhật.
SignOutAsync sẽ xóa các xác nhận quyền sở hữu của người dùng được lưu trữ trong cookie.
Post được chỉ định trong Pages/Shared/_LoginPartial.cshtml
:
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index"
title="Manage">Hello @User.Identity.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout"
asp-route-returnUrl="@Url.Page("/", new { area = "" })"
method="post" >
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
Kiểm tra Identity (Danh tính)
Các mẫu dự án web mặc định cho phép truy cập ẩn danh vào trang chủ. Để kiểm tra Danh tính, hãy thêm [Authorize]:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace WebApp1.Pages
{
[Authorize]
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
Nếu bạn đã đăng nhập, hãy đăng xuất. Chạy ứng dụng và chọn liên kết Privacy. Bạn được chuyển hướng đến trang đăng nhập.
Khám phá Danh tính
Để khám phá Danh tính chi tiết hơn:
- Tạo nguồn UI nhận dạng đầy đủ
- Kiểm tra nguồn của từng trang và duyệt qua trình gỡ lỗi.
Thành phần Danh tính
Tất cả các gói NuGet phụ thuộc vào Danh tính đều được bao gồm trong framework chia sẻ ASP.NET Core.
Gói chính cho Identity là Microsoft.AspNetCore.Identity. Gói này chứa bộ giao diện cốt lõi cho ASP.NET Core Identity và được bao gồm bởi Microsoft.AspNetCore.Identity.EntityFrameworkCore
.
Di chuyển sang ASP.NET Core Identity
Để biết thêm thông tin và hướng dẫn về cách di chuyển kho danh tính hiện có của bạn, hãy xem Migrate chứng thực và danh tính.
Đặt độ mạnh mật khẩu
Xem Cấu hình để biết mẫu đặt các yêu cầu mật khẩu tối thiểu.
AddDefaultIdentity và AddIdentity
AddDefaultIdentity được giới thiệu trong ASP.NET Core 2.1. Cách gọi AddDefaultIdentity
tương tự như cách gọi sau:
Xem nguồn AddDefaultIdentity để biết thêm thông tin.
Ngăn chặn xuất bản nội dung Nhận dạng tĩnh
Để ngăn xuất bản nội dung Nhận dạng tĩnh (bảng định kiểu và tệp JavaScript cho Identity UI) lên web root, hãy thêm property ResolveStaticWebAssetsInputsDependsOn
và mục tiêu (target) RemoveIdentityAssets
sau vào tệp dự án của ứng dụng:
<PropertyGroup>
<ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>
<Target Name="RemoveIdentityAssets">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
</ItemGroup>
</Target>
Các bước tiếp theo
- Xem vấn đề GitHub này để biết thông tin về cách định cấu hình Danh tính bằng SQLite.
- Định cấu hình danh tính
- Tạo ứng dụng ASP.NET Core với dữ liệu người dùng được bảo vệ bằng ủy quyền
- Thêm, tải xuống và xóa dữ liệu người dùng vào Danh tính trong dự án ASP.NET Core
- Kích hoạt tính năng tạo mã QR cho ứng dụng xác thực TOTP trong ASP.NET Core
- Migrate chứng thực và Nhận dạng sang ASP.NET Core
- Xác nhận tài khoản và khôi phục mật khẩu trong ASP.NET Core
- Chứng thực hai yếu tố bằng SMS trong ASP.NET Core
- Lưu trữ ASP.NET Core trong một web farm