英文:
.Net 7 Swagger API JWT auth: HTTP 401 (Unauthorized) after accessing endpoint with a valid token
问题
我已经尝试过
https://stackoverflow.com/questions/64372015/getting-401-unauthorized-with-a-valid-jwt-token
但徒劳无功,因为这不是我的情况。
我刚刚在我的dotnet 7 API中实现了jwt令牌认证,令牌成功生成,但是当尝试访问任何端点时,我始终收到401未经授权的HTTP错误。
Program.cs
var builder = WebApplication.CreateBuilder(args);
// 将服务添加到容器中。
builder.Services.AddControllers();
// 了解有关配置Swagger/OpenAPI的更多信息,请访问https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
#region swagger
//builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo API", Version = "v1" });
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "请输入有效令牌",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "Bearer"
});
option.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type=ReferenceType.SecurityScheme,
Id="Bearer"
}
},
new string[]{}
}
});
});
#endregion
#region jwt数据
var validIssuer = builder.Configuration["JWT:ValidIssuer"];
var validAudience = builder.Configuration["JWT:ValidAudience"];
var issuerSigningKey = builder.Configuration["JWT:IssuerSigningKey"];
#endregion
if (validIssuer != null && validAudience != null && issuerSigningKey != null)
{
#region jwt
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ClockSkew = TimeSpan.Zero,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = validIssuer,
ValidAudience = validAudience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(issuerSigningKey)
),
};
});
#endregion
#region DbContext
//builder.Services.AddDbContext<SurveysDbContext>(options =>
//{
// options.UseSqlServer(builder.Configuration.GetConnectionString("SurveyConnection"));
//});
//builder.Services.AddDbContext<UsersDbContext>(options =>
//{
// options.UseSqlServer(builder.Configuration.GetConnectionString("SurveyConnection"));
//});
builder.Services.AddDbContext<UsersDbContext>();
#endregion
#region neo4j数据
var neo4jServer = builder.Configuration["Neo4J:server"];
var neo4jUser = builder.Configuration["Neo4J:user"];
var neo4jPwd = builder.Configuration["Neo4J:pwd"];
#endregion
if (neo4jServer != null && neo4jUser != null && neo4jPwd != null)
{
#region neo4j
var client = new BoltGraphClient(new Uri(neo4jServer), neo4jUser, neo4jPwd);
client.ConnectAsync();
#endregion
#region services
#region domain
builder.Services.AddSingleton<SurveysDomain>();
builder.Services.AddSingleton<BlocksDomain>();
builder.Services.AddSingleton<QuestionsDomain>();
builder.Services.AddSingleton<OptionsDomain>();
builder.Services.AddSingleton<AnswersDomain>();
#endregion
#region repository
//builder.Services.AddTransient<IQuestionsRepository, QuestionsRepository>();
builder.Services.AddSingleton<SurveysRepository>();
builder.Services.AddSingleton<BlocksRepository>();
builder.Services.AddSingleton<QuestionsRepository>();
builder.Services.AddSingleton<OptionsRepository>();
builder.Services.AddSingleton<AnswersRepository>();
#endregion
builder.Services.AddSingleton<IGraphClient>(client);
builder.Services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
builder.Services.AddScoped<TokenService, TokenService>();
#endregion
#region users
builder.Services
.AddIdentityCore<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = true;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
}).AddRoles<IdentityRole>().AddEntityFrameworkStores<UsersDbContext>();
#endregion
var app = builder.Build();
// 配置HTTP请求管道。
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
// }
#region swagger默认页面在发布模式下
if (!app.Environment.IsDevelopment())
{
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Surveys Web API");
options.RoutePrefix = string.Empty;
});
app.UseHsts();
}
#endregion
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseAuthentication();
app.MapControllers();
app.Run();
}
else
{
throw new Exception("Neo4J未配置!");
}
}
else
{
throw new Exception("JWT未配置!");
}
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SurveyConnection": "xxx"
},
"Neo4J": {
"server": "bolt://localhost:",
"user": "
英文:
I've already tried
https://stackoverflow.com/questions/64372015/getting-401-unauthorized-with-a-valid-jwt-token
to no avail, because it's not my case.
I've just implemented jwt token auth in my dotnet 7 api and the token is generated successfully, but tehn when trying to access any endpoint I always receive a 401 unauthorized http error.
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
#region swagger
//builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo API", Version = "v1" });
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please enter a valid token",
Name = "Authorization",
Type = SecuritySchemeType.Http,
BearerFormat = "JWT",
Scheme = "Bearer"
});
option.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type=ReferenceType.SecurityScheme,
Id="Bearer"
}
},
new string[]{}
}
});
});
#endregion
#region jwt data
var validIssuer = builder.Configuration["JWT:ValidIssuer"];
var validAudience = builder.Configuration["JWT:ValidAudience"];
var issuerSigningKey = builder.Configuration["JWT:IssuerSigningKey"];
#endregion
if (validIssuer != null && validAudience != null && issuerSigningKey != null)
{
#region jwt
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ClockSkew = TimeSpan.Zero,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = validIssuer,
ValidAudience = validAudience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(issuerSigningKey)
),
};
});
#endregion
#region DbContext
//builder.Services.AddDbContext<SurveysDbContext>(options =>
//{
// options.UseSqlServer(builder.Configuration.GetConnectionString("SurveyConnection"));
//});
//builder.Services.AddDbContext<UsersDbContext>(options =>
//{
// options.UseSqlServer(builder.Configuration.GetConnectionString("SurveyConnection"));
//});
builder.Services.AddDbContext<UsersDbContext>();
#endregion
#region neo4j data
var neo4jServer = builder.Configuration["Neo4J:server"];
var neo4jUser = builder.Configuration["Neo4J:user"];
var neo4jPwd = builder.Configuration["Neo4J:pwd"];
#endregion
if (neo4jServer != null && neo4jUser != null && neo4jPwd != null)
{
#region neo4j
var client = new BoltGraphClient(new Uri(neo4jServer), neo4jUser, neo4jPwd);
client.ConnectAsync();
#endregion
#region services
#region domain
builder.Services.AddSingleton<SurveysDomain>();
builder.Services.AddSingleton<BlocksDomain>();
builder.Services.AddSingleton<QuestionsDomain>();
builder.Services.AddSingleton<OptionsDomain>();
builder.Services.AddSingleton<AnswersDomain>();
#endregion
#region repository
//builder.Services.AddTransient<IQuestionsRepository, QuestionsRepository>();
builder.Services.AddSingleton<SurveysRepository>();
builder.Services.AddSingleton<BlocksRepository>();
builder.Services.AddSingleton<QuestionsRepository>();
builder.Services.AddSingleton<OptionsRepository>();
builder.Services.AddSingleton<AnswersRepository>();
#endregion
builder.Services.AddSingleton<IGraphClient>(client);
builder.Services.Configure<RouteOptions>(options => options.LowercaseUrls = true);
builder.Services.AddScoped<TokenService, TokenService>();
#endregion
#region users
builder.Services
.AddIdentityCore<IdentityUser>(options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.User.RequireUniqueEmail = true;
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
}).AddRoles<IdentityRole>().AddEntityFrameworkStores<UsersDbContext>();
#endregion
var app = builder.Build();
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
//{
app.UseSwagger();
app.UseSwaggerUI();
//}
#region swagger default page in release mode
if (!app.Environment.IsDevelopment())
{
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Surveys Web API");
options.RoutePrefix = string.Empty;
});
app.UseHsts();
}
#endregion
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseAuthentication();
app.MapControllers();
app.Run();
}
else
{
throw new Exception("Neo4J is not configured!");
}
}
else
{
throw new Exception("JWT is not configured!");
}
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SurveyConnection": "xxx"
},
"Neo4J": {
"server": "bolt://localhost:",
"user": "yyy",
"pwd": "yyy"
},
"JWT": {
"ValidIssuer": "aaa",
"ValidAudience": "aaa",
"IssuerSigningKey": "bbb"
}
}
TokenService:
public class TokenService
{
private const int ExpirationMinutes = 30;
public string? CreateToken(IdentityUser user)
{
var signingCredentials = CreateSigningCredentials();
if (signingCredentials == null) return null;
var claims = CreateClaims(user);
if (claims==null) return null;
var expiration = DateTime.UtcNow.AddMinutes(ExpirationMinutes);
var token = CreateJwtToken(
claims,
signingCredentials,
expiration
);
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}
private JwtSecurityToken CreateJwtToken(List<Claim> claims, SigningCredentials credentials,
DateTime expiration)
{
var validIssuer = Util.Util.getAppSettingsValue("JWT:ValidIssuer");
var validAudience = Util.Util.getAppSettingsValue("JWT:ValidAudience");
return new(
validIssuer,
validAudience,
claims,
expires: expiration,
signingCredentials: credentials
);
}
private List<Claim>? CreateClaims(IdentityUser user)
{
if (user.UserName == null || user.Email == null) return null;
try
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, "TokenForTheApiWithAuth"),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Email, user.Email)
};
return claims;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
private SigningCredentials? CreateSigningCredentials()
{
var secret = Util.Util.getAppSettingsValue("JWT:IssuerSigningKey");
if (secret == null) return null;
return new SigningCredentials(
new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(secret)
),
SecurityAlgorithms.HmacSha256
);
}
}
AuthRequest:
public class AuthRequest
{
public string Email { get; set; } = null!;
public string Password { get; set; } = null!;
}
AuthResponse:
public class AuthResponse
{
public string Username { get; set; } = null!;
public string Email { get; set; } = null!;
public string Token { get; set; } = null!;
}
AuthController:
[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly UserManager<IdentityUser> _userManager;
private readonly UsersDbContext _context;
private readonly TokenService _tokenService;
public AuthController(UserManager<IdentityUser> userManager, UsersDbContext context, TokenService tokenService)
{
_userManager = userManager;
_context = context;
_tokenService = tokenService;
}
[HttpPost]
[Route("register")]
public async Task<IActionResult> Register(User user)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var result = await _userManager.CreateAsync(
new IdentityUser { UserName = user.Username, Email = user.Email },
user.Passwd
);
if (result.Succeeded)
{
user.Passwd = "";
return CreatedAtAction(nameof(Register), new { email = user.Email }, user);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(error.Code, error.Description);
}
return BadRequest(ModelState);
}
[HttpPost]
[Route("login")]
public async Task<ActionResult<AuthResponse>> Authenticate([FromBody] AuthRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var managedUser = await _userManager.FindByEmailAsync(request.Email);
if (managedUser == null)
{
return BadRequest("Bad credentials");
}
var isPasswordValid = await _userManager.CheckPasswordAsync(managedUser, request.Password);
if (!isPasswordValid)
{
return BadRequest("Bad credentials");
}
var userInDb = _context.Users.FirstOrDefault(u => u.Email == request.Email);
if (userInDb is null)
return Unauthorized();
var accessToken = _tokenService.CreateToken(userInDb);
await _context.SaveChangesAsync();
return Ok(new AuthResponse
{
Username = userInDb.UserName,
Email = userInDb.Email,
Token = accessToken,
});
}
}
QuestionsController:
[ApiController]
[Route("surveys")]
public class QuestionsController : ControllerBase
{
private readonly QuestionsDomain _questionsDomain;
public QuestionsController(QuestionsDomain questionsDomain)
{
_questionsDomain = questionsDomain;
}
/// <summary>
/// Get all questions in a block
/// </summary>
/// <param name="surveyId"></param>
/// <returns>json</returns>
[HttpGet("{surveyId}/blocks/{blockId}/questions"), Authorize]
public async Task<IActionResult> GetQuestionsAsync(int surveyId)
{
//var questions = await _questionDomain.GetQuestionsAsync(surveyId);
//return Ok(questions);
var question1 = new Question
{
Id = 1,
IdSurvey = 1,
Text = "¿Te gusta el deporte?"
};
var question2 = new Question
{
Id = 2,
IdSurvey = 1,
Text = "¿Te gusta el fútbol?"
};
var questions = new List<Question> { question1, question2, question3, question4 };
return Ok(questions);
}
...
}
答案1
得分: 2
结束时,我没有在端点的[Authorize]装饰器中指定AuthenticationScheme。不知道是强制的,但添加后它开始正确验证令牌。
英文:
It ended up being that I was not specifying the AuthenticationScheme in the [Authorize] decorator in endpoints. Didn't know is mandatory, but after adding this it started validating the token correctly.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论