.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
相关产品推荐
相关产品推荐

