Возникла проблема с аутентификацией, которую решили здесь. Сейчас возникла проблема с авторизацией, если я ставлю параметр Roles
, то получаю ошибку.
Program.cs
:
namespace MySteamDBMetacritic
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ApplicationDbContext>((options) =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<User, IdentityRole<int>>(options =>
{
options.User.RequireUniqueEmail = true;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireDigit = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.IncludeErrorDetails = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
RoleClaimType = ClaimsIdentity.DefaultRoleClaimType,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
var result = JsonSerializer.Serialize(new { message = "Authentication failed" });
return context.Response.WriteAsync(result);
}
};
options.TokenHandlers.Add(new BearerTokenHandler());
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Admin"));
options.AddPolicy("UserPolicy", policy => policy.RequireRole("User"));
});
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 1024 * 1024 * 20;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var userManager = services.GetRequiredService<UserManager<User>>();
var rolesManager = services.GetRequiredService<RoleManager<IdentityRole<int>>>();
var context = services.GetRequiredService<ApplicationDbContext>();
await RoleInitializer.InitializeAsync(context, userManager, rolesManager);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the database.");
}
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Game}/{action=Index}/{id?}");
app.Run();
}
}
}
Аккаунты создаются в AccountController.cs
, админка создается в RoleInitializer.cs
. Этот контроллер является примером возникновения ошибки UsersController.cs
:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MySteamDBMetacritic.Models;
using MySteamDBMetacritic.ViewModels;
namespace MySteamDBMetacritic.Controllers
{
[Authorize(Roles = "Admin")]
public class UsersController : Controller
{
UserManager<User> _userManager;
public UsersController(UserManager<User> userManager)
{
_userManager = userManager;
}
public IActionResult Index() => View(_userManager.Users.ToList());
public IActionResult Create() => View();
[HttpPost]
public async Task<IActionResult> Create(CreateUserViewModel model)
{
if (ModelState.IsValid)
{
User user = new User { Email = model.Email, UserName = model.UserName };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
}
return View(model);
}
public async Task<IActionResult> Edit(int id)
{
User user = await _userManager.FindByIdAsync(id.ToString());
if (user == null)
{
return NotFound();
}
EditUserViewModel model = new EditUserViewModel { Id = user.Id, Email = user.Email, UserName = user.UserName };
return View(model);
}
[HttpPost]
public async Task<IActionResult> Edit(EditUserViewModel model)
{
if (ModelState.IsValid)
{
User user = await _userManager.FindByIdAsync(model.Id.ToString());
if (user != null)
{
user.Email = model.Email;
user.UserName = model.UserName;
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction("Index");
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
}
}
return View(model);
}
[HttpPost]
public async Task<ActionResult> Delete(int id)
{
User user = await _userManager.FindByIdAsync(id.ToString());
if (user != null)
{
IdentityResult result = await _userManager.DeleteAsync(user);
}
return RedirectToAction("Index");
}
}
}
AccountController.cs
public class AccountController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ApplicationDbContext _context;
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signInManager;
private readonly IConfiguration _configuration;
public AccountController(ILogger<HomeController> logger, ApplicationDbContext context, UserManager<User> userManager, SignInManager<User> signInManager, IConfiguration configuration)
{
_context = context;
_logger = logger;
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register([FromBody]RegisterViewModel model)
{
if (ModelState.IsValid)
{
User user = new User { Email = model.Email, UserName = model.UserName };
if (_userManager.Users
.FirstOrDefault(x => x.Email == user.Email) != default(User))
{
return View(model);
}
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_context.SaveChanges();
await _signInManager.SignInAsync(user, false);
return Json(new { token = Token(model.UserName, model.Password), returnUrl = Url.Action("Index", "Game") });
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
}
return View(model);
}
[HttpGet]
public IActionResult Login(string returnUrl = null)
{
return View(new LoginViewModel { ReturnUrl = returnUrl });
}
[HttpPost]
public async Task<IActionResult> Login([FromBody]LoginViewModel model)
{
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, false);
if (result.Succeeded)
{
if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
{
return Json(new { token = ((JsonResult)Token(model.UserName, model.Password)).Value, returnUrl = model.ReturnUrl});
}
else
{
return Json(new { token = ((JsonResult)Token(model.UserName, model.Password)).Value, returnUrl = Url.Action("Index", "Game") });
}
}
else
{
ModelState.AddModelError("", "Incorrect username or password");
}
}
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Index", "Game");
}
public async Task<IActionResult> ChangePassword(int id)
{
User user = await _userManager.FindByIdAsync(id.ToString());
if (user == null)
{
return NotFound();
}
ChangePasswordViewModel model = new ChangePasswordViewModel { Id = user.Id, Email = user.Email };
return View(model);
}
[HttpPost]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (ModelState.IsValid)
{
User user = await _userManager.FindByIdAsync(model.Id.ToString());
if (user != null)
{
var _passwordValidator = HttpContext.RequestServices.GetService(typeof(IPasswordValidator<User>)) as IPasswordValidator<User>;
var _passwordHasher = HttpContext.RequestServices.GetService(typeof(IPasswordHasher<User>)) as IPasswordHasher<User>;
IdentityResult result = await _passwordValidator.ValidateAsync(_userManager, user, model.NewPassword);
if (result.Succeeded)
{
user.PasswordHash = _passwordHasher.HashPassword(user, model.NewPassword);
await _userManager.UpdateAsync(user);
return RedirectToAction("Index");
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
}
else
{
ModelState.AddModelError(string.Empty, "User is missing");
}
}
return View(model);
}
public IActionResult Token(string username, string password)
{
var identity = GetIdentity(username, password).GetAwaiter().GetResult();
if (identity == null)
{
return BadRequest(new { errorText = "Invalid username or password." });
}
var now = DateTime.Now;
var jwt = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
notBefore: now,
claims: identity.Claims,
expires: now.Add(TimeSpan.FromMinutes(double.Parse(_configuration["Jwt:ExpiresMinutes"]))),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])), SecurityAlgorithms.HmacSha256));
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
access_token = encodedJwt,
username = identity.Name
};
return Json(response);
}
private async Task<ClaimsIdentity> GetIdentity(string username, string password)
{
User user = await _userManager.FindByNameAsync(username);
if (user != null)
{
var claims = new List<Claim>
{
new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName),
new Claim(ClaimsIdentity.DefaultRoleClaimType, string.Join(',',_userManager.GetRolesAsync(user).Result))
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, "Token",
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
return claimsIdentity;
}
return null;
}
}
Я проверил, роль назначена.
RoleInitializer
, выполнить каждую стартовую программу, GetRoleAsync
вернуть «Администратор»
using Microsoft.AspNetCore.Identity;
using MySteamDBMetacritic.Db;
using MySteamDBMetacritic.Models;
namespace MySteamDBMetacritic.Classes
{
public class RoleInitializer
{
public static async Task InitializeAsync(ApplicationDbContext _context, UserManager<User> userManager, RoleManager<IdentityRole<int>> roleManager)
{
string adminEmail = "[email protected]";
string password = "aA12345!";
if (await roleManager.FindByNameAsync("Admin") == null)
{
await roleManager.CreateAsync(new IdentityRole<int>("Admin"));
}
if (await roleManager.FindByNameAsync("User") == null)
{
await roleManager.CreateAsync(new IdentityRole<int>("User"));
}
if (await userManager.FindByNameAsync("Admin") == null)
{
User admin = new User { Email = adminEmail, UserName = "Admin" };
IdentityResult result = await userManager.CreateAsync(admin, password);
if (result.Succeeded)
{
await userManager.AddToRoleAsync(admin, "Admin");
}
}
else
{
User? admin = await userManager.FindByNameAsync("Admin");
var result = await userManager.GetRolesAsync(admin);
if (!result.Contains("Admin"))
{
await userManager.AddToRoleAsync(admin, "Admin");
}
}
await _context.SaveChangesAsync();
}
}
}
Я провел небольшое исследование и не нашел ничего, что могло бы мне помочь, я просто добавил в
TokenValidationParameters RoleClaimType = ClaimsIdentity.DefaultRoleClaimType
и это мне не помогло.
Когда я создаю токен JWT, все в порядке:
Но когда я проверяю HttpContext
, претензии «роли» нет:
не смотрите на методы в UserController (не завершено), я это показал, чтобы вы видели в каком контроллере проблема, в RoleInitializer создается пользователь Admin, в нем также создается запись с ним и роль база данных, также добавленная в таблицу UserRole. Данные хранятся в токене JWT, роль есть, токен отправляю в ответ, он правильный, когда получаю токен от JWT тоже правильно, но из-за какой-то проблемы ASP не берет на себя роль поле от него.
Проблема JWT ASP.NET Core MVC с авторизацией при проверке роли. Но когда я проверяю HttpContext, утверждения «роли» нет:
После долгих исследований и испытаний я обнаружил проблему в вашем проекте.
Я просмотрел ваш файл csproj и заметил, что вы используете <PackageReference Include = "Microsoft.IdentityModel.Tokens" Version = "7.6.3" />
и <PackageReference Include = "Microsoft.AspNetCore.Authentication.JwtBearer" Version = "8.0.7" />
В совокупности это является основной причиной неожиданного поведения при доступе к утверждениям токенов, которое создает конфликт и проблемы совместимости между пакетом Microsoft.IdentityModel.Tokens
и другими пакетами, связанными с JWT и обработкой удостоверений внутри проекта.
Чтобы решить проблему, просто удалите <PackageReference Include = "Microsoft.IdentityModel.Tokens" Version = "7.6.3" />
из файла csproj
.
Ваш csproj должен выглядеть следующим образом:
<Project Sdk = "Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include = "Microsoft.AspNetCore.Authentication.JwtBearer" Version = "8.0.7" />
<PackageReference Include = "Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version = "8.0.7" />
<PackageReference Include = "Microsoft.EntityFrameworkCore" Version = "8.0.7" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.SqlServer" Version = "8.0.6" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.Tools" Version = "8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include = "Swashbuckle.AspNetCore.Swagger" Version = "6.6.2" />
<PackageReference Include = "Swashbuckle.AspNetCore.SwaggerGen" Version = "6.6.2" />
<PackageReference Include = "Swashbuckle.AspNetCore.SwaggerUI" Version = "6.6.2" />
</ItemGroup>
<ItemGroup>
<Folder Include = "Migrations\" />
</ItemGroup>
</Project>
Выход:
Примечание. Пакет Microsoft.IdentityModel.Tokens
может вам не понадобиться напрямую, поскольку пакет Microsoft.AspNetCore.Authentication.JwtBearer
уже включает его в качестве зависимости. Весь существующий код кажется правильным и никаких дополнительных изменений не требуется. Я протестировал весь ваш сценарий, и все работает так, как ожидалось.
У меня возникла проблема с аутентификацией токена из-за несоответствия формата (IDX14100), но jwt.io сказал, что все правильно. Я решил эту проблему, написав BearerTokenHandler
, он получает на вход правильный токен, но после вызова JwtSecurityTokenHandler.ValidateToken()
не передает утверждение роли из jwt в validatedToken
https://i.sstatic.net/YuFnSkx7.png . Описание этой проблемы в другом моем вопросе ссылка. насколько я понимаю решение было неправильным.
Можете ли вы загрузить мой проект с github и попытаться решить проблему именно для моего проекта?
Я просмотрел ваш проект, и мне кажется, что проблема странная, у токена есть претензии, но когда дело доходит до индекса контроллера Users
, я проверил, что претензий там нет.
Какая может быть причина в данном случае? Я уже прочитал много статей и не нашел решения, которое мне поможет?
Возможно, что-то не так с версиями библиотеки, и мне посчастливилось скачать глючную версию.
Наконец, я смог разобраться в проблеме и обновил для вас ответ. Попробуйте, надеюсь, это решит вашу проблему.
Спасибо большое, все работает так, как мне нужно, но откуда я мог знать, что Microsoft.AspNetCore.Authentication.JwtBearer
уже включает Microsoft.IdentityModel.Tokens
, если nuget (или я чего-то не понимаю) в Microsoft.AspNetCore.Authentication.JwtBearer
зависимостях не указан Microsoft.IdentityModel.Tokens
? Прошу не допускать такой глупой ошибки в будущем.
На самом деле, это сложно объяснить, потому что нет конкретного способа узнать это, но хотя вам понадобится какой-то конкретный пакет nuget, вы можете проверить его предварительные условия. Также можно увидеть примечание к выпуску. Но да, мне пришлось проверить много вещей, чтобы сделать такой вывод. Однако я не уверен, почему вы добавили этот пакет, поскольку он не требуется для вашего сценария.
Это все потому, что я посмотрел реализацию JWT Token
и статьи, которые я нашел, использовали это, возможно, это было для .net 6 или это была не обновленная информация. Еще хотелось бы попросить совета, хочу сделать так, чтобы после входа пользователь перенаправлялся на какую-то ссылку, но мой метод входа отправляет JWT Token
и ссылку на которую будет произведено перенаправление, в результате JS script
извлекает и вводит ответ на страницу входа в систему, я не знаю, смогу ли я каким-то образом отправить токен JWT и выполнить перенаправление с помощью RedirectToAction()
?
Поскольку эта проблема решена, лучше не расширять этот разговор, но, поскольку вы использовали js на стороне клиента, вам может потребоваться использовать ` window.location.href = '/Users/AccessDenied'` для перенаправления. Однако, если у вас есть какие-либо другие проблемы, вы можете опубликовать новый вопрос, чтобы я мог проверить, смогу ли я вам помочь.
В вашем коде после метода
Create(CreateUserViewModel model)
я не видел, чтобы вы назначали роль вновь созданному пользователю.