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

.NET Core 7 API结合Identity与仓储模式实现动态角色权限的咨询

.NET Core Identity 结合动态角色与声明实现指南

一、基础配置:扩展Identity模型并集成

不用完全自定义用户/角色模型,直接扩展Identity内置类,既复用基础功能,又能添加自定义关联逻辑(比如角色绑定菜单)。

1. 扩展Identity实体

// 自定义用户类(可添加昵称等自定义字段)
public class ApplicationUser : IdentityUser { }

// 自定义角色类,关联菜单集合
public class ApplicationRole : IdentityRole
{
    public ICollection<RoleMenu> RoleMenus { get; set; } = new List<RoleMenu>();
}

// 角色-菜单关联表(复合主键)
public class RoleMenu
{
    public string RoleId { get; set; }
    public ApplicationRole Role { get; set; }
    public int MenuId { get; set; }
    public Menu Menu { get; set; }
}

// 菜单模型,可添加权限声明字段
public class Menu
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public string Icon { get; set; }
    public int ParentId { get; set; }
    // 对应权限声明,如"Menu.View.Dashboard"
    public string RequiredClaim { get; set; }
    public ICollection<RoleMenu> RoleMenus { get; set; } = new List<RoleMenu>();
}

2. 配置DbContext

让你的DbContext继承IdentityDbContext,配置关联关系:

public class AppDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Menu> Menus { get; set; }
    public DbSet<RoleMenu> RoleMenus { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // 配置RoleMenu复合主键
        builder.Entity<RoleMenu>().HasKey(rm => new { rm.RoleId, rm.MenuId });
        // 配置角色与菜单的关联
        builder.Entity<RoleMenu>()
            .HasOne(rm => rm.Role)
            .WithMany(r => r.RoleMenus)
            .HasForeignKey(rm => rm.RoleId);
        builder.Entity<RoleMenu>()
            .HasOne(rm => rm.Menu)
            .WithMany(m => m.RoleMenus)
            .HasForeignKey(rm => rm.MenuId);
    }
}

3. 注册Identity服务

在Program.cs中注册Identity和DbContext:

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<AppDbContext>()
    .AddDefaultTokenProviders();

二、动态角色与菜单管理实现

1. 角色管理API

实现角色的增删改查,以及角色绑定菜单的功能:

[ApiController]
[Route("api/roles")]
public class RolesController : ControllerBase
{
    private readonly RoleManager<ApplicationRole> _roleManager;
    private readonly AppDbContext _dbContext;

    public RolesController(RoleManager<ApplicationRole> roleManager, AppDbContext dbContext)
    {
        _roleManager = roleManager;
        _dbContext = dbContext;
    }

    // 创建角色并绑定菜单
    [HttpPost]
    public async Task<IActionResult> CreateRole([FromBody] CreateRoleRequest request)
    {
        var role = new ApplicationRole { Name = request.RoleName };
        var result = await _roleManager.CreateAsync(role);
        
        if (!result.Succeeded) return BadRequest(result.Errors);

        // 绑定菜单(如果传入菜单ID列表)
        if (request.MenuIds?.Any() == true)
        {
            var roleMenus = request.MenuIds.Select(id => new RoleMenu
            {
                RoleId = role.Id,
                MenuId = id
            });
            await _dbContext.RoleMenus.AddRangeAsync(roleMenus);
            await _dbContext.SaveChangesAsync();
        }
        return Ok(role);
    }

    // 其他接口:更新角色、删除角色、修改角色菜单绑定等
}

public class CreateRoleRequest
{
    public string RoleName { get; set; }
    public List<int> MenuIds { get; set; }
}

2. 菜单管理API

实现菜单的增删改查:

[ApiController]
[Route("api/menus")]
public class MenusController : ControllerBase
{
    private readonly AppDbContext _dbContext;

    public MenusController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    [HttpPost]
    public async Task<IActionResult> CreateMenu([FromBody] Menu menu)
    {
        _dbContext.Menus.Add(menu);
        await _dbContext.SaveChangesAsync();
        return CreatedAtAction(nameof(GetMenu), new { id = menu.Id }, menu);
    }

    // 其他接口:更新、删除、获取菜单列表等
}

三、JWT令牌与动态菜单加载

1. 配置JWT认证

在Program.cs中配置JWT验证:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });

2. 生成JWT时携带角色与声明

