You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 19:09:50