保护应用程序资源、管理用户访问是任何现代 Web 应用的核心,ASP.NET 提供了一套强大、灵活且可扩展的身份验证和授权框架,使开发者能够轻松实现用户登录、权限控制和安全防护,核心机制包括基于 Cookie 的身份验证、JWT (JSON Web Tokens) 认证以及集成外部身份提供商 (如 Microsoft、Google、GitHub) 的 OAuth/OpenID Connect。

基石:基于 Cookie 的身份验证 (ASP.NET Core Identity)
这是构建传统 Web 应用(服务器端渲染,如 Razor Pages 或 MVC)最常用的方案。ASP.NET Core Identity 是一个完整的会员系统,处理用户注册、登录、密码管理、角色、声明、双因素认证等。
核心组件与流程:
-
配置服务 (Startup.cs –
ConfigureServices):services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); // 添加角色支持(可选) // .AddRoles<IdentityRole>() // .AddEntityFrameworkStores<ApplicationDbContext>();AddDbContext: 注册数据库上下文(存储用户数据)。AddDefaultIdentity<IdentityUser>: 设置 Identity 使用默认的IdentityUser模型和 UI。RequireConfirmedAccount控制是否需要确认邮箱。AddEntityFrameworkStores: 指定 Identity 使用 EF Core 存储数据到注册的DbContext。AddRoles: (可选)启用角色管理。
-
配置中间件 (Startup.cs –
Configure):app.UseRouting(); app.UseAuthentication(); // 启用身份验证中间件(必须放在 UseAuthorization 和 UseEndpoints 之前) app.UseAuthorization(); // 启用授权中间件 app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); });UseAuthentication(): 核心!此中间件负责读取请求中的身份信息(通常是 Cookie),并将其设置到HttpContext.User。UseAuthorization(): 依赖于UseAuthentication(),它根据策略(Roles, Claims, Requirements)检查HttpContext.User是否有权访问资源。
-
登录控制器逻辑 (AccountController.cs –
LoginPOST):[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginInputModel model, string? returnUrl = null) { returnUrl ??= Url.Content("~/"); if (ModelState.IsValid) { // 使用 SignInManager 进行登录尝试 var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { // 登录成功,重定向到 returnUrl 或首页 return LocalRedirect(returnUrl); } if (result.RequiresTwoFactor) { // 需要双因素认证,重定向到双因素验证页 return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); } if (result.IsLockedOut) { // 账户被锁定 return RedirectToPage("./Lockout"); } else { // 登录失败(用户名/密码错误) ModelState.AddModelError(string.Empty, "Invalid login attempt."); return View(model); } } return View(model); }_signInManager.PasswordSignInAsync: 核心登录方法,验证用户名/密码,如果成功,会创建包含用户身份信息的加密 Cookie 并附加到响应中,后续请求会自动携带此 Cookie。lockoutOnFailure: 控制登录失败是否触发账户锁定。- 处理各种结果状态:成功、需要双因素、账户锁定、失败。
-
授权控制 (Controller/Action 或 Razor Page):
[Authorize]属性: 要求用户必须登录才能访问该 Controller 或 Action。[Authorize] public class SecureController : Controller { ... }- 基于角色:
[Authorize(Roles = "Admin,Manager")] // 要求用户属于 Admin 或 Manager 角色 public IActionResult AdminPanel() { ... } - 基于策略 (更强大灵活): 在
ConfigureServices中定义策略:services.AddAuthorization(options => { options.AddPolicy("RequireAdmin", policy => policy.RequireRole("Admin")); options.AddPolicy("MinimumAge21", policy => policy.RequireClaim("Age", "21", "22", "23"...) // 或使用 RequirementHandlers); });在 Controller/Action 上使用:
[Authorize(Policy = "MinimumAge21")] public IActionResult Bar() { ... }
API 与 SPA 的守护者:JWT 认证
对于 Web API、单页应用 (SPA) 或移动应用后端,无状态的 JWT 是首选,用户凭据验证成功后,服务器生成一个包含用户声明 (Claims) 的 JWT 令牌返回给客户端,客户端在后续请求的 Authorization 头中携带此令牌 (Bearer <token>),服务器验证令牌的签名和有效性后建立用户身份。

核心配置与流程:
-
配置 JWT 认证服务 (Startup.cs –
ConfigureServices):services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, // 验证颁发者 ValidIssuer = Configuration["Jwt:Issuer"], // 配置文件中获取 ValidateAudience = true, // 验证接收者 ValidAudience = Configuration["Jwt:Audience"], ValidateIssuerSigningKey = true, // 验证签名密钥 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"])), // 密钥(必须安全存储!) ValidateLifetime = true, // 验证令牌有效期 ClockSkew = TimeSpan.Zero // 可容忍的时间偏差(通常设为0) }; // 可选:处理事件(如认证失败、令牌接收) // options.Events = new JwtBearerEvents { ... }; });- 设置默认的认证和质询方案为 JWT Bearer。
TokenValidationParameters: 核心配置项,定义如何验证传入的 JWT 令牌。密钥 (SecretKey) 是最高机密,必须使用安全的存储方式(如 Azure Key Vault、环境变量),绝不能硬编码在代码或配置文件中!
-
生成 JWT 令牌 (登录 API 端点):
[HttpPost("login")] [AllowAnonymous] public async Task<IActionResult> Login([FromBody] LoginModel model) { // 1. 验证用户名密码 (使用 SignInManager 或自定义逻辑) var user = await AuthenticateUser(model.Username, model.Password); if (user == null) return Unauthorized(); // 2. 创建用户声明 (Claims) var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id), // Subject (通常是用户ID) new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // JWT ID (防重放) new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), // Issued At new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Email, user.Email), // 添加自定义声明,如角色 new Claim(ClaimTypes.Role, "User"), }; // 3. 创建签名密钥 var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:SecretKey"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // 4. 创建 JWT 令牌描述 var token = new JwtSecurityToken( issuer: _config["Jwt:Issuer"], audience: _config["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(_config["Jwt:ExpiryMinutes"])), signingCredentials: creds); // 5. 序列化令牌为字符串 var tokenString = new JwtSecurityTokenHandler().WriteToken(token); // 6. 返回令牌给客户端 (通常放在响应体中) return Ok(new { Token = tokenString }); } -
保护 API 端点:
在需要认证的 Controller 或 Action 上使用[Authorize]属性(确保已配置好 JWT 服务和中间件UseAuthentication()/UseAuthorization()):[ApiController] [Route("api/[controller]")] [Authorize] // 要求所有 Action 都认证 public class SecureApiController : ControllerBase { [HttpGet("data")] public IActionResult GetData() { // 可以通过 HttpContext.User 访问用户声明 var userName = User.Identity.Name; var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); return Ok(new { message = $"Hello, {userName} (ID: {userId})! This is secure data." }); } [HttpGet("admin")] [Authorize(Roles = "Admin")] // 额外要求 Admin 角色 public IActionResult AdminOnly() { ... } }客户端需要在请求头中添加:
Authorization: Bearer <your_jwt_token_here>。
拥抱外部世界:OAuth 2.0 / OpenID Connect (第三方登录)
让用户使用已有的社交或企业账户(如 Microsoft、Google、Facebook、GitHub)登录,提升用户体验并减少管理本地密码的负担,ASP.NET Core 通过 AddAuthentication().AddXXX() 模式简化了集成。
以 GitHub 登录为例:
-
注册 GitHub OAuth App:
- 访问 GitHub Developer Settings (https://github.com/settings/developers)。
- 创建 New OAuth App,设置
Homepage URL(你的应用URL) 和Authorization callback URL(通常是https://yourdomain.com/signin-github)。
-
配置服务 (Startup.cs –
ConfigureServices):
services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; // 最终用Cookie管理会话 options.DefaultChallengeScheme = "GitHub"; // 当需要登录时,挑战GitHub方案 }) .AddCookie() // 添加Cookie认证处理程序 .AddGitHub("GitHub", options => // "GitHub"是方案名,需与DefaultChallengeScheme匹配 { options.ClientId = Configuration["GitHub:ClientId"]; options.ClientSecret = Configuration["GitHub:ClientSecret"]; // 可选:请求特定权限范围 options.Scope.Add("user:email"); // 可选:自定义事件处理,如保存额外声明 options.Events = new OAuthEvents { OnCreatingTicket = context => { // 解析GitHub返回的JSON数据,添加自定义声明 var login = context.Identity?.FindFirst(ClaimTypes.Name)?.Value; if (!string.IsNullOrEmpty(login)) { context.Identity?.AddClaim(new Claim("GitHubLogin", login)); } return Task.CompletedTask; } }; });- 核心: 组合 Cookie 方案(管理本地会话)和 GitHub 方案(处理与 GitHub 的 OAuth 流)。
ClientId和ClientSecret从 GitHub OAuth App 获取,安全存储。Scope定义请求的权限(如user:email获取邮箱)。
-
触发登录与回调处理:
- 登录入口: 在登录页提供一个链接或按钮,指向
/signin-github路由(由中间件自动处理),点击会重定向到 GitHub 授权页面。 - 用户授权: 用户在 GitHub 确认授权。
- 回调: GitHub 重定向回你配置的
CallbackPath(/signin-github),中间件自动处理:- 用授权码换取访问令牌。
- 使用访问令牌获取用户信息。
- 触发
OnCreatingTicket事件,创建包含用户信息的ClaimsIdentity。 - 使用 Cookie 方案签发一个包含此身份的 Cookie 到浏览器。
- 重定向回应用(或
ReturnUrl)。
- 登录入口: 在登录页提供一个链接或按钮,指向
-
访问用户信息:
登录成功后,在 Controller 或 Razor Page 中通过HttpContext.User访问用户声明(包括 GitHub 提供的和OnCreatingTicket中添加的)。
安全加固:不可或缺的防护措施
无论选择哪种机制,安全防护至关重要:
- HTTPS: 强制使用 HTTPS,Cookie 和令牌在 HTTP 明文传输极易被窃取。
- Cookie 安全属性:
HttpOnly: 防止 JavaScript 访问 Cookie(防 XSS 窃取)。Secure: 仅通过 HTTPS 传输。SameSite: 控制跨站请求是否发送 Cookie (Strict或Lax是推荐值,防 CSRF)。
在AddCookie配置中设置:services.ConfigureApplicationCookie(options => { options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 生产环境用 Always options.Cookie.SameSite = SameSiteMode.Lax; // 或 Strict // ... 其他配置如过期时间、路径等 });
- 防跨站请求伪造 (CSRF/XSRF): 对使用 Cookie 认证的表单提交,必须使用防伪令牌,ASP.NET Core 内置支持 (
[ValidateAntiForgeryToken]属性 + Razor 中的@Html.AntiForgeryToken()或FormTagHelper),API 通常依赖其他机制(如 JWT 在 Header 中传递,本身不易受 CSRF 影响)。 - 敏感数据保护: 绝对不要将密码、密钥 (
JWT SecretKey, OAuthClientSecret) 硬编码或明文存储在配置文件 (如appsettings.json) 中,使用:- 开发环境: 用户机密 (
dotnet user-secrets). - 生产环境: 环境变量、Azure Key Vault、HashiCorp Vault 等安全存储。
- 开发环境: 用户机密 (
- 令牌管理:
- JWT: 设置合理的过期时间 (
exp),考虑使用刷新令牌机制,安全存储密钥。 - OAuth: 妥善保管
ClientSecret,根据提供商要求处理刷新令牌。
- JWT: 设置合理的过期时间 (
- 输入验证与清理: 对所有用户输入进行严格验证和清理,防止 SQL 注入、XSS 等攻击。
- 安全标头: 使用中间件(如
NWebsec或自定义)设置 HTTP 安全标头 (Content-Security-Policy,X-Content-Type-Options,X-Frame-Options,Strict-Transport-Security等)。 - 日志记录与监控: 记录关键安全事件(登录成功/失败、授权失败)并设置告警。
选择之道与应用场景
- 传统 Web 应用 (MVC/Razor Pages):
ASP.NET Core Identity+ Cookie 认证 是成熟、全面的解决方案。 - API 后端 / SPA 前端 / 移动应用: JWT 认证 提供无状态、跨域友好的安全通信。
- 简化用户登录 / 社交集成: OAuth 2.0 / OpenID Connect 是连接 Google, Microsoft, Facebook, GitHub 等外部身份提供商的标准方式,常与本地 Cookie 或 JWT 结合使用(外部登录后颁发本地令牌/Cookie)。
ASP.NET 的身份验证机制提供了从基础到高级、从本地到第三方的全方位解决方案,理解 Cookie、JWT 和 OAuth/OpenID Connect 的核心原理、配置方式和适用场景,是构建安全、可靠、用户体验良好的 Web 应用的关键,通过 AddAuthentication()、UseAuthentication()、[Authorize] 以及各种 AddXXX() 扩展方法 (AddCookie, AddJwtBearer, AddGitHub 等),开发者可以高效地集成所需的安全方案。切记,安全是一个持续的过程,严格的配置(特别是 HTTPS、Cookie 属性、密钥管理)和遵循最佳实践(防 CSRF、输入验证、安全标头)与选择正确的认证机制同等重要。
您目前在项目中主要使用哪种身份验证方案?是经典的 Identity + Cookie,灵活的 JWT for API,还是便捷的第三方登录?或者遇到了特定的集成挑战?欢迎分享您的实践经验和遇到的问题!
原创文章,作者:世雄 - 原生数据库架构专家,如若转载,请注明出处:https://idctop.com/article/7800.html