.NET 7 Web API JWT授权异常:无法通过Token访问接口
.NET 7 Web API JWT认证故障:Token生成后无法访问接口(错误码从405转为401)
开发.NET 7 Web API时,登录接口可正常生成JWT Token,但使用该Token访问受保护接口时失败。参考相关方案调整后,错误码从405(方法不允许)变为401(未授权)。
相关代码片段
Program.cs
var builder = WebApplication.CreateBuilder(args); builder.Services.AddCors(options => { options.AddPolicy("CORSPolicy", builder => { builder .AllowAnyMethod() .AllowAnyHeader() .WithOrigins("http://localhost:3000", "https://localhost:3000", "https://appname.asurestaticapps.net"); }); }); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("auth0", new OpenApiSecurityScheme { In = ParameterLocation.Header, Name = "Authorization", Type = SecuritySchemeType.ApiKey }); options.OperationFilter<SecurityRequirementsOperationFilter>(); }); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidAudience = builder.Configuration["Jwt:Audience"], ValidIssuer = builder.Configuration["Jwt:Issuer"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]) ) }; }); builder.Services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser().Build()); }); builder.Services.AddDbContext<SdeResearchDbContext>(); builder.Services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<SdeResearchDbContext>() .AddDefaultTokenProviders(); builder.Services.AddScoped<JwtService>(); builder.Services.Configure<IdentityOptions>(options => { // 密码设置 options.Password.RequireDigit= true; options.Password.RequireLowercase= true; options.Password.RequireNonAlphanumeric= true; options.Password.RequireUppercase= true; options.Password.RequiredLength= 8; options.Password.RequireLowercase= true; options.Password.RequiredUniqueChars = 6; // 用户设置 options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+!"; options.User.RequireUniqueEmail= true; }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseCors("CORSPolicy"); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "Topic", pattern: "topic/*{action}" ); app.Run();
appsettings.json
{ "Jwt": { "Key": "this is the secret key for the jwt, it must be kept secure", "Issuer": "https://localhost:7174", "Audience": "https://localhost:7174", "Subject": "JWT for this site" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" }
JwtService.cs
namespace SdeResearch.Api.Services { public class JwtService { private const int EXPIRATION_MINUTES = 5; private readonly IConfiguration _configuration; public JwtService(IConfiguration configuration) { _configuration = configuration; } public AuthenticationResponse CreateToken(ApplicationUser user) { var expiration = DateTime.UtcNow.AddMinutes(EXPIRATION_MINUTES); var token = CreateJwtToken( CreateClaims(user), CreateSigningCredentials(), expiration ); var tokenHandler = new JwtSecurityTokenHandler(); return new AuthenticationResponse { Token = tokenHandler.WriteToken(token), Expiration = expiration }; } private JwtSecurityToken CreateJwtToken(Claim[] claims, SigningCredentials credentials, DateTime expiration) => new JwtSecurityToken( _configuration["Jwt:Issuer"], _configuration["Jwt:Audience"], claims, expires: expiration, signingCredentials: credentials ); private Claim[] CreateClaims(ApplicationUser user) => new[] { new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()), new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Email, user.Email), }; private SigningCredentials CreateSigningCredentials() => new SigningCredentials( new SymmetricSecurityKey( Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]) ), SecurityAlgorithms.HmacSha256 ); } }
AuthController 登录方法
[HttpPost] [Route("/account/login")] public async Task<ActionResult<AuthenticationResponse>> Login(LoginDTO userLogin) { ApplicationUser user = await _userManager.FindByEmailAsync(userLogin.Email); bool result = await _userManager.CheckPasswordAsync(user, userLogin.Password); if (!result) return BadRequest(result); var token = _jwtService.CreateToken(user); return Ok(token); }
TopicController.cs
namespace SdeResearch.Api.Controllers { [ApiController] [Route("[controller]")] [Authorize("Bearer")] public class TopicController : ControllerBase { private readonly SdeResearchDbContext _db; public TopicController(SdeResearchDbContext db) { _db = db; } [HttpGet] [Route("/topic/get-all-topics")] public async Task<List<Topic>> GetTopicsAsync() { return await _db.Topics.ToListAsync(); } } }
Postman测试截图

问题排查与修复方案
1. 认证参数配置矛盾
在Program.cs的JWT验证参数中,同时设置了ValidateIssuer = false、ValidateAudience = false和具体的ValidIssuer、ValidAudience,导致验证逻辑冲突。生成Token时已经指定了Issuer和Audience,验证阶段应该开启对应校验,确保一致性:
修改Program.cs中的认证配置:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidAudience = builder.Configuration["Jwt:Audience"], ValidIssuer = builder.Configuration["Jwt:Issuer"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]) ) }; });
2. 路由重复配置导致405错误
TopicController同时使用控制器级路由[Route("[controller]")]和方法级绝对路由[Route("/topic/get-all-topics")],加上Program.cs中额外配置的MapControllerRoute,导致路由匹配混乱,引发405错误。
- 删除
Program.cs中的以下路由配置:
// app.MapControllerRoute( // name: "Topic", // pattern: "topic/*{action}" // );
- 修改
TopicController的路由定义,避免绝对路由和前缀重复:
namespace SdeResearch.Api.Controllers { [ApiController] [Route("topic")] [Authorize("Bearer")] public class TopicController : ControllerBase { private readonly SdeResearchDbContext _db; public TopicController(SdeResearchDbContext db) { _db = db; } [HttpGet("get-all-topics")] public async Task<List<Topic>> GetTopicsAsync() { return await _db.Topics.ToListAsync(); } } }
3. 请求头格式验证
确保请求时Authorization头的格式为Bearer {Token内容},缺少Bearer 前缀会直接导致401未授权。
4. Token过期检查
当前Token有效期仅5分钟,测试时需确保Token未过期,可临时延长有效期便于调试。
内容的提问来源于stack exchange,提问作者Morgan Bradford
相关产品推荐
相关产品推荐