登录接口中,从Identity获取用户角色和声明,同时添加菜单对应的权限声明:

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly IConfiguration _configuration;
    private readonly AppDbContext _dbContext;

    public AuthController(UserManager<ApplicationUser> userManager, IConfiguration configuration, AppDbContext dbContext)
    {
        _userManager = userManager;
        _configuration = configuration;
        _dbContext = dbContext;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        var user = await _userManager.FindByEmailAsync(request.Email);
        if (user == null || !await _userManager.CheckPasswordAsync(user, request.Password))
            return Unauthorized();

        // 获取用户角色和基础声明
        var roles = await _userManager.GetRolesAsync(user);
        var baseClaims = await _userManager.GetClaimsAsync(user);
        // 添加角色声明
        var roleClaims = roles.Select(r => new Claim(ClaimTypes.Role, r));
        // 添加菜单权限声明
        var menuClaims = await _dbContext.RoleMenus
            .Where(rm => roles.Contains(rm.Role.Name))
            .Select(rm => rm.Menu.RequiredClaim)
            .Distinct()
            .Where(c => !string.IsNullOrEmpty(c))
            .Select(c => new Claim("MenuClaim", c))
            .ToListAsync();

        var allClaims = baseClaims.Concat(roleClaims).Concat(menuClaims);

        // 生成JWT
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: allClaims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: credentials);

        return Ok(new
        {
            token = new JwtSecurityTokenHandler().WriteToken(token),
            expiration = token.ValidTo
        });
    }
}

public class LoginRequest
{
    public string Email { get; set; }
    public string Password { get; set; }
}

3. 根据用户身份加载导航菜单

创建接口返回当前用户可访问的菜单:

[ApiController]
[Route("api/user/menus")]
[Authorize]
public class UserMenusController : ControllerBase
{
    private readonly AppDbContext _dbContext;
    private readonly UserManager<ApplicationUser> _userManager;

    public UserMenusController(AppDbContext dbContext, UserManager<ApplicationUser> userManager)
    {
        _dbContext = dbContext;
        _userManager = userManager;
    }

    [HttpGet]
    public async Task<IActionResult> GetUserMenus()
    {
        var user = await _userManager.GetUserAsync(User);
        var roles = await _userManager.GetRolesAsync(user);

        // 获取用户角色关联的所有菜单
        var menus = await _dbContext.Menus
            .Where(m => m.RoleMenus.Any(rm => roles.Contains(rm.Role.Name)))
            .OrderBy(m => m.ParentId)
            .ThenBy(m => m.Id)
            .ToListAsync();

        // 构建树形菜单(可选)
        var treeMenus = BuildMenuTree(menus, 0);
        return Ok(treeMenus);
    }

    private List<Menu> BuildMenuTree(List<Menu> menus, int parentId)
    {
        return menus.Where(m => m.ParentId == parentId)
            .Select(m => new Menu
            {
                Id = m.Id,
                Name = m.Name,
                Url = m.Url,
                Icon = m.Icon,
                ParentId = m.ParentId,
                RequiredClaim = m.RequiredClaim,
                Children = BuildMenuTree(menus, m.Id)
            }).ToList();
    }
}

四、基于声明的API权限控制

1. 注册权限策略(可选)

在Program.cs中注册全局策略,方便复用:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ViewDashboard", policy =>
        policy.RequireClaim("MenuClaim", "Menu.View.Dashboard"));
});

2. 控制器/Action上应用权限

直接在Action上使用[Authorize]指定策略,或手动检查声明:

[ApiController]
[Route("api/dashboard")]
[Authorize]
public class DashboardController : ControllerBase
{
    // 使用策略控制访问
    [HttpGet]
    [Authorize(Policy = "ViewDashboard")]
    public IActionResult GetDashboardData()
    {
        return Ok(new { data = "Dashboard content" });
    }

    // 手动检查声明
    [HttpPost]
    public IActionResult UpdateDashboard()
    {
        if (!User.HasClaim(c => c.Type == "MenuClaim" && c.Value == "Menu.Edit.Dashboard"))
            return Forbid();
        // 执行更新逻辑
        return Ok();
    }
}

五、关键注意事项

  • 所有角色名称从数据库动态获取,避免硬编码或枚举。
  • 菜单声明设计成[资源].[操作]格式(如User.Edit),提升权限控制灵活性。
  • 可将数据操作封装到仓储类中,保持你熟悉的仓储模式开发习惯。
  • 实现令牌刷新机制,确保用户权限变更后能及时获取新的声明。

内容的提问来源于stack exchange,提问作者Santhoshkumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 20:13:11