- 示例工程
【免费下载链接】practical-aspnetcore
Practical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.
本文基于 practical-aspnetcore 仓库的 Security 目录,系统讲解 ASP.NET Core 中两大核心安全能力:基于Identity Core + EF Core + SQLite的用户认证体系(注册、登录、登出、受限访问),以及Data Protection 数据保护栈的六种密钥存储/加密方案(默认设置、Azure Blob、Azure Key Vault、EF Core、Redis、自定义 XML 加密器)。读完本文,你将掌握如何在真实项目中注册 Identity、配置密码策略、保护受限路由,并根据部署环境为 Data Protection 选择正确的密钥持久化与加密方案。
仓库 Security 部分全景
仓库的 projects/security/README.md 是整个安全主题的入口,其下包含7 个可运行示例:
| 目录 | 主题 | 示例数 |
|---|---|---|
| authentication-with-identity | Identity Core + EF Core + SQLite 基础认证 | 1 |
| dataprotection | Data Protection 密钥存储与加密 | 6 |
其中 Data Protection 部分又细分为六个独立示例,覆盖了从"零配置开箱即用"到"密钥托管到 Azure/Redis/数据库"的完整演进路径,本文后续会逐一展开。
第一部分:基于 Identity Core 的用户认证实战
项目定位与核心能力
authentication-with-identity 是一个展示 Identity Core 基础认证流程的独立可运行示例,其 README 明确列出了它提供的四项功能:
- 注册用户(Register a user)
- 登录(Sign in)
- 登出(Sign out)
- 访问仅限已认证用户查看的页面(Access to a page restricted to the authenticated users)
示例选择 SQLite 作为数据库,是因为 SQLite 以 DLL 形式随项目分发,可以让示例"开箱即跑"、无需额外安装数据库服务;同时 Identity 支持大量其他数据库提供程序,生产环境可平滑切换到 SQL Server、PostgreSQL、MySQL 等。
项目结构解析
从源码结构看,该项目是一个典型的 MVC 三层结构:
Controllers/:AccountController(认证相关动作)、HomeController(公开首页)、RestrictedAreaController(受限区域);Data/ApplicationDbContext.cs:EF Core 数据库上下文;Services/:IEmailSender与EmailSender(邮件发送占位实现);ViewModels/:登录/注册/受限页面的视图模型;Views/:Account、Home、RestrictedArea、Shared 对应的 Razor 视图。
项目目标框架为net10.0(见 authentication-with-identity.csproj),依赖以下 NuGet 包:
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.11" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.11" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.11" />数据库上下文:IdentityDbContext
Identity 的默认用户/角色模型(IdentityUser、IdentityRole)需要落库,示例通过 ApplicationDbContext.cs 继承IdentityDbContext完成:
public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } }IdentityDbContext内置了用户、角色、声明、登录信息等全部 Identity 所需实体集,无需手动声明任何DbSet。
服务注册与密码策略配置
核心配置集中在 Program.cs 的Startup类中,分三个层次完成:
1. 数据库服务(SQLite):
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlite("Data Source=identity_db.db"));连接串Data Source=identity_db.db会在应用目录生成 SQLite 数据库文件。
2. Identity 服务(用户 + 角色 + EF 存储 + 默认令牌提供程序):
services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders();3. Identity 选项(示例刻意放宽的密码策略):
services.Configure<IdentityOptions>(options => { // 最小密码策略:仅要求长度 >= 3 options.Password.RequireDigit = false; options.Password.RequiredLength = 3; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequireLowercase = false; // 登录设置:不强制邮箱确认 options.SignIn.RequireConfirmedEmail = false; });实战提醒:上述策略是为了降低示例演示门槛(密码只需 3 位)。生产环境务必启用更强的密码策略(默认要求大小写字母、数字、特殊字符与 6 位以上长度),并开启
RequireConfirmedEmail、账户锁定等选项。
请求管道中的认证中间件
认证要真正生效,还必须在管线中按顺序注册中间件(顺序错误是常见的认证失效根因):
app.UseRouting(); app.UseAuthentication(); // 先认证 app.UseAuthorization(); // 再授权 app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); });Program.Main中还通过EnsureDatabase(host)在启动时调用dbContext.Database.EnsureCreated()自动建库,省去手动迁移步骤(源码见 Program.cs)。
AccountController:注册、登录、登出、邮箱确认
AccountController.cs 是认证逻辑的核心,通过构造函数注入UserManager<IdentityUser>与SignInManager<IdentityUser>。
注册(Register):接收RegisterViewModel,创建IdentityUser(用户名与邮箱同值)后调用userManager.CreateAsync(user, model.Password);成功后生成邮箱确认令牌并发送确认链接,随后直接调用signInManager.SignInAsync(user, isPersistent: false)完成自动登录并跳转首页。
登录(Login):调用signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);失败时通过ModelState.AddModelError(string.Empty, "Invalid login attempt.")返回通用错误信息(避免泄露"用户不存在"等枚举攻击信息)。
登出(Logout):signInManager.SignOutAsync()后重定向首页。
邮箱确认(ConfirmEmail):校验userId与code参数后依次调用FindByIdAsync与ConfirmEmailAsync,确认成功渲染ConfirmEmail视图。
注意:AccountController类级别标有[Authorize],而Register、Login、ConfirmEmail动作用[AllowAnonymous]放行——这是一种"默认安全、按需开放"的经典写法。
受限区域:Authorization 的最小示例
RestrictedAreaController.cs 只在类级别标注[Authorize],未登录访问会被中间件拦截并跳转登录页。Index动作通过userManager.GetUserAsync(User)读取当前登录用户,并组装出用户名、邮箱、手机号与邮箱确认状态的IndexViewModel,渲染到 Views/RestrictedArea/Index.cshtml 展示"个人资料"页。
视图模型与表单验证
- RegisterViewModel.cs:
Email([Required]+[EmailAddress])、Password([Required]+[DataType(DataType.Password)]); - LoginViewModel.cs:在注册模型基础上增加
RememberMe布尔选项。
注册/登录视图(Register.cshtml、Login.cshtml)使用 Tag Helper(asp-for、asp-validation-for、asp-validation-summary)实现服务端验证消息的自动渲染,POST 动作同时标注[ValidateAntiForgeryToken]防跨站请求伪造。
导航与登录状态展示
_LoginPartial.cshtml 通过@inject SignInManager<IdentityUser>与@inject UserManager<IdentityUser>在视图中直接访问认证服务,用SignInManager.IsSignedIn(User)分支渲染:
- 已登录:显示问候语(
Hello @User.Identity.Name!)、Home/受限区域导航与 Logout 表单; - 未登录:显示 Register、Login 链接(受限区域链接同样可见,但访问会被拦截)。
该分部视图被 _Layout.cshtml 以<partial name="_LoginPartial" />引入到全局布局中。
运行示例
在 authentication-with-identity 目录执行:
dotnet restore dotnet run启动后访问首页,依次体验"注册 → 自动登录 → 访问受限区域 → 登出 → 重新登录"的完整闭环。
第二部分:Data Protection 数据保护栈(六种密钥方案)
为什么需要 Data Protection?
ASP.NET Core 的 Data Protection 栈用于对需持久化或传输的敏感数据(如认证 Cookie、防伪令牌、临时数据)进行加密与签名。它的核心是密钥管理:密钥必须以某种方式持久化,并在多实例部署时保持一致,否则不同实例间加密的数据无法互相解密。仓库 dataprotection/README.md 给出了六种从简到繁的方案,下面逐一实战解析。
方案一:默认设置(零配置开箱即用)
default-settings 展示 Data Protection 的最简用法——只需一行注册:
builder.Services.AddDataProtection();对应 Program.cs 中的完整最小化宿主代码。
默认行为:密钥默认持久化到本机用户配置文件目录(%LOCALAPPDATA%\ASP.NET\DataProtection-Keys),密钥环自动按 90 天周期轮换(从仓库中 custom-encryptor 的示例密钥文件 可以看到真实的密钥 XML 结构:包含creationDate、activationDate、expirationDate、AES_256_CBC加密算法与HMACSHA256校验算法等元数据)。
加密/解密使用方式(见 Pages/Index.cshtml.cs):
// 通过 IDataProtectionProvider 创建"用途化"的 protector _protector = dataProtectionProvider.CreateProtector("DP.DefaultSettings.V1"); // 加密 EncryptedString = _protector.Protect(StringToEncrypt); // 解密 DeCryptedString = _protector.Unprotect(EncryptedString);页面提交后同时展示密文与明文,直观验证加解密闭环(界面见 assets/main-page.png)。
关键概念:
CreateProtector("DP.DefaultSettings.V1")中的用途字符串(purpose)是 Data Protection 的隔离机制——不同用途创建的 protector 互相无法解密对方的数据,防止密文被"重放"到其他场景。
方案二:密钥持久化到 Azure Blob 存储
azure-storage-blob-key-store 解决多实例共享密钥问题:把密钥环 XML 放入 Azure Blob,所有实例读取同一份密钥。
appsettings 配置(见 appsettings.json,仓库默认使用UseDevelopmentStorage=true即 Azurite 本地模拟器):
"DataProtection": { "StorageConnectionString": "UseDevelopmentStorage=true", "ContainerName": "container-dp-keys", "BlobName": "dpkeys.xml" }注册代码(见 Startup.cs):
var storageConnectionString = Configuration["DataProtection:StorageConnectionString"]; var containerName = Configuration["DataProtection:ContainerName"]; var blobName = Configuration["DataProtection:BlobName"]; services.AddDataProtection() .PersistKeysToAzureBlobStorage(storageConnectionString, containerName, blobName);三个配置项的含义:StorageConnectionString为存储账号连接串、ContainerName为容器名、BlobName为密钥 XML 文件名(默认建议dpkeys.xml)。
方案三:Azure Key Vault 加密 + Blob 存储(密钥的密钥)
azure-keyvault-storage-blob-key-store 是方案二的增强:密钥环虽然放在 Blob 里,但其主密钥(master key)本身由 Azure Key Vault 加密,实现"静态密钥加密"。
appsettings 配置(见 appsettings.json):
"DataProtection": { "StorageConnectionString": "UseDevelopmentStorage=true", "ContainerName": "container-dp-keys", "BlobName": "dpkeys.xml", "KeyId": "https://<your key vault name>.vault.azure.net/keys/<your key name>/<your key version>" }KeyId必须是完整的 Key Vault 密钥 URI(含密钥版本)。
注册代码(见 Startup.cs):
var keyId = Configuration["DataProtection:KeyId"]; services.AddDataProtection() // 密钥加密:使用 Azure Key Vault .ProtectKeysWithAzureKeyVault( new Uri(keyId), new ChainedTokenCredential( new ManagedIdentityCredential(), new AzureCliCredential())) // 密钥持久化:使用 Azure Blob Storage .PersistKeysToAzureBlobStorage(storageConnectionString, containerName, blobName);注意凭据链ChainedTokenCredential:优先尝试 Azure 托管身份(生产环境),失败则回退到 Azure CLI 登录(本地开发),实现了"开发/生产同代码"的认证切换。
方案四:密钥存储到关系数据库(EF Core)
ef-key-store 把密钥环存进关系数据库,适合已有数据库基础设施、希望密钥与业务数据同库管理的团队。示例目标数据库为 SQL Server。
关键步骤 1:引入包与自定义 DbContext(见 DataProtectionKeyContext.cs):
// 包:Microsoft.AspNetCore.DataProtection.EntityFrameworkCore class DataProtectionKeyContext : DbContext, IDataProtectionKeyContext { public DataProtectionKeyContext(DbContextOptions<DataProtectionKeyContext> options) : base(options) { } public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } }关键步骤 2:创建并应用 EF 迁移(README 给出的命令):
dotnet ef migrations add AddDataProtectionKeys --context DataProtectionKeyContext dotnet ef database update --context DataProtectionKeyContext仓库已预置迁移产物 20210610013150_AddDataProtectionKeys.cs,从中可以看到生成的DataProtectionKeys表结构:Id(自增主键)、FriendlyName、Xml(密钥 XML 内容)三个字段。
关键步骤 3:注册 DbContext 与密钥持久化(见 Startup.cs):
var storageConnectionString = Configuration["DataProtection:DefaultConnection"]; services.AddDbContext<DataProtectionKeyContext>(options => options.UseSqlServer(storageConnectionString)); services.AddDataProtection() .PersistKeysToDbContext<DataProtectionKeyContext>();连接串在 appsettings 的DataProtection:DefaultConnection节点下配置。
方案五:密钥存储到 Redis
redis-key-store 适合已使用 Redis 的分布式架构,把密钥环作为 Redis 字符串键保存,多实例共享且天然支持高可用。
appsettings 配置(见 appsettings.json,仓库默认本地localhost:6379无密码):
"DataProtection": { "RedisServer": "localhost:6379", "RedisPassword": "", "RedisKey": "dp-keys" }注册代码(见 Program.cs,该示例采用 WebApplication 最小宿主):
var redisServer = configuration["DataProtection:RedisServer"]; var redisPassword = configuration["DataProtection:RedisPassword"]; var redisKey = configuration["DataProtection:RedisKey"]; var redis = ConnectionMultiplexer.Connect($"{redisServer},password={redisPassword}"); builder.Services.AddDataProtection() .PersistKeysToStackExchangeRedis(redis, redisKey);RedisServer为 Redis 地址与端口,RedisPassword为密码(本地无密码时留空),RedisKey为密钥环在 Redis 中的键名(默认建议dp-keys)。
方案六:自定义 XML 加密器
custom-encryptor 展示如何通过实现IXmlEncryptor/IXmlDecryptor接口,完全接管密钥 XML 的加密逻辑,用于对接公司内部加密体系或国密等特殊算法。
1. 加密器实现(CustomXmlEncryptor.cs):Encrypt接收明文密钥 XML,返回EncryptedXmlInfo(新 XML 元素 + 对应的解密器类型)。示例中为演示自定义机制,故意不真正加密,仅把明文包一层unencryptedKey标记:
public EncryptedXmlInfo Encrypt(XElement plaintextElement) { var newElement = new XElement("unencryptedKey", new XComment(" This key is not encrypted. "), new XElement(plaintextElement)); return new EncryptedXmlInfo(newElement, typeof(CustomXmlDecryptor)); }2. 解密器实现(CustomXmlDecryptor.cs):Decrypt从包装元素中取出原始密钥 XML:
public XElement Decrypt(XElement encryptedElement) { return new XElement(encryptedElement.Elements().Single()); }3. 扩展方法接入配置系统(DependencyInjection.cs):通过注册IConfigureOptions<KeyManagementOptions>把自定义加密器挂到KeyManagementOptions.XmlEncryptor上:
public static IDataProtectionBuilder UseXmlEncryptor( this IDataProtectionBuilder builder, Func<IServiceProvider, IXmlEncryptor> factory) { builder.Services.AddSingleton<IConfigureOptions<KeyManagementOptions>>(serviceProvider => { var instance = factory(serviceProvider); return new ConfigureOptions<KeyManagementOptions>(options => { options.XmlEncryptor = instance; }); }); return builder; }4. 组合使用(见 Program.cs):密钥持久化到本地temp-keys目录 + 自定义加密器:
var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys"); builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(keysFolder)) .UseXmlEncryptor(s => new CustomXmlEncryptor(s));仓库中预置了一个真实生成的密钥文件 key-7c2fc905-2a4b-4da4-b168-c0764b406eff.xml,其encryptedSecret节点明确标注decryptorType="CustomEncryptor.Services.CustomXmlDecryptor, ...",可以直观看到自定义加密器如何被写进密钥环的 XML 序列化结果。
README 原注:本演示把密钥存放在磁盘临时目录,不适用于生产环境。生产场景应至少使用 DPAPI/证书加密密钥(或接入 Key Vault 等托管加密服务)。
六种方案的选择指南
| 方案 | 密钥存储位置 | 密钥加密 | 适用场景 |
|---|---|---|---|
| 默认设置 | 本机用户目录 | 本机 DPAPI | 单实例开发/演示 |
| Azure Blob | Azure Blob 存储 | 无(明文 XML) | 多实例共享密钥环 |
| Key Vault + Blob | Azure Blob 存储 | Azure Key Vault | 多实例 + 静态加密合规要求 |
| EF Core | 关系数据库 | 无 | 已有数据库基础设施的团队 |
| Redis | Redis | 无 | 已用 Redis 的分布式架构 |
| 自定义加密器 | 任意(示例为本地目录) | 自定义算法 | 对接内部/国密加密体系 |
小结
通过本仓库 Security 部分的 7 个示例,可以完整掌握 ASP.NET Core 安全体系的两个核心面:认证授权(Identity Core 的注册/登录/登出/受限访问完整闭环,含密码策略、反伪造令牌、[Authorize]声明式授权)与数据保护(密钥环的持久化与加密的六种生产级方案)。建议按"默认设置跑通 → 换存储提供程序 → 加密钥加密 → 接自定义加密器"的顺序逐个运行示例,并在真实项目中优先选择"集中式密钥存储 + 托管密钥加密"的组合方案。
延伸阅读
- 仓库安全主题入口:projects/security/README.md
- Identity 认证示例完整源码:authentication-with-identity
- Data Protection 示例目录:dataprotection
- 仓库总览:README.md 与 STRUCTURE.md
- 示例工程
【免费下载链接】practical-aspnetcore
Practical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.
相关推荐
practical-aspnetcore 实战:在 Redis 中持久化 ASP.NET Core 数据保护密钥(Data Protection Redis Key Store)
practical aspnetcore 实战:在 Redis 中持久化 ASP.NET Core 数据保护密钥(Data Protection Redis K
示例工程ASP.NET Core 简化认证实战:JWT、Cookie、混合方案与 Identity API(practical-aspnetcore 全示例解析)
ASP.NET Core 简化认证实战:JWT、Cookie、混合方案与 Identity API(practical aspnetcore 全示例解析) 本文
示例工程基于 JWT 认证与令牌刷新保护 REST API —— practical-aspnetcore Pathway 1 实战指南(Exercise 6)
基于 JWT 认证与令牌刷新保护 REST API —— practical aspnetcore Pathway 1 实战指南(Exercise 6) 本文是
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考