.NET 8无脚手架+自定义数据库实现自定义角色提供者方案
ASP.NET Core 8 MVC 自定义角色校验实现方案
针对你的自定义表结构(User/Role/UserRole),无需Code First或脚手架,以下是兼容[Authorize(Roles)]和IsInRole()的实现步骤:
1. 定义实体类
对应自定义数据库表,仅用于数据映射:
// User表实体 public class CustomUser { public Guid UserId { get; set; } public string Email { get; set; } } // Role表实体 public class CustomRole { public Guid RoleId { get; set; } public string Title { get; set; } } // UserRole关联表实体 public class CustomUserRole { public Guid UserId { get; set; } public Guid RoleId { get; set; } }
2. 实现角色数据访问类
负责从数据库查询用户关联的角色:
public class CustomRoleRepository { private readonly string _connectionString; public CustomRoleRepository(IConfiguration configuration) { _connectionString = configuration.GetConnectionString("YourDbConnection"); } // 根据用户ID获取所有角色名称 public async Task<List<string>> GetUserRolesAsync(Guid userId) { var roles = new List<string>(); using (var conn = new SqlConnection(_connectionString)) { await conn.OpenAsync(); var sql = @" SELECT r.Title FROM UserRole ur JOIN Role r ON ur.RoleId = r.RoleId WHERE ur.UserId = @UserId"; var cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@UserId", userId); using (var reader = await cmd.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { roles.Add(reader.GetString(0)); } } } return roles; } }
3. 两种角色集成方案
方案一:登录时一次性加载角色(适合角色变动频率低的场景)
在登录逻辑中验证用户后,将角色作为Claim添加到认证主体:
public async Task<IActionResult> Login(LoginModel model) { // 1. 自定义用户验证逻辑(查询User表校验邮箱和密码) var user = await ValidateUserAsync(model.Email, model.Password); if (user == null) { ModelState.AddModelError("", "账号或密码错误"); return View(model); } // 2. 获取用户角色 var roleRepo = HttpContext.RequestServices.GetRequiredService<CustomRoleRepository>(); var roles = await roleRepo.GetUserRolesAsync(user.UserId); // 3. 构建认证Claims var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()), new Claim(ClaimTypes.Email, user.Email) }; // 添加角色声明 foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role)); } var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); // 4. 执行登录 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = model.RememberMe, ExpiresUtc = DateTimeOffset.UtcNow.AddDays(7) }); return RedirectToAction("Index", "Home"); } // 自行实现用户验证方法 private async Task<CustomUser> ValidateUserAsync(string email, string password) { // 查询User表,验证邮箱和密码(注意密码应存储哈希值,此处仅为示例) using (var conn = new SqlConnection(_connectionString)) { await conn.OpenAsync(); var sql = "SELECT UserId, Email FROM User WHERE Email = @Email AND Password = @Password"; var cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@Email", email); cmd.Parameters.AddWithValue("@Password", password); // 实际应使用哈希校验 using (var reader = await cmd.ExecuteReaderAsync()) { if (await reader.ReadAsync()) { return new CustomUser { UserId = reader.GetGuid(0), Email = reader.GetString(1) }; } } } return null; }
方案二:动态加载角色(适合角色实时变动,无需重新登录的场景)
实现IClaimsTransformation接口,每次请求时自动同步最新角色:
public class CustomClaimsTransformer : IClaimsTransformation { private readonly CustomRoleRepository _roleRepo; private readonly IMemoryCache _cache; public CustomClaimsTransformer(CustomRoleRepository roleRepo, IMemoryCache cache) { _roleRepo = roleRepo; _cache = cache; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { if (!principal.Identity.IsAuthenticated) return principal; if (!Guid.TryParse(principal.FindFirstValue(ClaimTypes.NameIdentifier), out var userId)) return principal; // 使用缓存减少数据库查询压力(缓存10分钟) var cacheKey = $"UserRoles_{userId}"; var latestRoles = await _cache.GetOrCreateAsync(cacheKey, async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10); return await _roleRepo.GetUserRolesAsync(userId); }); var identity = principal.Identity as ClaimsIdentity; var existingRoles = principal.FindAll(ClaimTypes.Role).Select(c => c.Value).ToList(); // 添加新增角色 foreach (var role in latestRoles) { if (!existingRoles.Contains(role)) identity.AddClaim(new Claim(ClaimTypes.Role, role)); } // 移除已取消的角色 foreach (var existingRole in existingRoles) { if (!latestRoles.Contains(existingRole)) { var claimToRemove = identity.FindFirst(c => c.Type == ClaimTypes.Role && c.Value == existingRole); if (claimToRemove != null) identity.RemoveClaim(claimToRemove); } } return principal; } }
4. 配置服务与中间件
在Program.cs中注册服务并启用认证授权:
var builder = WebApplication.CreateBuilder(args); // 添加控制器和视图服务 builder.Services.AddControllersWithViews(); // 注册角色数据访问类和Claims转换器(方案二需要) builder.Services.AddScoped<CustomRoleRepository>(); builder.Services.AddScoped<IClaimsTransformation, CustomClaimsTransformer>(); // 配置Cookie认证 builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Account/Login"; options.AccessDeniedPath = "/Account/AccessDenied"; }); // 启用授权服务 builder.Services.AddAuthorization(); var app = builder.Build(); // 中间件配置 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); // 必须先启用认证,再启用授权 app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
5. 使用方式
控制器/Action授权
直接使用[Authorize(Roles)]特性,支持多角色逗号分隔:
[Authorize(Roles = "Admin,Editor")] public IActionResult ManageContent() { return View(); }
Razor视图角色校验
使用User.IsInRole()方法:
@if (User.IsInRole("Admin")) { <div class="admin-menu"> <a href="/Admin/Dashboard">管理员面板</a> </div> }
注意事项
- 确保
appsettings.json中配置正确的数据库连接字符串:
{ "ConnectionStrings": { "YourDbConnection": "Server=.;Database=你的数据库名;Trusted_Connection=True;TrustServerCertificate=True;" } }
- 密码存储必须使用哈希算法(如BCrypt),禁止明文存储
- 方案二的缓存时间可根据业务需求调整,平衡实时性与性能
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

