Проблема JWT ASP.NET Core MVC с авторизацией при проверке роли

Возникла проблема с аутентификацией, которую решили здесь. Сейчас возникла проблема с авторизацией, если я ставлю параметр 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, претензии «роли» нет:

В вашем коде после метода Create(CreateUserViewModel model) я не видел, чтобы вы назначали роль вновь созданному пользователю.

Md Farid Uddin Kiron 23.07.2024 11:11

не смотрите на методы в UserController (не завершено), я это показал, чтобы вы видели в каком контроллере проблема, в RoleInitializer создается пользователь Admin, в нем также создается запись с ним и роль база данных, также добавленная в таблицу UserRole. Данные хранятся в токене JWT, роль есть, токен отправляю в ответ, он правильный, когда получаю токен от JWT тоже правильно, но из-за какой-то проблемы ASP не берет на себя роль поле от него.

CriticalError 23.07.2024 11:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
133
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема 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 в validatedTokenhttps://i.sstatic.net/YuFnSkx7.png . Описание этой проблемы в другом моем вопросе ссылка. насколько я понимаю решение было неправильным.

CriticalError 24.07.2024 15:07

Можете ли вы загрузить мой проект с github и попытаться решить проблему именно для моего проекта?

CriticalError 24.07.2024 15:17

Я просмотрел ваш проект, и мне кажется, что проблема странная, у токена есть претензии, но когда дело доходит до индекса контроллера Users, я проверил, что претензий там нет.

Md Farid Uddin Kiron 25.07.2024 10:18

Какая может быть причина в данном случае? Я уже прочитал много статей и не нашел решения, которое мне поможет?

CriticalError 25.07.2024 15:42

Возможно, что-то не так с версиями библиотеки, и мне посчастливилось скачать глючную версию.

CriticalError 25.07.2024 15:53

Наконец, я смог разобраться в проблеме и обновил для вас ответ. Попробуйте, надеюсь, это решит вашу проблему.

Md Farid Uddin Kiron 26.07.2024 04:41

Спасибо большое, все работает так, как мне нужно, но откуда я мог знать, что Microsoft.AspNetCore.Authentication.JwtBearer уже включает Microsoft.IdentityModel.Tokens, если nuget (или я чего-то не понимаю) в Microsoft.AspNetCore.Authentication.JwtBearer зависимостях не указан Microsoft.IdentityModel.Tokens? Прошу не допускать такой глупой ошибки в будущем.

CriticalError 26.07.2024 09:07

На самом деле, это сложно объяснить, потому что нет конкретного способа узнать это, но хотя вам понадобится какой-то конкретный пакет nuget, вы можете проверить его предварительные условия. Также можно увидеть примечание к выпуску. Но да, мне пришлось проверить много вещей, чтобы сделать такой вывод. Однако я не уверен, почему вы добавили этот пакет, поскольку он не требуется для вашего сценария.

Md Farid Uddin Kiron 26.07.2024 09:33

Это все потому, что я посмотрел реализацию JWT Token и статьи, которые я нашел, использовали это, возможно, это было для .net 6 или это была не обновленная информация. Еще хотелось бы попросить совета, хочу сделать так, чтобы после входа пользователь перенаправлялся на какую-то ссылку, но мой метод входа отправляет JWT Token и ссылку на которую будет произведено перенаправление, в результате JS script извлекает и вводит ответ на страницу входа в систему, я не знаю, смогу ли я каким-то образом отправить токен JWT и выполнить перенаправление с помощью RedirectToAction()?

CriticalError 26.07.2024 09:58

Поскольку эта проблема решена, лучше не расширять этот разговор, но, поскольку вы использовали js на стороне клиента, вам может потребоваться использовать ` window.location.href = '/Users/AccessDenied'` для перенаправления. Однако, если у вас есть какие-либо другие проблемы, вы можете опубликовать новый вопрос, чтобы я мог проверить, смогу ли я вам помочь.

Md Farid Uddin Kiron 26.07.2024 10:32

Другие вопросы по теме