尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

ASP.NET Core Web API 身份验证与授权实战:JWT配置、策略与常见问题排查

ASP.NET Core Web API 身份验证与授权实战:JWT配置、策略与常见问题排查 这类主题最容易让新手绕晕也最容易让有经验的人忽略细节。很多人一看到“身份验证与授权”就觉得是老生常谈但实际落地时问题往往出在几个关键环节没理清比如中间件顺序错了、授权策略没生效、或者生产环境下的令牌Token管理一团糟。这篇文章不打算从零开始讲概念而是直接切入一个更实际的角度如何在一个典型的 ASP.NET Core Web API 项目中把身份验证和授权跑通并且能稳定应对单用户登录、API调用、角色权限控制这些常见场景。我会把重点放在配置顺序、常见坑点和排查思路上让你看完能立刻在自己的项目里复现和调试。适合正在开发或维护 .NET Web 应用、需要集成登录和权限控制但被各种AddAuthentication、AddAuthorization、[Authorize]和策略搞得有点乱的开发者。最关键的是理解中间件管道里事件的触发顺序以及授权策略如何与实际用户声明Claims绑定。1. 先拆清楚身份验证Authentication和授权Authorization到底在干什么很多人会把这两个词混用但在 .NET 特别是 ASP.NET Core 的语境下它们有明确的先后分工。理解这个分工是后面所有配置不跑偏的前提。身份验证回答的是“你是谁”这个问题。它的核心工作是校验用户提交的凭证比如用户名密码、JWT Token、Cookie如果凭证有效就创建一个代表用户身份的ClaimsPrincipal对象并把它放到当前 HTTP 上下文中HttpContext.User。这个过程本身不判断用户能做什么。授权回答的是“你能做什么”这个问题。它发生在身份验证之后依据HttpContext.User中的信息主要是角色 Roles 和声明 Claims来判断当前用户是否有权限执行某个操作比如访问某个控制器 Action、读取某个资源。常见的[Authorize]特性、策略Policy都是授权的一部分。一个最直观的比喻进办公楼时保安检查你的工牌身份验证确认你是公司员工后放行。但进入具体会议室授权时可能需要你的工牌具备“项目经理”角色或者拥有“会议审批”这个特定权限声明。在代码层面这个分工体现在Startup.cs或Program.cs的配置顺序上也体现在中间件Middleware的添加顺序上。顺序错了后面所有功能都可能失灵。2. 环境与项目准备从干净的 Web API 模板开始为了避免被现有项目复杂的依赖干扰我建议你直接用命令行或 IDE 创建一个全新的 ASP.NET Core Web API 项目来跟随操作。这样环境干净问题也容易隔离。打开终端执行以下命令dotnet new webapi -n AuthDemoApi cd AuthDemoApi这会创建一个基于最新稳定版 .NET可能是 8.0 或 9.0的 Web API 项目模板。模板默认使用了 Minimal API 或 Controller 风格我们以 Controller 为例因为它更贴近传统 MVC 结构权限控制也更直观。创建好后先用dotnet run命令跑起来访问https://localhost:5001/swagger或http://localhost:5000/swagger确认基础的 WeatherForecast API 能正常返回数据。这一步是验证你的基础开发环境.NET SDK、运行时没问题。接下来我们需要引入身份验证和授权相关的 NuGet 包。对于大多数场景尤其是 API 项目JWTJSON Web Token是首选方案。在项目根目录执行dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer这个包提供了用于验证 JWT Token 的中间件和配置支持。如果是 .NET 8 或更高版本这个包通常已经隐含在元包中但显式添加可以确保版本明确。3. 配置身份验证让 API 认识 JWT Token配置的入口在Program.cs文件。我们需要在var app builder.Build();这行代码之前添加服务注册和中间件配置。3.1 注册身份验证服务首先在builder.Services部分添加身份验证服务。这里的关键是调用AddAuthentication并指定默认的认证方案Scheme然后通过AddJwtBearer来配置 JWT 的详细参数。using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; var builder WebApplication.CreateBuilder(args); // 添加 Controllers 服务如果模板没有的话 builder.Services.AddControllers(); // 1. 注册身份验证服务 builder.Services.AddAuthentication(options { // 设置默认的认证方案为 JWT Bearer options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options { // Token 验证参数配置 options.TokenValidationParameters new TokenValidationParameters { // 验证签发者Issuer ValidateIssuer true, ValidIssuer builder.Configuration[Jwt:Issuer], // 从配置读取 // 验证接收者Audience ValidateAudience true, ValidAudience builder.Configuration[Jwt:Audience], // 从配置读取 // 验证令牌的签名密钥 ValidateIssuerSigningKey true, IssuerSigningKey new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration[Jwt:Key])), // 从配置读取 // 验证令牌有效期 ValidateLifetime true, // 允许的时钟偏移量解决服务器间微小时间差 ClockSkew TimeSpan.Zero // 生产环境可适当放宽如 TimeSpan.FromMinutes(5) }; // 可选自定义事件用于复杂场景的日志或处理 options.Events new JwtBearerEvents { OnAuthenticationFailed context { // 可以在这里记录认证失败的日志 Console.WriteLine($认证失败: {context.Exception.Message}); return Task.CompletedTask; }, OnTokenValidated context { // Token 验证成功后可以在这里添加自定义的 Claims 或逻辑 Console.WriteLine(Token 验证成功); return Task.CompletedTask; } }; });这段代码有几个关键点DefaultAuthenticateScheme和DefaultChallengeScheme通常设为同一个告诉系统默认用 JWT Bearer 方案来处理认证和质询比如返回 401 状态码。TokenValidationParameters这是核心。ValidateIssuer、ValidateAudience、ValidateIssuerSigningKey、ValidateLifetime这几个开关建议在开发初期都设为true确保 Token 被严格校验。生产环境可以根据安全要求调整。配置来源Issuer、Audience、Key这些敏感信息应该放在appsettings.json或更安全的环境变量、密钥管理服务里。这里从builder.Configuration读取。在appsettings.json中添加对应的配置节{ Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, Jwt: { Issuer: AuthDemoApi, Audience: AuthDemoApiClient, Key: YourSuperSecretKeyHere_MustBeLongEnough_32CharsPlus // 至少32位 }, AllowedHosts: * }注意Jwt:Key是签名密钥必须足够长且复杂。示例中的字符串只是一个占位符绝对不要在生产环境使用。应该使用强随机生成的密钥并通过dotnet user-secrets或 Azure Key Vault 等方式管理。3.2 注册授权服务身份验证服务注册好后紧接着注册授权服务。这一步相对简单但它是后续使用策略Policy和角色Role的基础。// 2. 注册授权服务 builder.Services.AddAuthorization();AddAuthorization方法有很多重载可以用于预定义授权策略。我们先使用最简单的无参形式后续再添加策略。3.3 添加中间件到请求管道服务注册完后必须在请求管道Pipeline中添加对应的中间件而且顺序至关重要。正确的顺序是先认证再授权最后是端点映射。var app builder.Build(); // 配置 HTTP 请求管道 if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // *** 关键顺序先认证再授权 *** app.UseAuthentication(); // 身份验证中间件 app.UseAuthorization(); // 授权中间件 app.MapControllers(); app.Run();为什么顺序不能错UseAuthentication中间件负责读取请求头如Authorization: Bearer token中的 Token进行验证并将成功的用户身份ClaimsPrincipal设置到HttpContext.User。UseAuthorization中间件则依赖HttpContext.User中的信息来做权限判断。如果顺序反了授权中间件执行时用户身份还没被设置所有[Authorize]保护的路由都会直接失败。4. 实现一个简单的登录接口来颁发 Token现在API 已经具备了验证 Token 的能力但我们还没有生成 Token 的源头。接下来我们创建一个简单的登录接口它接收用户名密码验证通过后生成一个 JWT Token 返回给客户端。4.1 创建登录请求模型和用户服务模拟首先在项目根目录创建一个Models文件夹并添加一个登录请求的模型。Models/LoginRequest.cs:namespace AuthDemoApi.Models { public class LoginRequest { public string Username { get; set; } public string Password { get; set; } } }为了简化我们模拟一个用户服务而不是连接真实数据库。创建一个Services文件夹。Services/IUserService.cs:using AuthDemoApi.Models; namespace AuthDemoApi.Services { public interface IUserService { TaskUser? AuthenticateAsync(string username, string password); } // 模拟用户实体 public class User { public int Id { get; set; } public string Username { get; set; } public string PasswordHash { get; set; } // 实际应用中应是哈希值 public string Role { get; set; } // 用户角色如 Admin, User public Liststring Permissions { get; set; } // 用户权限列表 } }Services/UserService.cs:using AuthDemoApi.Models; namespace AuthDemoApi.Services { public class UserService : IUserService { // 模拟一个内存中的用户列表 private readonly ListUser _users new ListUser { new User { Id 1, Username admin, PasswordHash admin123, Role Admin, Permissions new Liststring { Read, Write, Delete } }, new User { Id 2, Username user1, PasswordHash user123, Role User, Permissions new Liststring { Read } } }; public TaskUser? AuthenticateAsync(string username, string password) { // 警告这里为了演示直接比较明文密码。生产环境必须使用密码哈希加盐比较 var user _users.SingleOrDefault(u u.Username username u.PasswordHash password); return Task.FromResult(user); } } }重要安全提醒上述代码为了演示直接比较明文密码。真实项目绝对不可以这样做必须使用像BCrypt.Net-Next或 ASP.NET Core Identity 中的PasswordHasher来进行哈希加盐验证。在Program.cs中注册这个服务builder.Services.AddScopedIUserService, UserService();4.2 创建 Token 生成帮助类我们需要一个工具来生成 JWT Token。创建一个Helpers文件夹。Helpers/JwtHelper.cs:using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace AuthDemoApi.Helpers { public class JwtHelper { private readonly IConfiguration _configuration; public JwtHelper(IConfiguration configuration) { _configuration configuration; } public string GenerateToken(string username, string role, Liststring? permissions null) { // 1. 准备签名密钥 var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration[Jwt:Key])); var credentials new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // 2. 创建用户声明Claims var claims new ListClaim { new Claim(JwtRegisteredClaimNames.Sub, username), // 主题通常放用户名 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // JWT ID唯一标识 new Claim(ClaimTypes.Name, username), // 标准 Name 声明 new Claim(ClaimTypes.Role, role) // 标准 Role 声明 }; // 3. 添加自定义权限声明如果需要 if (permissions ! null) { claims.AddRange(permissions.Select(p new Claim(Permission, p))); } // 4. 创建 Token 描述 var token new JwtSecurityToken( issuer: _configuration[Jwt:Issuer], audience: _configuration[Jwt:Audience], claims: claims, expires: DateTime.UtcNow.AddHours(2), // 令牌有效期例如2小时 signingCredentials: credentials ); // 5. 生成最终的 Token 字符串 return new JwtSecurityTokenHandler().WriteToken(token); } } }在Program.cs中注册这个帮助类builder.Services.AddSingletonJwtHelper();4.3 创建登录控制器现在创建一个 API 控制器来处理登录请求。Controllers/AuthController.cs:using AuthDemoApi.Helpers; using AuthDemoApi.Models; using AuthDemoApi.Services; using Microsoft.AspNetCore.Mvc; namespace AuthDemoApi.Controllers { [Route(api/[controller])] [ApiController] public class AuthController : ControllerBase { private readonly IUserService _userService; private readonly JwtHelper _jwtHelper; public AuthController(IUserService userService, JwtHelper jwtHelper) { _userService userService; _jwtHelper jwtHelper; } [HttpPost(login)] public async TaskIActionResult Login([FromBody] LoginRequest request) { // 1. 验证请求模型 if (request null || string.IsNullOrEmpty(request.Username) || string.IsNullOrEmpty(request.Password)) { return BadRequest(用户名和密码不能为空。); } // 2. 验证用户凭证 var user await _userService.AuthenticateAsync(request.Username, request.Password); if (user null) { return Unauthorized(用户名或密码错误。); } // 3. 生成 JWT Token var token _jwtHelper.GenerateToken(user.Username, user.Role, user.Permissions); // 4. 返回 Token return Ok(new { Token token }); } } }4.4 测试登录接口运行项目 (dotnet run)使用 Postman、curl 或 Swagger UI 测试登录接口。请求示例 (POSThttps://localhost:5001/api/auth/login):{ username: admin, password: admin123 }成功响应示例{ token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImp0aSI6ImY5YzQ0ZTMwLTAwMzYtND...很长一串 }拿到这个 Token就完成了身份验证的“颁发”环节。接下来我们要用这个 Token 去访问受保护的资源。5. 使用授权保护 API 资源现在 API 能发 Token 了也能验 Token 了。下一步就是告诉某些 API 端点“你需要有效的 Token 才能访问”甚至“你需要特定的角色或权限才能访问”。5.1 使用[Authorize]特性进行基础保护最简单的授权方式是在控制器Controller或动作方法Action上添加[Authorize]特性。修改自带的WeatherForecastController或新建一个测试控制器。Controllers/ProtectedController.cs:using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace AuthDemoApi.Controllers { [Route(api/[controller])] [ApiController] [Authorize] // 整个控制器都需要认证 public class ProtectedController : ControllerBase { [HttpGet(public)] [AllowAnonymous] // 此方法允许匿名访问覆盖控制器的 [Authorize] public IActionResult Public() { return Ok(new { message 这个接口任何人都可以访问。 }); } [HttpGet(secure)] public IActionResult Secure() { // 由于控制器有 [Authorize]此方法需要有效 Token 才能访问 var userName User.Identity?.Name; // 可以从 HttpContext.User 获取认证信息 return Ok(new { message $你好{userName}你已成功访问受保护接口。 }); } } }测试访问GET /api/protected/public不需要 Token应该成功。访问GET /api/protected/secure不带 Token 或 Token 无效应返回401 Unauthorized。访问GET /api/protected/secure并在请求头中添加Authorization: Bearer 你的Token应该成功并返回包含用户名的消息。5.2 基于角色Role的授权很多时候我们不仅需要用户登录还需要他具备特定角色。这可以通过[Authorize(Roles “角色名”)]来实现。首先确保在生成 Token 时用户的角色信息已经作为ClaimTypes.Role类型的声明Claim添加进去了我们在JwtHelper中已经做了。然后在控制器方法上指定角色[HttpGet(admin-only)] [Authorize(Roles Admin)] // 只有角色为 “Admin” 的用户可以访问 public IActionResult AdminOnly() { return Ok(new { message 欢迎管理员 }); } [HttpGet(user-or-admin)] [Authorize(Roles User,Admin)] // 角色为 “User” 或 “Admin” 的用户可以访问 public IActionResult UserOrAdmin() { return Ok(new { message 欢迎用户或管理员 }); }测试用admin用户登录获得的 Token可以访问admin-only和user-or-admin。用user1用户登录获得的 Token只能访问user-or-admin访问admin-only会返回403 Forbidden。5.3 使用策略Policy进行更灵活的授权角色授权简单直接但不够灵活。策略Policy允许你定义更复杂的授权规则比如要求同时满足多个声明、自定义验证逻辑等。5.3.1 定义策略在Program.cs的AddAuthorization部分配置策略。builder.Services.AddAuthorization(options { // 策略1要求用户拥有 “Read” 权限声明 options.AddPolicy(RequireReadPermission, policy policy.RequireClaim(Permission, Read)); // 策略2要求用户同时拥有 “Write” 和 “Delete” 权限声明 options.AddPolicy(RequireWriteDeletePermission, policy { policy.RequireClaim(Permission, Write); policy.RequireClaim(Permission, Delete); }); // 策略3自定义策略通过函数判断 options.AddPolicy(Over18Only, policy policy.RequireAssertion(context { // 这里可以编写任意复杂的逻辑例如从数据库或声明中检查年龄 // 假设我们有一个 “DateOfBirth” 的声明 var dobClaim context.User.FindFirst(c c.Type DateOfBirth); if (dobClaim null) return false; var dob DateTime.Parse(dobClaim.Value); return DateTime.Today.Year - dob.Year 18; })); });5.3.2 在控制器中使用策略通过[Authorize(Policy “策略名”)]来应用策略。[HttpGet(needs-read)] [Authorize(Policy RequireReadPermission)] public IActionResult NeedsReadPermission() { return Ok(new { message 你拥有 Read 权限。 }); } [HttpGet(needs-write-delete)] [Authorize(Policy RequireWriteDeletePermission)] public IActionResult NeedsWriteDeletePermission() { return Ok(new { message 你同时拥有 Write 和 Delete 权限。 }); }测试admin用户拥有Read,Write,Delete权限可以访问以上两个接口。user1用户只拥有Read权限只能访问needs-read接口访问needs-write-delete会返回403。6. 关键细节与常见问题排查配置跑通只是第一步。在实际开发中你会遇到各种边界情况和错误。下面是一些高频问题和排查思路。6.1 中间件顺序错误导致[Authorize]无效现象添加了[Authorize]的接口不传 Token 也能访问或者返回奇怪的错误。排查首先检查Program.cs中app.UseAuthentication()和app.UseAuthorization()的顺序和位置。确保它们出现在app.UseRouting()之后如果显式调用了的话并且在app.MapControllers()或app.MapRazorPages()等端点映射之前。正确顺序模板app.UseRouting(); app.UseAuthentication(); // 1. 认证 app.UseAuthorization(); // 2. 授权 app.MapControllers(); // 3. 映射端点6.2 Token 验证失败返回 401现象请求带了 Token但还是返回401 Unauthorized。排查步骤检查 Token 格式确认请求头是Authorization: Bearer token注意Bearer后面有一个空格。Token 字符串要完整没有多余换行或引号。检查 Token 有效期在JwtHelper中设置的expires参数可能已过期。生成一个新 Token 试试。检查签名密钥Key确保生成 Token 和验证 Token 使用的是完全相同的Jwt:Key。如果重启了服务或者配置源变了密钥不一致会导致验证失败。检查签发者Issuer和受众Audience同样生成和验证时的Issuer、Audience值必须匹配。检查appsettings.json和代码中的配置。查看日志在JwtBearerEvents.OnAuthenticationFailed事件中打印的日志会给出具体的失败原因如“令牌已过期”、“签名无效”等。6.3 基于角色的授权返回 403现象登录成功能访问普通[Authorize]接口但访问[Authorize(Roles “Admin”)]接口返回403 Forbidden。排查检查 Token 中的角色声明使用类似 jwt.io 的网站解码你的 Token查看role声明或ClaimTypes.Role对应的字段的值是否正确。确保它是字符串Admin而不是数组或其他格式。检查声明类型在JwtHelper中我们使用了ClaimTypes.Role其值通常是http://schemas.microsoft.com/ws/2008/06/identity/claims/role。JWT 标准字段是roles复数。AddJwtBearer中间件默认能映射一些标准声明但如果你用了自定义类型可能需要配置TokenValidationParameters.RoleClaimType。多个角色如果用户有多个角色声明值应该是一个字符串数组或者在单个声明中用逗号分隔取决于你的实现。[Authorize(Roles “Admin”)]会检查用户是否拥有任一指定的角色。6.4 Swagger UI 无法测试带认证的接口现象在 Swagger 页面上受保护的接口没有“锁”图标或者点击“Try it out”后无法自动添加 Authorization 头。配置需要在Program.cs中为 Swagger 添加安全定义。// 在 builder.Services.AddSwaggerGen() 之前确保引入了 Swashbuckle.AspNetCore builder.Services.AddSwaggerGen(c { c.SwaggerDoc(v1, new() { Title AuthDemoApi, Version v1 }); // 添加 JWT 认证支持到 Swagger c.AddSecurityDefinition(Bearer, new OpenApiSecurityScheme { Description JWT Authorization header using the Bearer scheme. Example: \Authorization: Bearer {token}\, Name Authorization, In ParameterLocation.Header, Type SecuritySchemeType.ApiKey, Scheme Bearer }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference new OpenApiReference { Type ReferenceType.SecurityScheme, Id Bearer } }, new string[] {} } }); });还需要在文件顶部添加using Microsoft.OpenApi.Models;。配置后Swagger UI 右上角会出现“Authorize”按钮输入Bearer 你的Token后所有请求都会自动带上认证头。6.5 在服务或中间件中获取当前用户信息场景你不在控制器里而是在一个自定义服务、过滤器Filter或中间件中需要获取当前登录用户的信息。方法通过IHttpContextAccessor服务来访问HttpContext。在Program.cs中注册服务builder.Services.AddHttpContextAccessor();在自定义服务中注入并使用public class MyService { private readonly IHttpContextAccessor _httpContextAccessor; public MyService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor httpContextAccessor; } public void DoSomething() { var user _httpContextAccessor.HttpContext?.User; var userName user?.Identity?.Name; var roles user?.Claims.Where(c c.Type ClaimTypes.Role).Select(c c.Value); // ... 使用用户信息 } }注意在非请求线程如后台任务、IHostedService中HttpContext为null这种方法不适用。7. 进阶刷新令牌、API 密钥与生产环境考量简单的 JWT 颁发和验证足以应对很多内部系统。但如果面向公网或需要更高安全性还需要考虑更多。7.1 实现刷新令牌Refresh Token机制JWT 通常有效期较短如15分钟以降低令牌泄露的风险。但用户不可能每15分钟登录一次。这时需要刷新令牌机制访问令牌Access Token短有效期用于访问 API。刷新令牌Refresh Token长有效期存储在服务端如数据库或分布式缓存仅用于获取新的访问令牌。基本流程登录接口同时返回access_token和refresh_token。客户端访问 API 时使用access_token。当access_token过期客户端用refresh_token调用一个专门的/api/auth/refresh端点。服务端验证refresh_token的有效性和所有权是否属于该用户、是否被撤销。验证通过后颁发新的access_token和可选的新的refresh_token。实现要点refresh_token必须是随机、不可预测的字符串如 GUID。将refresh_token及其关联的用户ID、过期时间存储在服务端。提供撤销refresh_token的接口如用户修改密码、登出时。7.2 使用 API 密钥ApiKey认证对于服务器到服务器的通信有时使用 API 密钥比 JWT 更简单。可以实现一个自定义的认证处理器。创建自定义认证方案public class ApiKeyAuthenticationHandler : AuthenticationHandlerAuthenticationSchemeOptions { private const string ApiKeyHeaderName X-API-Key; public ApiKeyAuthenticationHandler(IOptionsMonitorAuthenticationSchemeOptions options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } protected override async TaskAuthenticateResult HandleAuthenticateAsync() { if (!Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKeyHeaderValues)) { return AuthenticateResult.NoResult(); } var providedApiKey apiKeyHeaderValues.FirstOrDefault(); if (string.IsNullOrEmpty(providedApiKey)) { return AuthenticateResult.NoResult(); } // 这里应该从安全存储如数据库、配置中验证 API 密钥 var validApiKeys new Liststring { YourSecretApiKey123 }; // 示例 if (!validApiKeys.Contains(providedApiKey)) { return AuthenticateResult.Fail(无效的 API 密钥。); } var claims new[] { new Claim(ClaimTypes.Name, ApiClient) }; var identity new ClaimsIdentity(claims, Scheme.Name); var principal new ClaimsPrincipal(identity); var ticket new AuthenticationTicket(principal, Scheme.Name); return AuthenticateResult.Success(ticket); } }注册该方案builder.Services.AddAuthentication() .AddJwtBearer() .AddSchemeAuthenticationSchemeOptions, ApiKeyAuthenticationHandler(ApiKey, options { });在控制器中使用[Authorize(AuthenticationSchemes ApiKey)] [HttpGet(api-key-only)] public IActionResult ApiKeyOnly() { ... }7.3 生产环境安全配置密钥管理Jwt:Key绝不能硬编码或提交到代码仓库。使用dotnet user-secrets开发、环境变量、Azure Key Vault、AWS Secrets Manager 等。HTTPS 强制生产环境务必启用 HTTPS并在Program.cs中使用app.UseHsts()和app.UseHttpsRedirection()。令牌安全设置合理的短有效期如15-60分钟。使用强签名算法如HS256或RS256。考虑将令牌加入黑名单用于立即吊销但这会牺牲无状态特性需要后端存储。CORS 配置如果前端与 API 分属不同域名需精确配置 CORS不要使用AllowAnyOrigin()。日志与监控记录认证失败、授权失败的事件便于审计和安全分析。依赖更新定期更新Microsoft.AspNetCore.Authentication.JwtBearer等安全相关 NuGet 包。8. 总结从能跑到跑稳的关键检查点把身份验证和授权跑起来不难但要让它在生产环境稳定、安全地运行你需要养成一套检查习惯。部署前检查清单[ ] 中间件顺序UseAuthentication在UseAuthorization之前两者都在端点映射之前。[ ] JWT 参数Issuer,Audience,Key在生成和验证端完全一致且密钥足够强。[ ] 令牌有效期根据业务场景设置合理的expires时间。[ ] 敏感配置所有密钥、连接字符串都已移出代码使用安全配置源。[ ] 角色/声明映射确认 Token 中的声明类型和值与授权策略中的要求匹配。[ ] 错误处理认证失败返回401授权失败返回403并有清晰的日志。[ ] 前端集成确认前端能正确在请求头中携带Authorization: Bearer token并处理401响应如跳转登录。日常排查路径当遇到权限问题时按这个顺序看看请求有没有Authorization头格式对不对Token 过期没看配置服务端Jwt:Key等配置有没有被意外覆盖或更改看声明解码 Token看里面的role,permission等声明是不是你期望的值看策略控制器上标注的[Authorize]、Roles、Policy是否与用户声明匹配看日志JwtBearerEvents或应用日志里有没有认证/授权过程的错误信息最后对于大多数内部管理系统和中小型 API 项目本文基于 JWT 的方案已经足够。如果项目复杂度增加涉及多租户、动态权限、OAuth 2.0/OpenID Connect 第三方登录可以考虑转向更专业的身份服务如 IdentityServer、Azure AD B2C、Auth0 等但它们的底层原理——认证建立身份授权判断权限——依然是相通的。先把这套基础流程在自己的环境里吃透再去看那些大型框架会清晰得多。
返回列表