☰
ASP.NET Core 安全实践指南:Identity 认证与 Data Protection 数据保护实战(基于 practical-aspnetcore 仓库)
2026/10/16 2:11:52 网站建设 项目流程
  • 示例工程

【免费下载链接】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.

项目地址:https://gitcode.com/gh_mirrors/pr/practical-aspnetcore
点击查看免费下载

本文基于 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-identityIdentity Core + EF Core + SQLite 基础认证1
dataprotectionData 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 BlobAzure Blob 存储无(明文 XML)多实例共享密钥环
Key Vault + BlobAzure Blob 存储Azure Key Vault多实例 + 静态加密合规要求
EF Core关系数据库无已有数据库基础设施的团队
RedisRedis无已用 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.

项目地址:https://gitcode.com/gh_mirrors/pr/practical-aspnetcore
点击查看免费下载
上一篇:D2RML终极指南:5分钟掌握暗黑2重制版多开神器
下一篇:Ryujinx模拟器深度解析:如何在PC上免费畅玩Switch游戏的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询