ASP.NET Core Identity角色权限异常:配置角色仍被拒绝访问
问题描述
我的项目中用户端位于根目录,管理端在Area区域内,两者的登录页面、布局及视图完全独立。需要实现以下需求:
- 管理端与用户端分别进行角色权限控制(如不同管理员无法访问不同页面,用户端同理);
- 管理端和用户端配置独立的LoginPath与AccessDeniedPath,示例:管理端为
/Management/Login/Index,用户端为/Login/Index; - 登录时进行校验,若用户尝试跨端登录(如管理员登录用户端),需提示“您未被授权”。
目前仅能实现登录时的校验,但无法基于Controller实现角色权限控制:当在HomeController的[Authorize]特性中添加Roles="User-IT"后,即使用户拥有该角色,仍会收到访问拒绝提示。
相关代码
Program.cs
var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpClient(); builder.Services.AddControllersWithViews(); builder.Services.AddDbContext<Context>(); builder.Services.AddIdentity<AppUser, AppRole>(options => { options.User.RequireUniqueEmail = true; }).AddEntityFrameworkStores<Context>().AddErrorDescriber<CustomIdentityValidator>().AddDefaultTokenProviders().AddRoles<AppRole>(); builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie("UserLogin", options => { options.LoginPath = "/Login/Index"; options.Cookie.Name = "UserLoginCookie"; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = context => { var now = DateTime.UtcNow; var expires = context.Properties.ExpiresUtc; if (expires != null && expires.Value < now) { context.RejectPrincipal(); context.ShouldRenew = true; context.Response.Redirect("/Login/Index"); } return Task.CompletedTask; } }; }) .AddCookie("ManagementLogin", options => { options.LoginPath = "/Management/Login/Index"; options.Cookie.Name = "ManagementLoginCookie"; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = context => { var now = DateTime.UtcNow; var expires = context.Properties.ExpiresUtc; if (expires != null && expires.Value < now) { context.RejectPrincipal(); context.ShouldRenew = true; context.Response.Redirect("/Management/Login/Index"); } return Task.CompletedTask; } }; }); builder.Services.AddSession(); builder.Services.AddDistributedMemoryCache(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseSession(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); app.Run();
LoginController.cs
[AllowAnonymous] public class LoginController : Controller { private readonly SignInManager<AppUser> _signInManager; private readonly UserManager<AppUser> _userManager; public LoginController(SignInManager<AppUser> signInManager, UserManager<AppUser> userManager) { _signInManager = signInManager; _userManager = userManager; } [HttpGet] public IActionResult Index() { return View(); } [HttpPost] public async Task<IActionResult> Index(LoginDto loginDto) { LoginValidator validationRules = new LoginValidator(); ValidationResult validationResult = await validationRules.ValidateAsync(loginDto); if (validationResult.IsValid) { var user = await _userManager.FindByNameAsync(loginDto.UserName); if (user != null) { var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, true); if (result.Succeeded) { if (!await _userManager.IsEmailConfirmedAsync(user)) { TempData["Mail"] = "Mail"; return RedirectToAction("Confirm", "Confirmation"); } else { var login = await _signInManager.PasswordSignInAsync(loginDto.UserName, loginDto.Password, true, true); if (login.Succeeded) { if (await _userManager.IsInRoleAsync(user, UserRoles.Kullanici)) { var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName), }; var userIdentity = new ClaimsIdentity(claims, "UserLogin"); var userPrincipal = new ClaimsPrincipal(userIdentity); await HttpContext.SignInAsync("UserLogin", userPrincipal); return RedirectToAction("Index", "Home"); } else { ModelState.AddModelError("", "Bu sayfaya erişim izniniz bulunmamaktadır."); return View(); } } else if (login.IsLockedOut) { ModelState.AddModelError("", "Fazla sayıda hatalı giriş yaptığınız için hesabınız kilitlendi. Lütfen daha sonra tekrar deneyiniz. Şifrenizi hatırlamıyorsanız 'Şifremi Unuttum' kısmından yeni bir şifre belirleyebilirsiniz."); } else { ModelState.AddModelError("", "Hatalı Kullanıcı Adı veya Şifre"); } } } else if (result.IsLockedOut) { ModelState.AddModelError("", "Fazla sayıda hatalı giriş yaptığınız için hesabınız kilitlendi. Lütfen daha sonra tekrar deneyiniz. Şifrenizi hatırlamıyorsanız 'Şifremi Unuttum' kısmından yeni bir şifre belirleyebilirsiniz."); } else { ModelState.AddModelError("", "Hatalı Kullanıcı Adı veya Şifre"); } } else { ModelState.AddModelError("", "Böyle Bir Hesap Bulunamadı"); } } else { foreach (var item in validationResult.Errors) { ModelState.AddModelError(item.PropertyName, item.ErrorMessage); } } return View(); } [HttpGet] public IActionResult ForgotPassword() { return View(); } }
原始HomeController.cs
[Authorize(AuthenticationSchemes = "UserLogin")] public class HomeController : Controller { public IActionResult Index() { return View(); } }
修改后的HomeController.cs
[Authorize(AuthenticationSchemes = "UserLogin", Roles="User-IT")] public class HomeController : Controller { public IActionResult Index() { return View(); } }
解决方案
角色权限不生效的核心原因是登录时未将用户的角色信息添加到ClaimsPrincipal中,授权系统无法读取到用户的角色声明。同时需要完善Cookie认证配置中的AccessDeniedPath,以及优化跨端登录校验逻辑。
1. 完善用户端登录逻辑,添加角色声明
修改LoginController的登录逻辑,移除重复的PasswordSignInAsync调用(自定义ClaimsPrincipal时无需重复调用),并添加用户的所有角色声明:
[HttpPost] public async Task<IActionResult> Index(LoginDto loginDto) { LoginValidator validationRules = new LoginValidator(); ValidationResult validationResult = await validationRules.ValidateAsync(loginDto); if (validationResult.IsValid) { var user = await _userManager.FindByNameAsync(loginDto.UserName); if (user != null) { var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, true); if (result.Succeeded) { if (!await _userManager.IsEmailConfirmedAsync(user)) { TempData["Mail"] = "Mail"; return RedirectToAction("Confirm", "Confirmation"); } else { if (await _userManager.IsInRoleAsync(user, UserRoles.Kullanici)) { // 获取用户所有角色 var roles = await _userManager.GetRolesAsync(user); var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName), // 批量添加角色声明 ..roles.Select(role => new Claim(ClaimTypes.Role, role)) }; var userIdentity = new ClaimsIdentity(claims, "UserLogin"); var userPrincipal = new ClaimsPrincipal(userIdentity); await HttpContext.SignInAsync("UserLogin", userPrincipal, new AuthenticationProperties { IsPersistent = loginDto.RememberMe // 假设LoginDto包含记住我字段 }); return RedirectToAction("Index", "Home"); } else { ModelState.AddModelError("", "您未被授权"); return View(); } } } else if (result.IsLockedOut) { ModelState.AddModelError("", "多次错误登录导致账户锁定,请稍后重试。忘记密码可通过“忘记密码”功能重置。"); } else { ModelState.AddModelError("", "用户名或密码错误"); } } else { ModelState.AddModelError("", "未找到该账户"); } } else { foreach (var item in validationResult.Errors) { ModelState.AddModelError(item.PropertyName, item.ErrorMessage); } } return View(); }
2. 完善Cookie认证配置,添加AccessDeniedPath
在Program.cs中,移除默认Scheme的设置(两个端使用独立认证Scheme),并为每个Scheme添加无权限跳转路径:
builder.Services.AddAuthentication() .AddCookie("UserLogin", options => { options.LoginPath = "/Login/Index"; options.AccessDeniedPath = "/Login/AccessDenied"; // 用户端无权限跳转页 options.Cookie.Name = "UserLoginCookie"; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = context => { var now = DateTime.UtcNow; var expires = context.Properties.ExpiresUtc; if (expires != null && expires.Value < now) { context.RejectPrincipal(); context.ShouldRenew = true; context.Response.Redirect("/Login/Index"); } return Task.CompletedTask; } }; }) .AddCookie("ManagementLogin", options => { options.LoginPath = "/Management/Login/Index"; options.AccessDeniedPath = "/Management/Login/AccessDenied"; // 管理端无权限跳转页 options.Cookie.Name = "ManagementLoginCookie"; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); options.Events = new CookieAuthenticationEvents { OnValidatePrincipal = context => { var now = DateTime.UtcNow; var expires = context.Properties.ExpiresUtc; if (expires != null && expires.Value < now) { context.RejectPrincipal(); context.ShouldRenew = true; context.Response.Redirect("/Management/Login/Index"); } return Task.CompletedTask; } }; });
3. 管理端登录逻辑优化(同用户端逻辑)
在管理端Area下的LoginController中,同样添加角色声明并校验管理端角色:
// 管理端LoginController登录方法核心片段 if (await _userManager.IsInRoleAsync(user, UserRoles.Admin)) { var roles = await _userManager.GetRolesAsync(user); var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName), ..roles.Select(role => new Claim(ClaimTypes.Role, role)) }; var adminIdentity = new ClaimsIdentity(claims, "ManagementLogin"); var adminPrincipal = new ClaimsPrincipal(adminIdentity); await HttpContext.SignInAsync("ManagementLogin", adminPrincipal); return RedirectToAction("Index", "Home", new { area = "Management" }); } else { ModelState.AddModelError("", "您未被授权"); return View(); }
4. 验证角色权限
修改后的HomeController添加Roles特性后即可正常生效,系统会自动读取Claims中的角色声明进行权限校验:
[Authorize(AuthenticationSchemes = "UserLogin", Roles = "User-IT")] public class HomeController : Controller { public IActionResult Index() { return View(); } }
内容的提问来源于stack exchange,提问作者Gökmen Ada
相关产品推荐
相关产品推荐

