基于C# ASP.NET MVC的多分支员工管理系统数据隔离实现问询
解决方案:实现多分支数据隔离与权限验证
你的整体思路是可行的,但当前的自定义授权属性仅完成了登录状态和ChiNhanhId有效性校验,未关联当前用户的分支权限,也未处理数据层面的过滤逻辑。以下是具体落地步骤:
1. 修正授权逻辑:绑定用户与分支ID
不要依赖Session存储ChiNhanhId,建议在用户登录时将ChiNhanhId存入身份认证Claims,这样更安全且与用户身份强绑定:
// 登录时的核心代码示例 var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName), new Claim("ChiNhanhId", user.ChiNhanhId.ToString()), new Claim(ClaimTypes.Role, user.RoleName) }; var identity = new ClaimsIdentity(claims, "ApplicationCookie"); HttpContext.GetOwinContext().Authentication.SignIn( new AuthenticationProperties { IsPersistent = rememberMe }, identity );
随后修改自定义授权属性,从Claims中读取ChiNhanhId,并验证当前用户与分支的归属关系:
public class CustomAuthorizeWithChiNhanhAttribute : AuthorizeAttribute { private Db db = new Db(); protected override bool AuthorizeCore(HttpContextBase httpContext) { var user = httpContext.User; if (!user.Identity.IsAuthenticated) { Debug.WriteLine("[CustomAuthorize] 用户未登录"); return false; } // 从Claims获取当前用户的ChiNhanhId var chiNhanhIdClaim = user.Claims.FirstOrDefault(c => c.Type == "ChiNhanhId"); if (chiNhanhIdClaim == null || !int.TryParse(chiNhanhIdClaim.Value, out int chiNhanhId) || chiNhanhId <= 0) { Debug.WriteLine("[CustomAuthorize] 用户分支ID无效"); return false; } // 验证当前用户确实属于该分支(防止Claims被篡改) var currentUser = db.NhanViens.SingleOrDefault(nv => nv.UserName == user.Identity.Name && nv.ChiNhanhId == chiNhanhId ); if (currentUser == null) { Debug.WriteLine("[CustomAuthorize] 用户与分支不匹配"); return false; } // 将分支ID存入HttpContext.Items,供后续逻辑使用 httpContext.Items["CurrentChiNhanhId"] = chiNhanhId; return true; } protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary { { "controller", "Home" }, { "action", "DangNhap" } } ); } }
2. 全局数据过滤:自动限制分支数据
为避免每个控制器重复编写Where(nv => nv.ChiNhanhId == xxx),可以通过EF的全局查询过滤实现自动数据隔离:
针对EF6:实现IDbCommandInterceptor
public class BranchDataInterceptor : IDbCommandInterceptor { public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { ModifyCommand(command, interceptionContext); } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { ModifyCommand(command, interceptionContext); } public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { ModifyCommand(command, interceptionContext); } private void ModifyCommand(DbCommand command, DbCommandInterceptionContext interceptionContext) { var httpContext = HttpContext.Current; // FullAdmin不受分支限制 if (httpContext == null || httpContext.User.IsInRole("FullAdmin")) return; if (httpContext.Items["CurrentChiNhanhId"] is int chiNhanhId && chiNhanhId > 0) { // 对指定表添加分支过滤条件 var tablesToFilter = new[] { "NhanViens", "Orders", "Positions", "Titles" }; foreach (var table in tablesToFilter) { if (command.CommandText.Contains(table)) { command.CommandText = command.CommandText.Replace( $"FROM [{table}]", $"FROM [{table}] WHERE [{table}].[ChiNhanhId] = @ChiNhanhId" ); command.Parameters.Add(new SqlParameter("@ChiNhanhId", chiNhanhId)); } } } } // 空实现其他接口方法 public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) { } public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { } public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { } } // 在Global.asax注册拦截器 protected void Application_Start() { DbInterception.Add(new BranchDataInterceptor()); // 其他初始化代码 }
针对EF Core:使用全局查询过滤器
protected override void OnModelCreating(ModelBuilder modelBuilder) { var httpContext = HttpContextAccessor.HttpContext; if (httpContext != null && !httpContext.User.IsInRole("FullAdmin")) { if (int.TryParse(httpContext.User.Claims.FirstOrDefault(c => c.Type == "ChiNhanhId")?.Value, out int chiNhanhId)) { modelBuilder.Entity<NhanVien>().HasQueryFilter(nv => nv.ChiNhanhId == chiNhanhId); modelBuilder.Entity<Order>().HasQueryFilter(o => o.ChiNhanhId == chiNhanhId); modelBuilder.Entity<Position>().HasQueryFilter(p => p.ChiNhanhId == chiNhanhId); modelBuilder.Entity<Title>().HasQueryFilter(t => t.ChiNhanhId == chiNhanhId); } } }
3. 操作级验证:防止数据篡改
即使有全局过滤,在执行编辑、删除等操作时,仍需额外验证目标数据的ChiNhanhId是否与当前用户匹配(避免用户手动修改URL参数越权访问):
// 基类控制器封装通用验证逻辑 public class BaseBranchController : Controller { protected Db db = new Db(); protected bool IsDataBelongsToCurrentBranch(int entityChiNhanhId) { if (User.IsInRole("FullAdmin")) return true; if (HttpContext.Items["CurrentChiNhanhId"] is int currentChiNhanhId) { return entityChiNhanhId == currentChiNhanhId; } return false; } } // 员工控制器示例 [CustomAuthorizeWithChiNhanh] public class NhanVienController : BaseBranchController { public ActionResult Edit(int id) { var nhanVien = db.NhanViens.Find(id); if (nhanVien == null || !IsDataBelongsToCurrentBranch(nhanVien.ChiNhanhId)) { return new HttpStatusCodeResult(HttpStatusCode.Forbidden); } // 后续编辑逻辑 return View(nhanVien); } }
4. 特殊处理Full Admin
在授权属性和全局过滤逻辑中,对FullAdmin角色做排除处理,确保管理员可以访问所有分支的数据。
内容的提问来源于stack exchange,提问作者Zaria
相关产品推荐
相关产品推荐

