ASP.NET Core Identityï¼
ç¨æ·å¯ä½¿ç¨åå¨å¨ Identity ä¸çç»å½ä¿¡æ¯åå»ºå¸æ·ï¼æè å¯ä½¿ç¨å¤é¨ç»å½æä¾ç¨åºã æ¯æçå¤é¨ç»å½æä¾ç¨åºå æ¬ FacebookãGoogleãMicrosoft 叿·å Twitterã
æå ³å¦ä½å ¨å±è¦æ±ææç¨æ·è¿è¡èº«ä»½éªè¯çä¿¡æ¯ï¼è¯·åé éè¦éè¿èº«ä»½éªè¯çç¨æ·ã
GitHub 䏿ä¾äº Identity æºä»£ç ã Scaffold Identity 忥ççæçæä»¶ï¼ä»¥æ¥çä¸ Identity çæ¨¡æ¿äº¤äºã
Identity éå¸¸ä½¿ç¨ SQL Server æ°æ®åºè¿è¡é ç½®ï¼ä»¥åå¨ç¨æ·åãå¯ç åé ç½®æä»¶æ°æ®ã æè ï¼å¯ä½¿ç¨å ¶ä»æä¹ æ§åå¨ï¼ä¾å¦ Azure 表åå¨ã
卿¬ä¸»é¢ä¸ï¼ä½ å°å¦ä¹ å¦ä½ä½¿ç¨ Identity æ¥æ³¨åãç»å½å注éç¨æ·ã 注æï¼æ¨¡æ¿ä¼å°ç¨æ·çç¨æ·ååçµåé®ä»¶ç忝ç¸åçã è¥è¦æ´è¯¦ç»äºè§£å¦ä½åå»ºä½¿ç¨ Identity çåºç¨ï¼è¯·åé åç»æ¥éª¤ã
æå ³ Identity åºç¨ä¸ç Blazor çæ´å¤ä¿¡æ¯ï¼è¯·åé Blazor ææ¡£ä¸ç Blazoråå ¶åç»æç« ã
ASP.NET Core Identity ä¸ Microsoft æ è¯å¹³å°æ å ³ã 微软身份éªè¯å¹³å°æ¯ï¼
ASP.NET Core Identity å°ç¨æ·çé¢ (UI) ç»å½åè½æ·»å å° ASP.NET Core Web åºç¨ã è¥è¦ä¿æ¤ Web API å SPAï¼è¯·ä½¿ç¨ä»¥ä¸é¡¹ä¹ä¸ï¼
Duende Identity Server æ¯éç¨äº ASP.NET Core ç OpenID Connect å OAuth 2.0 æ¡æ¶ã Duende Identity Server æ¯æä»¥ä¸å®å ¨åè½ï¼
æå ³è¯¦ç»ä¿¡æ¯ï¼è¯·åé Duende Identity Server ææ¡£ï¼Duende Software ç½ç«ï¼ã
æ¥çæä¸è½½ç¤ºä¾ä»£ç ï¼ä¸è½½æ¹æ³ï¼ã
å建带æèº«ä»½éªè¯ç Blazor Web App使ç¨ä¸ªäººå¸æ·å建 ASP.NET æ ¸å¿ Blazor Web App 项ç®ã
dotnet new blazor -au Individual -o BlazorApp1
该 -o|--output
é项为åºç¨å建ä¸ä¸ªæä»¶å¤¹ï¼å¹¶è®¾ç½®åºç¨åç§°/å½å空é´ã
ä¸è¿°å½ä»¤ä½¿ç¨ SQLite å建 Blazor Web App ã è¥è¦ä½¿ç¨ LocalDB å建åºç¨ï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet new blazor -au Individual -uld -o BlazorApp1
çæç项ç®å
æ¬ IdentityRazor ç»ä»¶ã è¿äºç»ä»¶ä½äº Components/Account
æä»¶å¤¹ä¸ã ä¾å¦ï¼
/Components/Account/Pages/Register
/Components/Account/Pages/Login
/Components/Account/Pages/Manage/ChangePassword
Identity Razor ç»ä»¶å¨ææ¡£ä¸é对å
·ä½ç¨ä¾åç¬æè¿°ï¼å¹¶å¨æ¯ä¸ªçæ¬ä¸å¯è½ä¼åçååã 使ç¨ä¸ªäººå¸æ·çæ Blazor Web App ç»ä»¶æ¶ï¼ IdentityRazor ç»ä»¶å°å
å«å¨çæç项ç®ä¸ã Identity Razorè¿å¯ä»¥å¨ ASP.NET æ ¸å¿å¼ç¨æºï¼BlazorGitHub åå¨åºï¼ä¸çdotnet/aspnetcore
é¡¹ç®æ¨¡æ¿ä¸æ£æ¥è¿äºç»ä»¶ã
æå ³è¯¦ç»ä¿¡æ¯ï¼è¯·åé ææ¡£ä¸ ASP.NET æ ¸å¿ Blazor 身份éªè¯åææ åå ¶åé¢ç Blazor æç« ã ä¸»è¦ ASP.NET æ ¸å¿ææ¡£éçå®å ¨ å Identity åºåä¸ç大é¨åæç« éç¨äº Blazor åºç¨ã 使¯ï¼ Blazor ææ¡£éå å«åä»£ææ·»å ä¿¡æ¯çæç« åæåã 建议å ç ç©¶ä¸è¬ ASP.NET æ ¸å¿ææ¡£éï¼ç¶å访é®Identityææ¡£ä¸çæç« ã
å建带æèº«ä»½éªè¯ç Razor Pages åºç¨ä½¿ç¨ä¸ªäººå¸æ·å建 ASP.NET æ ¸å¿ Web åºç¨ç¨åºï¼Razor 页é¢ï¼é¡¹ç®ã
dotnet new webapp -au Individual -o WebApp1
该 -o|--output
é项为åºç¨å建ä¸ä¸ªæä»¶å¤¹ï¼å¹¶è®¾ç½®åºç¨åç§°/å½å空é´ã
ä¸è¿°å½ä»¤ä½¿ç¨ SQLite å建 Razor Pages åºç¨ã è¥è¦ä½¿ç¨ LocalDB å建åºç¨ï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet new webapp -au Individual -uld -o WebApp1
çæçé¡¹ç®æä¾ ASP.NET Core Identity ä½ä¸º Razor ç±»åº (RCL)ã Identity Razor ç±»åºå
¬å¼å
·æ Identity
åºåçç»ç»ç¹ã ä¾å¦ï¼
Areas/Identity/Pages/Account/Register
Areas/Identity/Pages/Account/Login
Areas/Identity/Pages/Account/Manage/ChangePassword
å¨ç¹å®ç¨ä¾çææ¡£ä¸ï¼å个页é¢ä¼è¢«åç¬æè¿°å¹¶ä¸å¯è½éçæ¯ä¸ªåå¸çæ¬è¿è¡æ´æ¹ã è¥è¦æ¥ç RCL ä¸çææé¡µé¢ï¼è¯·åé
ASP.NET æ ¸å¿å¼ç¨æºï¼dotnet/aspnetcore
GitHub åå¨åºï¼Identity/UI/src/Areas/Identity/Pages
æä»¶å¤¹ï¼ã å¯ä»¥æå»ºåä¸ªé¡µé¢æææé¡µé¢å°åºç¨ä¸ã æå
³è¯¦ç»ä¿¡æ¯ï¼è¯·åé
ASP.NET Core 项ç®ä¸çåºæ¶ Identityã
使ç¨ä¸ªäººå¸æ·å建 ASP.NET æ ¸å¿ MVC 项ç®ã
dotnet new mvc -au Individual -o WebApplication1
该 -o|--output
é项为åºç¨å建ä¸ä¸ªæä»¶å¤¹ï¼å¹¶è®¾ç½®åºç¨åç§°/å½å空é´ã
ä¸è¿°å½ä»¤ä½¿ç¨ SQLite å建 MVC åºç¨ã è¥è¦ä½¿ç¨ LocalDB å建 Web åºç¨ï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet new mvc -au Individual -uld -o WebApplication1
çæçé¡¹ç®æä¾ ASP.NET Core Identity ä½ä¸º Razor ç±»åº (RCL)ã Identity Razor ç±»åºåºäº Razor Pages å¹¶éè¿ Identity
åºåå
¬å¼ç»ç»ç¹ã ä¾å¦ï¼
Areas/Identity/Pages/Account/Register
Areas/Identity/Pages/Account/Login
Areas/Identity/Pages/Account/Manage/ChangePassword
å¨ç¹å®ç¨ä¾çææ¡£ä¸ï¼å个页é¢ä¼è¢«åç¬æè¿°å¹¶ä¸å¯è½éçæ¯ä¸ªåå¸çæ¬è¿è¡æ´æ¹ã è¥è¦æ¥ç RCL ä¸çææé¡µé¢ï¼è¯·åé
ASP.NET æ ¸å¿å¼ç¨æºï¼dotnet/aspnetcore
GitHub åå¨åºï¼Identity/UI/src/Areas/Identity/Pages
æä»¶å¤¹ï¼ã å¯ä»¥æå»ºåä¸ªé¡µé¢æææé¡µé¢å°åºç¨ä¸ã æå
³è¯¦ç»ä¿¡æ¯ï¼è¯·åé
ASP.NET Core 项ç®ä¸çåºæ¶ Identityã
åºç¨è¿ç§»ä»¥åå§åæ°æ®åºã
å¨å 管ç卿§å¶å° (PMC) ä¸è¿è¡ä»¥ä¸å½ä»¤ï¼
Update-Database
ä½¿ç¨ SQLite æ¶ï¼æ¤æ¥éª¤ä¸éè¦è¿ç§»ã
妿尿ªå®è£
dotnet ef
ï¼è¯·å®è£
å®ä½ä¸ºå
¨å±å·¥å
·ï¼
dotnet tool install --global dotnet-ef
æå ³ EF Core ç CLI ç详ç»ä¿¡æ¯ï¼è¯·åé .NET CLI ç EF Core å·¥å ·å¼ç¨ã
å¯¹äº LocalDBï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet ef database update
æµè¯æ³¨ååç»å½
è¿è¡åºç¨å¹¶æ³¨åç¨æ·ã æ ¹æ®å±å¹å¤§å°ï¼ä½ å¯è½éè¦éæ©å¯¼èªåæ¢æé®æ¥æ¥çâæ³¨åâåâç»å½â龿¥ã
æ¥ç Identity æ°æ®åºå¯ä¸è½½è®¸å¤ç¬¬ä¸æ¹å·¥å ·æ¥ç®¡ç忥ç SQLite æ°æ®åºï¼ä¾å¦ SQLite çæ°æ®åºæµè§å¨ã
é ç½® Identity æå¡è¿äºæå¡æ·»å å¨ Program.cs
ä¸ã å
¸åæ¨¡å¼æ¯æä»¥ä¸é¡ºåºè°ç¨æ¹æ³ï¼
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();
ä¸è¿°ä»£ç ç¨é»è®¤é项弿¥é ç½® Identityã å¯éè¿ä¾èµå ³ç³»æ³¨å ¥ååºç¨æä¾æå¡ã
éè¿è°ç¨ Identity å¯ç¨ UseAuthenticationã UseAuthentication
å请æ±ç®¡éæ·»å 身份éªè¯ä¸é´ä»¶ã
模æ¿çæçåºç¨ä¸ä½¿ç¨ææã app.UseAuthorization
ä¼è¢«å
嫿¥å
ï¼ç¡®ä¿å¨åºç¨æ·»å æææ¶ææ£ç¡®çé¡ºåºæ·»å å®ã å¿
é¡»æä¸è¿°ä»£ç ä¸æç¤ºç顺åºè°ç¨ UseRouting
ãUseAuthentication
å UseAuthorization
ã
æå
³ IdentityOptions
ç详ç»ä¿¡æ¯ï¼è¯·åé
IdentityOptions ååºç¨ç¨åºå¯å¨ã
æ·»å Register
ãLogin
ãLogOut
å RegisterConfirmation
æä»¶ã æç
§å°åºæ¶æ è¯æå»ºå°å
·æææç Razor 项ç®è¯´ææ¥çææ¬è䏿¾ç¤ºç代ç ã
妿å建ç项ç®çå称为 WebApp1ï¼å¹¶ä¸æªä½¿ç¨ SQLiteï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ã å¦åï¼è¯·ä¸º ApplicationDbContext
ä½¿ç¨æ£ç¡®çå½å空é´ï¼
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation"
ä½¿ç¨ SQLite æ¶ï¼è¿½å --useSqLite
æ -sqlite
ï¼
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation" --useSqLite
PowerShell 使ç¨åå·ä½ä¸ºå½ä»¤åé符ã ä½¿ç¨ PowerShell æ¶ï¼å¯¹æä»¶å表ä¸çåå·è¿è¡è½¬ä¹ï¼æè å°æä»¶å表æ¾å¨åå¼å·ä¸ï¼å¦ä¸è¿°ç¤ºä¾æç¤ºã
æå ³åºæ¶ Identity ç详ç»ä¿¡æ¯ï¼è¯·åé å°åºæ¶æ è¯æå»ºå°å ·æææç Razor 项ç®ã
æ£æ¥æ³¨åå½ç¨æ·åå» é¡µé¢ä¸çâæ³¨åâæé®æ¶ï¼ä¼è°ç¨ Register
æä½ãRegisterModel.OnPostAsync
CreateAsync(TUser) å¨ _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();
}
ç¦ç¨é»è®¤å¸æ·éªè¯
使ç¨é»è®¤æ¨¡æ¿æ¶ï¼ä¼å°ç¨æ·éå®åå° Account.RegisterConfirmation
ï¼ç¨æ·å¯ä»¥ä»ä¸éæ©ä¸ä¸ªé¾æ¥æ¥ç¡®è®¤å¸æ·ã é»è®¤å¼ Account.RegisterConfirmation
ä»
ç¨äºæµè¯ï¼åºå¨ç产åºç¨ä¸ç¦ç¨èªå¨å¸æ·éªè¯ã
è¥è¦è¦æ±ä½¿ç¨å·²ç¡®è®¤ç叿·ï¼å¹¶é²æ¢æ³¨åæ¶ç«å³ç»å½ï¼è¯·å¨ DisplayConfirmAccountLink = false
ä¸è®¾ç½® /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();
}
}
ç»å½
å¨ä»¥ä¸æ åµä¸ï¼ä¼æ¾ç¤ºâç»å½âçªä½ï¼
æäº¤ç»å½é¡µä¸ççªä½æ¶ï¼å°è°ç¨ OnPostAsync
æä½ã 对 PasswordSignInAsync
对象è°ç¨ _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();
}
è¥è¦äºè§£å¦ä½ååºææå³å®ï¼è¯·åé ASP.NET Core ä¸çææç®ä»ã
注éâæ³¨éâ龿¥ä¼è°ç¨ æä½ã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();
}
}
}
}
å¨åé¢ç代ç ä¸ï¼ä»£ç return RedirectToPage();
å¿
é¡»æ¯éå®åï¼ä»¥ä¾¿æµè§å¨æ§è¡æ°è¯·æ±å¹¶æ´æ°ç¨æ·çæ è¯ã
SignOutAsync æ¸ é¤åå¨å¨ cookie ä¸çç¨æ·å£°æã
å¨ Pages/Shared/_LoginPartial.cshtml
䏿å®äº Postï¼
@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>
æµè¯ Identity
é»è®¤ Web é¡¹ç®æ¨¡æ¿å
许å¿å访é®ä¸»é¡µã è¥è¦æµè¯ Identityï¼è¯·æ·»å [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()
{
}
}
}
妿已ç»å½ï¼è¯·æ³¨éã请è¿è¡åºç¨å¹¶éæ©Privacy龿¥ã å°è¢«éå®åå°ç»å½é¡µã
äºè§£ Identityè¥è¦æ´è¯¦ç»å°äºè§£ Identityï¼
ææä¾èµ Identity ç NuGet å é½å å«å¨ ASP.NET Core å ±äº«æ¡æ¶ä¸ã
Identity ç主å
æ¯ Microsoft.AspNetCore.Identityã æ¤å
å
å« ASP.NET Core Identity çæ ¸å¿æ¥å£éï¼å¹¶ä¸ç± Microsoft.AspNetCore.Identity.EntityFrameworkCore
å
å«ã
æå ³è¿ç§»ç°æ Identity åå¨ç详ç»ä¿¡æ¯åæå¯¼ï¼è¯·åé è¿ç§»èº«ä»½éªè¯å Identityã
设置å¯ç 强度æå ³è®¾ç½®æå°å¯ç è¦æ±ç示ä¾ï¼è¯·æ¥çé ç½®ã
AddDefaultIdentity å AddIdentityASP.NET Core 2.1 ä¸å¼å
¥äº AddDefaultIdentityã è°ç¨ AddDefaultIdentity
类似äºè°ç¨ä»¥ä¸å
容ï¼
æå ³è¯¦ç»ä¿¡æ¯ï¼è¯·åé AddDefaultIdentity æºã
ç¦æ¢åå¸éæ Identity èµäº§è¥è¦é²æ¢å°éæ Identity èµäº§ï¼Identity UI çæ ·å¼è¡¨å JavaScript æä»¶ï¼åå¸å° Web æ ¹ï¼è¯·å°ä»¥ä¸ ResolveStaticWebAssetsInputsDependsOn
屿§å RemoveIdentityAssets
ç®æ æ·»å å°åºç¨çé¡¹ç®æä»¶ä¸ï¼
<PropertyGroup>
<ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>
<Target Name="RemoveIdentityAssets">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
</ItemGroup>
</Target>
åç»æ¥éª¤
dotnet/AspNetCore.Docs
#5131ï¼ãä½è ï¼Rick Anderson
ASP.NET Core Identityï¼
ç¨æ·å¯ä½¿ç¨åå¨å¨ Identity ä¸çç»å½ä¿¡æ¯åå»ºå¸æ·ï¼æè å¯ä½¿ç¨å¤é¨ç»å½æä¾ç¨åºã æ¯æçå¤é¨ç»å½æä¾ç¨åºå æ¬ FacebookãGoogleãMicrosoft 叿·å Twitterã
æå ³å¦ä½å ¨å±è¦æ±ææç¨æ·è¿è¡èº«ä»½éªè¯çä¿¡æ¯ï¼è¯·åé éè¦éè¿èº«ä»½éªè¯çç¨æ·ã
GitHub 䏿ä¾äº Identity æºä»£ç ã Scaffold Identity 忥ççæçæä»¶ï¼ä»¥æ¥çä¸ Identity çæ¨¡æ¿äº¤äºã
Identity éå¸¸ä½¿ç¨ SQL Server æ°æ®åºè¿è¡é ç½®ï¼ä»¥åå¨ç¨æ·åãå¯ç åé ç½®æä»¶æ°æ®ã æè ï¼å¯ä½¿ç¨å ¶ä»æä¹ æ§åå¨ï¼ä¾å¦ Azure 表åå¨ã
卿¬ä¸»é¢ä¸ï¼ä½ å°å¦ä¹ å¦ä½ä½¿ç¨ Identity æ¥æ³¨åãç»å½å注éç¨æ·ã 注æï¼æ¨¡æ¿ä¼å°ç¨æ·çç¨æ·ååçµåé®ä»¶ç忝ç¸åçã è¥è¦æ´è¯¦ç»äºè§£å¦ä½åå»ºä½¿ç¨ Identity çåºç¨ï¼è¯·åé åç»æ¥éª¤ã
ASP.NET Core Identity ä¸ Microsoft æ è¯å¹³å°æ å ³ã 微软身份éªè¯å¹³å°æ¯ï¼
ASP.NET Core Identity å°ç¨æ·çé¢ (UI) ç»å½åè½æ·»å å° ASP.NET Core Web åºç¨ã è¥è¦ä¿æ¤ Web API å SPAï¼è¯·ä½¿ç¨ä»¥ä¸é¡¹ä¹ä¸ï¼
Duende Identity Server æ¯éç¨äº ASP.NET Core ç OpenID Connect å OAuth 2.0 æ¡æ¶ã Duende Identity Server æ¯æä»¥ä¸å®å ¨åè½ï¼
æå ³è¯¦ç»ä¿¡æ¯ï¼è¯·åé Duende Identity Server ææ¡£ï¼Duende Software ç½ç«ï¼ã
æ¥çæä¸è½½ç¤ºä¾ä»£ç ï¼ä¸è½½æ¹æ³ï¼ã
å建使ç¨èº«ä»½éªè¯ç Web åºç¨ä½¿ç¨ä¸ªäººç¨æ·å¸æ·å建âASP.NET Core Web åºç¨ç¨åºâ项ç®ã
dotnet new webapp --auth Individual -o WebApp1
ä¸è¿°å½ä»¤ä½¿ç¨ SQLite å建 Razor Web åºç¨ã è¥è¦ä½¿ç¨ LocalDB å建 Web åºç¨ï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet new webapp --auth Individual -uld -o WebApp1
çæçé¡¹ç®æä¾ ASP.NET Core Identity ä½ä¸º Razor ç±»åºã Identity Razor ç±»åºå
¬å¼å
·æ Identity
åºåçç»ç»ç¹ã ä¾å¦ï¼
åºç¨è¿ç§»ä»¥åå§åæ°æ®åºã
å¨å 管ç卿§å¶å° (PMC) ä¸è¿è¡ä»¥ä¸å½ä»¤ï¼
Update-Database
ä½¿ç¨ SQLite æ¶ï¼æ¤æ¥éª¤ä¸éè¦è¿ç§»ã
妿尿ªå®è£
dotnet ef
ï¼è¯·å®è£
å®ä½ä¸ºå
¨å±å·¥å
·ï¼
dotnet tool install --global dotnet-ef
æå ³ EF Core ç CLI ç详ç»ä¿¡æ¯ï¼è¯·åé .NET CLI ç EF Core å·¥å ·å¼ç¨ã
å¯¹äº LocalDBï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet ef database update
æµè¯æ³¨ååç»å½
è¿è¡åºç¨å¹¶æ³¨åç¨æ·ã æ ¹æ®å±å¹å¤§å°ï¼ä½ å¯è½éè¦éæ©å¯¼èªåæ¢æé®æ¥æ¥çâæ³¨åâåâç»å½â龿¥ã
æ¥ç Identity æ°æ®åºå¯ä¸è½½è®¸å¤ç¬¬ä¸æ¹å·¥å ·æ¥ç®¡ç忥ç SQLite æ°æ®åºï¼ä¾å¦ SQLite çæ°æ®åºæµè§å¨ã
é ç½® Identity æå¡è¿äºæå¡æ·»å å¨ Program.cs
ä¸ã å
¸åæ¨¡å¼æ¯æä»¥ä¸é¡ºåºè°ç¨æ¹æ³ï¼
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();
ä¸è¿°ä»£ç ç¨é»è®¤é项弿¥é ç½® Identityã å¯éè¿ä¾èµå ³ç³»æ³¨å ¥ååºç¨æä¾æå¡ã
éè¿è°ç¨ Identity å¯ç¨ UseAuthenticationã UseAuthentication
å请æ±ç®¡éæ·»å 身份éªè¯ä¸é´ä»¶ã
模æ¿çæçåºç¨ä¸ä½¿ç¨ææã app.UseAuthorization
ä¼è¢«å
嫿¥å
ï¼ç¡®ä¿å¨åºç¨æ·»å æææ¶ææ£ç¡®çé¡ºåºæ·»å å®ã å¿
é¡»æä¸è¿°ä»£ç ä¸æç¤ºç顺åºè°ç¨ UseRouting
ãUseAuthentication
å UseAuthorization
ã
æå
³ IdentityOptions
ç详ç»ä¿¡æ¯ï¼è¯·åé
IdentityOptions ååºç¨ç¨åºå¯å¨ã
æ·»å Register
ãLogin
ãLogOut
å RegisterConfirmation
æä»¶ã æç
§å°åºæ¶æ è¯æå»ºå°å
·æææç Razor 项ç®è¯´ææ¥çææ¬è䏿¾ç¤ºç代ç ã
妿å建ç项ç®çå称为 WebApp1ï¼å¹¶ä¸æªä½¿ç¨ SQLiteï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ã å¦åï¼è¯·ä¸º ApplicationDbContext
ä½¿ç¨æ£ç¡®çå½å空é´ï¼
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation"
ä½¿ç¨ SQLite æ¶ï¼è¿½å --useSqLite
æ -sqlite
ï¼
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation" --useSqLite
PowerShell 使ç¨åå·ä½ä¸ºå½ä»¤åé符ã ä½¿ç¨ PowerShell æ¶ï¼å¯¹æä»¶å表ä¸çåå·è¿è¡è½¬ä¹ï¼æè å°æä»¶å表æ¾å¨åå¼å·ä¸ï¼å¦ä¸è¿°ç¤ºä¾æç¤ºã
æå ³åºæ¶ Identity ç详ç»ä¿¡æ¯ï¼è¯·åé å°åºæ¶æ è¯æå»ºå°å ·æææç Razor 项ç®ã
æ£æ¥æ³¨åå½ç¨æ·åå» é¡µé¢ä¸çâæ³¨åâæé®æ¶ï¼ä¼è°ç¨ Register
æä½ãRegisterModel.OnPostAsync
CreateAsync(TUser) å¨ _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();
}
ç¦ç¨é»è®¤å¸æ·éªè¯
使ç¨é»è®¤æ¨¡æ¿æ¶ï¼ä¼å°ç¨æ·éå®åå° Account.RegisterConfirmation
ï¼ç¨æ·å¯ä»¥ä»ä¸éæ©ä¸ä¸ªé¾æ¥æ¥ç¡®è®¤å¸æ·ã é»è®¤å¼ Account.RegisterConfirmation
ä»
ç¨äºæµè¯ï¼åºå¨ç产åºç¨ä¸ç¦ç¨èªå¨å¸æ·éªè¯ã
è¥è¦è¦æ±ä½¿ç¨å·²ç¡®è®¤ç叿·ï¼å¹¶é²æ¢æ³¨åæ¶ç«å³ç»å½ï¼è¯·å¨ DisplayConfirmAccountLink = false
ä¸è®¾ç½® /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();
}
}
ç»å½
å¨ä»¥ä¸æ åµä¸ï¼ä¼æ¾ç¤ºâç»å½âçªä½ï¼
æäº¤ç»å½é¡µä¸ççªä½æ¶ï¼å°è°ç¨ OnPostAsync
æä½ã 对 PasswordSignInAsync
对象è°ç¨ _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();
}
è¥è¦äºè§£å¦ä½ååºææå³å®ï¼è¯·åé ASP.NET Core ä¸çææç®ä»ã
注éâæ³¨éâ龿¥ä¼è°ç¨ æä½ã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();
}
}
}
}
å¨åé¢ç代ç ä¸ï¼ä»£ç return RedirectToPage();
å¿
é¡»æ¯éå®åï¼ä»¥ä¾¿æµè§å¨æ§è¡æ°è¯·æ±å¹¶æ´æ°ç¨æ·çæ è¯ã
SignOutAsync æ¸ é¤åå¨å¨ cookie ä¸çç¨æ·å£°æã
å¨ Pages/Shared/_LoginPartial.cshtml
䏿å®äº Postï¼
@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>
æµè¯ Identity
é»è®¤ Web é¡¹ç®æ¨¡æ¿å
许å¿å访é®ä¸»é¡µã è¥è¦æµè¯ Identityï¼è¯·æ·»å [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()
{
}
}
}
妿已ç»å½ï¼è¯·æ³¨éã请è¿è¡åºç¨å¹¶éæ©Privacy龿¥ã å°è¢«éå®åå°ç»å½é¡µã
äºè§£ Identityè¥è¦æ´è¯¦ç»å°äºè§£ Identityï¼
ææä¾èµ Identity ç NuGet å é½å å«å¨ ASP.NET Core å ±äº«æ¡æ¶ä¸ã
Identity ç主å
æ¯ Microsoft.AspNetCore.Identityã æ¤å
å
å« ASP.NET Core Identity çæ ¸å¿æ¥å£éï¼å¹¶ä¸ç± Microsoft.AspNetCore.Identity.EntityFrameworkCore
å
å«ã
æå ³è¿ç§»ç°æ Identity åå¨ç详ç»ä¿¡æ¯åæå¯¼ï¼è¯·åé è¿ç§»èº«ä»½éªè¯å Identityã
设置å¯ç 强度æå ³è®¾ç½®æå°å¯ç è¦æ±ç示ä¾ï¼è¯·æ¥çé ç½®ã
AddDefaultIdentity å AddIdentityASP.NET Core 2.1 ä¸å¼å
¥äº AddDefaultIdentityã è°ç¨ AddDefaultIdentity
类似äºè°ç¨ä»¥ä¸å
容ï¼
æå ³è¯¦ç»ä¿¡æ¯ï¼è¯·åé AddDefaultIdentity æºã
ç¦æ¢åå¸éæ Identity èµäº§è¥è¦é²æ¢å°éæ Identity èµäº§ï¼Identity UI çæ ·å¼è¡¨å JavaScript æä»¶ï¼åå¸å° Web æ ¹ï¼è¯·å°ä»¥ä¸ ResolveStaticWebAssetsInputsDependsOn
屿§å RemoveIdentityAssets
ç®æ æ·»å å°åºç¨çé¡¹ç®æä»¶ä¸ï¼
<PropertyGroup>
<ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>
<Target Name="RemoveIdentityAssets">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
</ItemGroup>
</Target>
åç»æ¥éª¤
ä½è ï¼Rick Anderson
ASP.NET Core Identityï¼
ç¨æ·å¯ä½¿ç¨åå¨å¨ Identity ä¸çç»å½ä¿¡æ¯åå»ºå¸æ·ï¼æè å¯ä½¿ç¨å¤é¨ç»å½æä¾ç¨åºã æ¯æçå¤é¨ç»å½æä¾ç¨åºå æ¬ FacebookãGoogleãMicrosoft 叿·å Twitterã
æå ³å¦ä½å ¨å±è¦æ±ææç¨æ·è¿è¡èº«ä»½éªè¯çä¿¡æ¯ï¼è¯·åé éè¦éè¿èº«ä»½éªè¯çç¨æ·ã
GitHub 䏿ä¾äº Identity æºä»£ç ã Scaffold Identity 忥ççæçæä»¶ï¼ä»¥æ¥çä¸ Identity çæ¨¡æ¿äº¤äºã
Identity éå¸¸ä½¿ç¨ SQL Server æ°æ®åºè¿è¡é ç½®ï¼ä»¥åå¨ç¨æ·åãå¯ç åé ç½®æä»¶æ°æ®ã æè ï¼å¯ä½¿ç¨å ¶ä»æä¹ æ§åå¨ï¼ä¾å¦ Azure 表åå¨ã
卿¬ä¸»é¢ä¸ï¼ä½ å°å¦ä¹ å¦ä½ä½¿ç¨ Identity æ¥æ³¨åãç»å½å注éç¨æ·ã 注æï¼æ¨¡æ¿ä¼å°ç¨æ·çç¨æ·ååçµåé®ä»¶ç忝ç¸åçã è¥è¦æ´è¯¦ç»äºè§£å¦ä½åå»ºä½¿ç¨ Identity çåºç¨ï¼è¯·åé åç»æ¥éª¤ã
Microsoft æ è¯å¹³å° æ¯ï¼
ASP.NET Core Identity å°ç¨æ·çé¢ (UI) ç»å½åè½æ·»å å° ASP.NET Core Web åºç¨ã è¥è¦ä¿æ¤ Web API å SPAï¼è¯·ä½¿ç¨ä»¥ä¸é¡¹ä¹ä¸ï¼
Duende IdentityServer æ¯éç¨äº ASP.NET Core ç OpenID Connect å OAuth 2.0 æ¡æ¶ã Duende IdentityServer æ¯æä»¥ä¸å®å ¨åè½ï¼
æå ³è¯¦ç»ä¿¡æ¯ï¼è¯·åé Duende IdentityServer æ¦è¿°ã
æå ³å ¶ä»èº«ä»½éªè¯æä¾ç¨åºç详ç»ä¿¡æ¯ï¼è¯·åé éç¨äº ASP.NET Core çç¤¾åº OSS 身份éªè¯é项
æ¥çæä¸è½½ç¤ºä¾ä»£ç ï¼ä¸è½½æ¹æ³ï¼ã
å建使ç¨èº«ä»½éªè¯ç Web åºç¨ä½¿ç¨ä¸ªäººç¨æ·å¸æ·å建âASP.NET Core Web åºç¨ç¨åºâ项ç®ã
dotnet new webapp --auth Individual -o WebApp1
ä¸è¿°å½ä»¤ä½¿ç¨ SQLite å建 Razor Web åºç¨ã è¥è¦ä½¿ç¨ LocalDB å建 Web åºç¨ï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet new webapp --auth Individual -uld -o WebApp1
çæçé¡¹ç®æä¾ ASP.NET Core Identity ä½ä¸º Razor ç±»åºã Identity Razor ç±»åºå
¬å¼å
·æ Identity
åºåçç»ç»ç¹ã ä¾å¦ï¼
åºç¨è¿ç§»ä»¥åå§åæ°æ®åºã
å¨å 管ç卿§å¶å° (PMC) ä¸è¿è¡ä»¥ä¸å½ä»¤ï¼
PM> Update-Database
ä½¿ç¨ SQLite æ¶ï¼æ¤æ¥éª¤ä¸éè¦è¿ç§»ã
妿尿ªå®è£
dotnet ef
ï¼è¯·å®è£
å®ä½ä¸ºå
¨å±å·¥å
·ï¼
dotnet tool install --global dotnet-ef
æå ³ EF Core ç CLI ç详ç»ä¿¡æ¯ï¼è¯·åé .NET CLI ç EF Core å·¥å ·å¼ç¨ã
å¯¹äº LocalDBï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ï¼
dotnet ef database update
æµè¯æ³¨ååç»å½
è¿è¡åºç¨å¹¶æ³¨åç¨æ·ã æ ¹æ®å±å¹å¤§å°ï¼ä½ å¯è½éè¦éæ©å¯¼èªåæ¢æé®æ¥æ¥çâæ³¨åâåâç»å½â龿¥ã
æ¥ç Identity æ°æ®åºå¯ä¸è½½è®¸å¤ç¬¬ä¸æ¹å·¥å ·æ¥ç®¡ç忥ç SQLite æ°æ®åºï¼ä¾å¦ SQLite çæ°æ®åºæµè§å¨ã
é ç½® Identity æå¡è¿äºæå¡æ·»å å¨ ConfigureServices
ä¸ã å
¸åæ¨¡å¼æ¯è°ç¨ææ Add{Service}
æ¹æ³ï¼ç¶åè°ç¨ææ services.Configure{Service}
æ¹æ³ã
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlite(
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
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;
});
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;
});
}
ä¸è¿°çªåºæ¾ç¤ºç代ç ç¨é»è®¤é项弿¥é ç½® Identityã å¯éè¿ä¾èµå ³ç³»æ³¨å ¥ååºç¨æä¾æå¡ã
éè¿è°ç¨ Identity å¯ç¨ UseAuthenticationã UseAuthentication
å请æ±ç®¡éæ·»å 身份éªè¯ä¸é´ä»¶ã
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlite(
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
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;
});
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;
});
}
ä¸è¿°ä»£ç ç¨é»è®¤é项弿¥é ç½® Identityã å¯éè¿ä¾èµå ³ç³»æ³¨å ¥ååºç¨æä¾æå¡ã
éè¿è°ç¨ Identity å¯ç¨ UseAuthenticationã UseAuthentication
å请æ±ç®¡éæ·»å 身份éªè¯ä¸é´ä»¶ã
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
模æ¿çæçåºç¨ä¸ä½¿ç¨ææã app.UseAuthorization
ä¼è¢«å
嫿¥å
ï¼ç¡®ä¿å¨åºç¨æ·»å æææ¶ææ£ç¡®çé¡ºåºæ·»å å®ã å¿
é¡»æä¸è¿°ä»£ç ä¸æç¤ºç顺åºè°ç¨ UseRouting
ãUseAuthentication
ãUseAuthorization
å UseEndpoints
ã
æå
³ IdentityOptions
å Startup
ç详ç»ä¿¡æ¯ï¼è¯·åé
IdentityOptions ååºç¨ç¨åºå¯å¨ã
æ·»å Register
ãLogin
ãLogOut
å RegisterConfirmation
æä»¶ã æç
§å°åºæ¶æ è¯æå»ºå°å
·æææç Razor 项ç®è¯´ææ¥çææ¬è䏿¾ç¤ºç代ç ã
妿å建ç项ç®çå称为 WebApp1ï¼å¹¶ä¸æªä½¿ç¨ SQLiteï¼è¯·è¿è¡ä»¥ä¸å½ä»¤ã å¦åï¼è¯·ä¸º ApplicationDbContext
ä½¿ç¨æ£ç¡®çå½å空é´ï¼
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation"
ä½¿ç¨ SQLite æ¶ï¼è¿½å --useSqLite
æ -sqlite
ï¼
dotnet aspnet-codegenerator identity -dc WebApp1.Data.ApplicationDbContext --files "Account.Register;Account.Login;Account.Logout;Account.RegisterConfirmation" --useSqLite
PowerShell 使ç¨åå·ä½ä¸ºå½ä»¤åé符ã ä½¿ç¨ PowerShell æ¶ï¼å¯¹æä»¶å表ä¸çåå·è¿è¡è½¬ä¹ï¼æè å°æä»¶å表æ¾å¨åå¼å·ä¸ï¼å¦ä¸è¿°ç¤ºä¾æç¤ºã
æå ³åºæ¶ Identity ç详ç»ä¿¡æ¯ï¼è¯·åé å°åºæ¶æ è¯æå»ºå°å ·æææç Razor 项ç®ã
æ£æ¥æ³¨åå½ç¨æ·åå» é¡µé¢ä¸çâæ³¨åâæé®æ¶ï¼ä¼è°ç¨ Register
æä½ãRegisterModel.OnPostAsync
CreateAsync(TUser) å¨ _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();
}
ç¦ç¨é»è®¤å¸æ·éªè¯
使ç¨é»è®¤æ¨¡æ¿æ¶ï¼ä¼å°ç¨æ·éå®åå° Account.RegisterConfirmation
ï¼ç¨æ·å¯ä»¥ä»ä¸éæ©ä¸ä¸ªé¾æ¥æ¥ç¡®è®¤å¸æ·ã é»è®¤å¼ Account.RegisterConfirmation
ä»
ç¨äºæµè¯ï¼åºå¨ç产åºç¨ä¸ç¦ç¨èªå¨å¸æ·éªè¯ã
è¥è¦è¦æ±ä½¿ç¨å·²ç¡®è®¤ç叿·ï¼å¹¶é²æ¢æ³¨åæ¶ç«å³ç»å½ï¼è¯·å¨ DisplayConfirmAccountLink = false
ä¸è®¾ç½® /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();
}
}
ç»å½
å¨ä»¥ä¸æ åµä¸ï¼ä¼æ¾ç¤ºâç»å½âçªä½ï¼
æäº¤ç»å½é¡µä¸ççªä½æ¶ï¼å°è°ç¨ OnPostAsync
æä½ã 对 PasswordSignInAsync
对象è°ç¨ _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();
}
è¥è¦äºè§£å¦ä½ååºææå³å®ï¼è¯·åé ASP.NET Core ä¸çææç®ä»ã
注éâæ³¨éâ龿¥ä¼è°ç¨ æä½ã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();
}
}
}
}
å¨åé¢ç代ç ä¸ï¼ä»£ç return RedirectToPage();
å¿
é¡»æ¯éå®åï¼ä»¥ä¾¿æµè§å¨æ§è¡æ°è¯·æ±å¹¶æ´æ°ç¨æ·çæ è¯ã
SignOutAsync æ¸ é¤åå¨å¨ cookie ä¸çç¨æ·å£°æã
å¨ Pages/Shared/_LoginPartial.cshtml
䏿å®äº Postï¼
@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>
æµè¯ Identity
é»è®¤ Web é¡¹ç®æ¨¡æ¿å
许å¿å访é®ä¸»é¡µã è¥è¦æµè¯ Identityï¼è¯·æ·»å [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()
{
}
}
}
妿已ç»å½ï¼è¯·æ³¨éã请è¿è¡åºç¨å¹¶éæ©Privacy龿¥ã å°è¢«éå®åå°ç»å½é¡µã
äºè§£ Identityè¥è¦æ´è¯¦ç»å°äºè§£ Identityï¼
ææä¾èµ Identity ç NuGet å é½å å«å¨ ASP.NET Core å ±äº«æ¡æ¶ä¸ã
Identity ç主å
æ¯ Microsoft.AspNetCore.Identityã æ¤å
å
å« ASP.NET Core Identity çæ ¸å¿æ¥å£éï¼å¹¶ä¸ç± Microsoft.AspNetCore.Identity.EntityFrameworkCore
å
å«ã
æå ³è¿ç§»ç°æ Identity åå¨ç详ç»ä¿¡æ¯åæå¯¼ï¼è¯·åé è¿ç§»èº«ä»½éªè¯å Identityã
设置å¯ç 强度æå ³è®¾ç½®æå°å¯ç è¦æ±ç示ä¾ï¼è¯·æ¥çé ç½®ã
ç¦æ¢åå¸éæ Identity èµäº§è¥è¦é²æ¢å°éæ Identity èµäº§ï¼Identity UI çæ ·å¼è¡¨å JavaScript æä»¶ï¼åå¸å° Web æ ¹ï¼è¯·å°ä»¥ä¸ ResolveStaticWebAssetsInputsDependsOn
屿§å RemoveIdentityAssets
ç®æ æ·»å å°åºç¨çé¡¹ç®æä»¶ä¸ï¼
<PropertyGroup>
<ResolveStaticWebAssetsInputsDependsOn>RemoveIdentityAssets</ResolveStaticWebAssetsInputsDependsOn>
</PropertyGroup>
<Target Name="RemoveIdentityAssets">
<ItemGroup>
<StaticWebAsset Remove="@(StaticWebAsset)" Condition="%(SourceId) == 'Microsoft.AspNetCore.Identity.UI'" />
</ItemGroup>
</Target>
åç»æ¥éª¤
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4