C#开发的MySQL通用权限管理框架(含完整源码)
简介:在企业级应用中,权限管理是保障系统安全的核心环节。本项目基于C#与MySQL构建了一个通用、可复用的权限框架,涵盖身份验证、角色权限控制、数据库操作与系统安全性设计。通过ASP.NET Core的身份验证机制、Entity Framework进行数据访问,结合中间件实现权限校验,并采用安全编码与日志记录提升系统可靠性。项目还包含完整的CRUD逻辑、ViewModel数据传递、单元测试及部署扩展方案,适用于各类需要精细化权限控制的应用场景。
C#与ASP.NET Core构建现代权限系统:从理论到实战的深度探索
在当今的企业级应用开发中,安全早已不再是“锦上添花”的附加功能,而是贯穿整个架构设计的生命线。尤其是在微服务、前后端分离和云原生盛行的时代背景下,一个灵活、可扩展且高度安全的权限系统,直接决定了系统的稳定性、合规性以及用户体验。
你有没有遇到过这样的场景?
👉 新增一个“财务专员”角色,结果要改七八个接口的硬编码判断;
👉 客户突然要求“只允许特定部门查看报表”,却发现权限逻辑散落在各处,根本没法统一管理;
👉 上线后发现某个API能被未授权用户访问——只因为忘了加 [Authorize] ?
这些问题的背后,往往不是技术选型的问题,而是 权限体系设计的缺失 。今天,我们就来一场彻头彻尾的“权限重构之旅”,用 C# 的面向对象思想 + ASP.NET Core 的现代化安全机制 + MySQL 与 EF Core 的高效数据支撑,搭建一套真正生产可用的权限框架。
准备好了吗?🚀 让我们从最底层的设计哲学开始讲起。
🧱 面向对象思维如何重塑权限模型?
很多人一提到权限系统,第一反应就是“用户、角色、权限”三张表。但这只是冰山一角。真正的挑战在于: 如何让这个结构既能应对当前需求,又能轻松适应未来的变化?
C# 的三大特性——封装、继承、多态,在这里大放异彩。
把“权限”变成活的对象,而不是死字符串
传统做法是把权限存成 "user:create" 这样的字符串,然后到处 if(permission == "user:create") 。听起来很直观,但一旦业务复杂起来,维护成本会指数级上升。
更好的方式是: 把权限抽象为类!
public abstract class Permission
{
public string Code { get; protected set; }
public string Description { get; protected set; }
// 每个权限都可以自我验证是否满足条件
public abstract bool IsAllowed(ClaimsPrincipal user, HttpContext context);
}
public class MenuPermission : Permission
{
public string MenuName { get; }
public override bool IsAllowed(ClaimsPrincipal user, HttpContext context)
{
return user.HasClaim("menu_access", MenuName);
}
}
public class ActionPermission : Permission
{
public HttpMethod AllowedMethod { get; }
public string Resource { get; }
public override bool IsAllowed(ClaimsPrincipal user, HttpContext context)
{
return context.Request.Method == AllowedMethod.ToString() &&
user.HasClaim("action", $"{Resource}:{AllowedMethod}");
}
}
看到没?我们不再只是存储 "users:read" ,而是创建了一个具有行为能力的对象。它知道自己代表什么操作,并且能主动参与决策过程。
💡 工程建议 :对于高频使用的权限类型(如菜单访问、按钮级操作),可以预先注册到 DI 容器中,避免每次都要反射创建实例。
接口隔离原则:别让你的服务承担太多责任!
还记得那个臃肿的 UserService 吗?既管登录,又管权限校验,还负责发送邮件……典型的反模式。
正确的做法是使用接口进行职责分离:
public interface IRoleService
{
Task<bool> HasPermissionAsync(string roleName, string permissionCode);
Task<IEnumerable<string>> GetPermissionsByRoleAsync(string roleId);
Task AssignPermissionToRoleAsync(string roleId, string permissionId);
}
public interface IUserPermissionService
{
Task<List<Permission>> GetUserEffectivePermissionsAsync(string userId);
Task<bool> UserHasPermissionAsync(string userId, string permissionCode);
}
这样做的好处是什么?
- ✅ 单元测试更容易:你可以 mock
IRoleService而不影响其他模块; - ✅ 扩展性强:未来如果想接入 LDAP 或 OAuth2 角色体系,只需实现新版本即可;
- ✅ 团队协作更顺畅:前端调用
/api/permissions/me获取权限列表时,完全不需要知道背后是数据库还是 Redis。
工厂模式登场:动态生成策略实例
假设你的系统支持多种权限策略:
- 基于角色的(RBAC)
- 基于属性的(ABAC)
- 基于任务的(TBAC)
每种策略的判断逻辑完全不同。这时候,抽象工厂就派上用场了!
public interface IAuthorizationStrategy
{
Task<bool> CheckAsync(AuthorizationContext context);
}
public class RoleBasedStrategy : IAuthorizationStrategy { /* ... */ }
public class AttributeBasedStrategy : IAuthorizationStrategy { /* ... */ }
public class AuthorizationStrategyFactory
{
private readonly IServiceProvider _serviceProvider;
public AuthorizationStrategyFactory(IServiceProvider serviceProvider) =>
_serviceProvider = serviceProvider;
public IAuthorizationStrategy Create(string strategyType)
{
return strategyType.ToLower() switch
{
"rbac" => _serviceProvider.GetRequiredService<RoleBasedStrategy>(),
"abac" => _serviceProvider.GetRequiredService<AttributeBasedStrategy>(),
_ => throw new NotSupportedException($"不支持的策略类型: {strategyType}")
};
}
}
然后在配置文件中指定当前启用的策略:
{
"Security": {
"DefaultAuthorizationStrategy": "rbac"
}
}
控制器里就可以这样用:
[HttpGet("profile")]
public async Task<IActionResult> GetProfile()
{
var context = new AuthorizationContext(User, Request, "profile:read");
var strategy = _factory.Create("rbac");
if (!await strategy.CheckAsync(context))
return Forbid();
return Ok(_userService.GetProfile(User.Identity.Name));
}
这种设计不仅解耦了具体实现,还为未来的灰度发布、A/B 测试打下了基础。
🔐 身份认证:Cookie vs JWT,到底怎么选?
现在我们有了清晰的权限模型,接下来要考虑的是: 怎么证明“你是你”?
ASP.NET Core 提供了极其灵活的身份认证机制,核心组件包括:
AuthenticationMiddleware:负责解析请求中的凭证;IAuthenticationService:执行具体的认证流程;AuthenticationHandler<T>:处理特定方案(如 Cookie、JWT)的逻辑;ClaimsPrincipal:封装用户身份信息。
但关键问题来了:该用 Cookie 还是 JWT?
Cookie 认证:适合传统MVC应用的经典选择
如果你的应用是服务器渲染为主(比如 Razor Pages 或 MVC),那 Cookie 是天然之选。
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/account/login";
options.LogoutPath = "/account/logout";
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
几个关键参数说明:
| 参数 | 推荐值 | 为什么重要 |
|---|---|---|
SlidingExpiration |
true |
用户持续操作时自动延长会话,提升体验 |
HttpOnly |
true |
防止 XSS 攻击读取 Cookie |
SecurePolicy |
SameAsRequest |
HTTPS 下强制加密传输 |
⚠️ 注意:Cookie 方案最大的痛点是 跨域共享 Session 。如果你有多台服务器,必须引入 Redis 等分布式缓存来同步 Session 数据。
// 使用 Redis 共享 Session
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
services.AddSession();
否则就会出现“登录这台机器有效,换一台就掉线”的诡异现象 😵💫
JWT:前后端分离时代的王者
当你做 SPA(单页应用)或移动端对接 API 时,JWT 几乎是标配。
它的基本流程很简单:
sequenceDiagram
participant Client
participant Server
Client->>Server: POST /login (username/password)
Server->>Server: 验证凭据,生成JWT
Server-->>Client: 返回JWT令牌
Client->>Server: 请求API(Authorization: Bearer <token>)
Server->>Server: 解析JWT,验证签名和过期时间
alt 有效
Server-->>Client: 返回资源数据
else 无效或过期
Server-->>Client: 401 Unauthorized
end
生成 JWT 的代码也很直观:
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes("your-super-secret-key-that-is-long-enough!!!");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(ClaimTypes.Role, "Admin"),
new Claim("department", "finance")
}),
Expires = DateTime.UtcNow.AddHours(2),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
🔥 最佳实践提示 :
- 密钥长度至少 32 字节(推荐 64+),防止暴力破解;
- 不要在 Payload 中存放敏感信息(如身份证号),虽然它是 Base64 编码但并非加密;
- 设置合理的过期时间(一般 15min ~ 2h),太长有安全风险,太短影响体验;
- 实现 Refresh Token 机制,避免频繁重新登录。
对比总结:两种方案的核心差异
| 维度 | Cookie 认证 | JWT 认证 |
|---|---|---|
| 状态管理 | 有状态(依赖 Session) | 无状态 |
| 部署复杂度 | 分布式需共享 Session 存储 | 天然支持分布式 |
| 安全威胁 | 易受 CSRF 攻击 | 易受 XSS 攻击(若 localStorage 存储) |
| 可吊销性 | 可立即清除 Session | 需黑名单机制(如 Redis) |
| 适用场景 | 内部管理系统、SSR 应用 | 前后端分离、移动App、微服务 |
📌 我的建议是: 优先考虑 JWT ,除非你有强烈的 SEO 需求或必须支持低版本浏览器。
👤 ASP.NET Core Identity:不只是用户管理
很多人以为 Identity 就是用来做注册登录的,其实它远不止如此。
核心组件一览
| 组件 | 作用 |
|---|---|
UserManager<T> |
创建、删除、重置密码等用户操作 |
SignInManager<T> |
登录、登出、双因素认证 |
RoleManager<T> |
角色 CRUD |
IUserStore<T> / IRoleStore<T> |
数据持久化抽象层 |
它们都通过依赖注入注入到控制器中:
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (!ModelState.IsValid) return View(model);
var result = await _signInManager.PasswordSignInAsync(
model.Username,
model.Password,
isPersistent: false,
lockoutOnFailure: true);
if (result.Succeeded)
return RedirectToAction("Index", "Home");
ModelState.AddModelError("", "用户名或密码错误");
return View(model);
}
}
注意到 lockoutOnFailure: true 了吗?这是防暴力破解的关键设置。连续失败几次后账户会被暂时锁定,极大提升了安全性。
自定义用户类:添加企业专属字段
默认的 IdentityUser 只有 UserName , Email , PhoneNumber 等基础字段。但在实际项目中,你可能需要:
- 员工编号
- 所属部门
- 入职时间
- 头像 URL
这时就要继承 IdentityUser :
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmployeeNo { get; set; }
public string Department { get; set; }
public DateTime JoinDate { get; set; } = DateTime.UtcNow;
public string AvatarUrl { get; set; }
}
然后在 DbContext 中继承 IdentityDbContext<ApplicationUser> :
public class AppDbContext : IdentityDbContext<ApplicationUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// 自定义配置
builder.Entity<ApplicationUser>(b =>
{
b.Property(u => u.EmployeeNo).HasMaxLength(20).IsRequired();
b.HasIndex(u => u.EmployeeNo).IsUnique();
});
}
}
✨ 小技巧:可以在种子数据中预置管理员账号:
public static async Task SeedUsersAsync(IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
if (await userManager.FindByNameAsync("admin") == null)
{
var admin = new ApplicationUser
{
UserName = "admin",
Email = "admin@company.com",
EmployeeNo = "EMP001",
Department = "IT",
JoinDate = DateTime.Now
};
await userManager.CreateAsync(admin, "P@ssw0rd123!");
await userManager.AddToRoleAsync(admin, "Administrator");
}
}
启动时调用一下,省去手动初始化的麻烦。
🛡️ RBAC 模型落地:不仅仅是 [Authorize(Roles="Admin")]
基于角色的访问控制(RBAC)是最常见的权限模型,但它很容易被用得“很糙”。
数据库设计:规范化才是长久之道
很多团队喜欢把权限直接写进角色字段里,比如:
UPDATE roles SET permissions = '["user:create","user:delete"]' WHERE name = 'Admin';
这种 JSON 存储看似方便,实则埋下巨大隐患:
- 无法建立外键约束
- 查询性能差(不能走索引)
- 修改权限时容易出错
正确的方式是采用标准的四表结构:
erDiagram
users ||--o{ user_roles : ""
roles ||--o{ user_roles : ""
roles ||--o{ role_permissions : ""
permissions ||--o{ role_permissions : ""
users {
BIGINT id PK
VARCHAR username
CHAR password_hash
VARCHAR email
}
roles {
BIGINT id PK
VARCHAR name
TEXT description
}
permissions {
BIGINT id PK
VARCHAR code
TEXT description
VARCHAR module
}
user_roles {
BIGINT user_id PK
BIGINT role_id PK
DATETIME assigned_at
}
role_permissions {
BIGINT role_id PK
BIGINT permission_id PK
DATETIME granted_at
}
每个权限都有唯一 code ,例如:
- user:create
- order:cancel
- report:export-pdf
这样做的优势非常明显:
✅ 权限变更无需修改代码
✅ 支持细粒度审计日志
✅ 可视化界面轻松拖拽赋权
登录时注入权限声明:减少数据库查询压力
每次请求都去查数据库判断权限?NO!太慢了!
聪明的做法是在用户登录成功后,一次性把所有权限加载进来,作为 Claim 注入到 ClaimsPrincipal 中:
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Email, user.Email),
new Claim("employee_no", user.EmployeeNo),
new Claim("department", user.Department)
};
// 查询该用户所有权限
var permissions = await _userPermissionService.GetPermissionsByUserAsync(user.Id);
foreach (var perm in permissions)
{
claims.Add(new Claim("permission", perm.Code)); // 类型为 "permission"
}
var identity = new ClaimsIdentity(claims, "CustomCookieAuth");
await HttpContext.SignInAsync(new ClaimsPrincipal(identity));
之后在任何地方都可以快速判断:
// 判断是否有某项权限
if (User.HasClaim("permission", "user:create"))
{
// 显示“新增用户”按钮
}
⚡ 性能对比:
- ❌ 每次查询数据库:~50ms
- ✅ 使用内存声明:~0.01ms
差距高达 5000倍 !这就是架构设计的力量 💪
Policy 模式:超越角色的高级授权
有时候光靠角色不够用了。比如:
“只有文章作者才能编辑自己的内容”
这种情况就不能用 [Authorize(Roles="Editor")] 解决了,因为它不知道谁是作者。
答案是: 自定义授权策略(Policy-based Authorization)
第一步:定义需求(Requirement)
public class OwnerRequirement : IAuthorizationRequirement
{
public string ResourceType { get; }
public OwnerRequirement(string resourceType)
{
ResourceType = resourceType;
}
}
第二步:编写处理器(Handler)
public class OwnerAuthorizationHandler : AuthorizationHandler<OwnerRequirement>
{
private readonly IBlogService _blogService;
public OwnerAuthorizationHandler(IBlogService blogService)
{
_blogService = blogService;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OwnerRequirement requirement)
{
if (context.Resource is Endpoint endpoint)
{
var httpContext = context.HttpContext;
var routeData = httpContext.GetRouteData();
var postId = routeData.Values["id"]?.ToString();
if (postId != null)
{
var post = await _blogService.GetPostByIdAsync(int.Parse(postId));
var currentUserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (post?.AuthorId == currentUserId)
{
context.Succeed(requirement); // 授权通过!
}
}
}
}
}
第三步:注册策略
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("OwnsPost", policy =>
policy.Requirements.Add(new OwnerRequirement("Post")));
});
builder.Services.AddScoped<IAuthorizationHandler, OwnerAuthorizationHandler>();
第四步:使用策略
[Authorize(Policy = "OwnsPost")]
[HttpPut("posts/{id}")]
public async Task<IActionResult> UpdatePost(int id, UpdatePostDto dto)
{
// 只有作者本人才能更新
}
🎯 这种模式的强大之处在于: 授权逻辑与业务代码彻底分离 。你想加“仅限工作日编辑”?再写个 WorkdayRequirement 就行了,完全不影响现有代码。
💾 数据库设计与EF Core实战:写出高效的权限查询
再好的架构也离不开扎实的数据层支持。下面我们来看看如何用 MySQL + EF Core 构建高性能的权限存储系统。
表结构设计要点
主表字段规范
| 表 | 关键字段 | 说明 |
|---|---|---|
users |
username (UNIQUE) |
唯一登录名 |
password_hash (CHAR60) |
BCrypt 加密后固定长度 | |
created_at , updated_at |
时间戳便于追踪 | |
roles |
name (UNIQUE) |
角色名唯一 |
permissions |
code (UNIQUE) |
权限码全局唯一 |
module |
分组管理,如 “User”, “Order” |
中间表优化:复合主键 + 索引
CREATE TABLE user_roles (
user_id BIGINT UNSIGNED NOT NULL,
role_id BIGINT UNSIGNED NOT NULL,
assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, role_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
INDEX idx_role_id (role_id)
);
重点:
- ✅ 复合主键防止重复关联
- ✅ 外键级联删除保持一致性
- ✅ idx_role_id 加速“查询某角色下所有用户”
EF Core 映射:Fluent API 更胜一筹
比起 [StringLength(50)] 这种数据注解,我更推荐使用 Fluent API 来配置实体关系,因为它更清晰、更强大。
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("users");
builder.HasKey(u => u.Id);
builder.Property(u => u.Username)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false);
builder.HasIndex(u => u.Username).IsUnique();
builder.Property(u => u.PasswordHash)
.IsRequired()
.HasColumnType("char(60)");
builder.Property(u => u.CreatedAt)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
builder.Property(u => u.UpdatedAt)
.HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP");
}
}
在 DbContext 中统一注册:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}
一行代码搞定所有配置,整洁又专业 ✨
Repository 模式:告别重复的 CRUD
为了避免每个服务都写一遍 FindAsync , AddAsync ,我们可以封装一个通用仓储:
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(long id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate);
Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);
}
public class Repository<T> : IRepository<T> where T : class
{
private readonly AppDbContext _context;
private readonly DbSet<T> _dbSet;
public Repository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(long id) =>
await _dbSet.FindAsync(id);
public async Task AddAsync(T entity)
{
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
}
// 其他方法略...
}
然后在 DI 中注册:
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
从此以后, IRepository<User> 、 IRepository<Role> 随取随用,再也不用手动写一堆相似代码了!
查询优化:避免 N+1 和过度加载
新手常犯的错误是滥用 Include :
// ❌ 错误示范:嵌套太多,性能低下
var users = await context.Users
.Include(u => u.UserRoles)
.ThenInclude(ur => ur.Role)
.ThenInclude(r => r.RolePermissions)
.ThenInclude(rp => rp.Permission)
.ToListAsync();
生成的 SQL 可能长达几百行,执行效率极低。
✅ 正确姿势是使用 Select 投影最小化数据:
var userDtos = await context.Users
.Where(u => u.IsActive)
.Select(u => new UserSummaryDto
{
Id = u.Id,
Username = u.Username,
Email = u.Email,
Roles = u.UserRoles.Select(ur => ur.Role.Name),
Permissions = u.UserRoles
.SelectMany(ur => ur.Role.RolePermissions)
.Select(rp => rp.Permission.Code)
.Distinct()
})
.AsNoTracking()
.ToListAsync();
特点:
- AsNoTracking() :只读查询不用跟踪实体状态,速度快;
- Select 投影:只拿需要的字段,减少网络传输;
- 返回 DTO:避免暴露敏感字段。
🔌 中间件加持:打造全自动权限拦截系统
到了最后一步—— 全局拦截未授权访问 。
ASP.NET Core 的中间件管道就像一条流水线,我们可以插入自己的“安检门”。
自定义权限中间件
public class PermissionMiddleware
{
private readonly RequestDelegate _next;
public PermissionMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context, IPermissionService permissionService)
{
var endpoint = context.GetEndpoint();
if (endpoint?.Metadata.GetMetadata<AllowAnonymousAttribute>() != null)
{
await _next(context);
return;
}
var user = context.User;
if (!user.Identity.IsAuthenticated)
{
context.Response.StatusCode = 401;
return;
}
var method = context.Request.Method;
var path = context.Request.Path.Value!;
// 检查是否有权限
var hasPermission = await permissionService.CheckAccessAsync(user, path, method);
if (!hasPermission)
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("⛔ 您没有访问此资源的权限。");
return;
}
await _next(context);
}
}
注册方式:
app.UseAuthentication();
app.UseAuthorization();
app.UsePermissionMiddleware(); // ← 插在这里!
app.UseRouting();
app.UseEndpoints(e => e.MapControllers());
顺序很重要!必须在 UseAuthorization 之后,确保 context.User 已经填充。
结合路由元数据实现精准控制
你还可以为 Action 添加自定义特性:
[AttributeUsage(AttributeTargets.Method)]
public class PermissionAttribute : Attribute
{
public string Code { get; }
public PermissionAttribute(string code) => Code = code;
}
[Permission("user:delete")]
[HttpDelete("users/{id}")]
public IActionResult DeleteUser(int id) { ... }
中间件读取这些元数据进行判断:
var requiredPerm = endpoint.Metadata.GetMetadata<PermissionAttribute>()?.Code;
if (requiredPerm != null && !user.HasClaim("permission", requiredPerm))
{
context.Response.StatusCode = 403;
return;
}
这样一来,权限规则就和路由绑定在一起,清晰明了。
🛡️ 安全加固:防御常见攻击手段
最后,别忘了给系统穿上“防弹衣”。
✅ 防 SQL 注入:永远不要拼接 SQL
即使用了 EF Core,也不要放松警惕:
// ❌ 危险!
var result = context.Users.FromSqlRaw($"SELECT * FROM users WHERE name = '{name}'").ToList();
// ✅ 安全
var result = context.Users.FromSqlInterpolated($"SELECT * FROM users WHERE name = {name}").ToList();
// 或者显式传参
var param = new MySqlParameter("@name", name);
var result = context.Users.FromSqlRaw("SELECT * FROM users WHERE name = @name", param).ToList();
✅ 防 XSS:输入验证 + 输出编码
使用 FluentValidation 进行强校验:
public class CreateUserValidator : AbstractValidator<CreateUserRequest>
{
RuleFor(x => x.Username).NotEmpty().MaximumLength(50);
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Bio).MaximumLength(500).Matches(@"^[a-zA-Z0-9\s\.\,\!\?]+$"); // 限制特殊字符
}
输出时自动编码:
<!-- Razor 默认会编码 -->
<p>@Model.UserInput</p>
<!-- 显式编码 -->
@Html.Encode(Model.UnsafeContent)
✅ 防 CSRF:开启 Anti-Forgery Token
特别是使用 Cookie 认证时:
// 在视图中
@Html.AntiForgeryToken()
// 在控制器中
[ValidateAntiForgeryToken]
[HttpPost]
public IActionResult TransferMoney(decimal amount) { ... }
🎯 总结:一套真正可用的权限系统长什么样?
经过这一路的深入剖析,我们可以画出这张完整的权限架构图:
graph TD
A[客户端] --> B{认证方式}
B -->|Cookie| C[登录页 → Set-Cookie]
B -->|JWT| D[API Login → 返回Token]
C & D --> E[携带凭证请求API]
E --> F[Authentication Middleware]
F --> G[ClaimsPrincipal]
G --> H[Authorization Pipeline]
H --> I{Policy/Role检查}
I -->|通过| J[执行业务逻辑]
I -->|拒绝| K[返回403]
J --> L[Repository → DB]
L --> M[返回结果]
style F fill:#4CAF50,color:white
style H fill:#2196F3,color:white
style L fill:#FF9800,color:black
这套系统具备以下特质:
✅ 高内聚低耦合 :权限逻辑集中在一处,易于维护
✅ 可扩展性强 :支持 RBAC/ABAC/TBAC 多种模型切换
✅ 高性能 :登录时预加载权限,避免重复查询
✅ 安全性好 :防御 SQLi、XSS、CSRF 常见攻击
✅ 开发效率高 :通过 Policy 和中间件实现自动化拦截
这才是现代 .NET 应用应有的安全水准!
所以,下次当你接到“做个权限系统”的任务时,不要再想着“不就是几张表嘛”,而是停下来问问自己:
“我的设计能不能支撑三年后的业务变化?”
“新增一个角色要改几处代码?”
“我能在一个地方看清所有人的权限吗?”
如果答案都是肯定的,那你才真的掌握了权限设计的艺术。🎨🔐
Keep coding, stay secure! 💻🔒
简介:在企业级应用中,权限管理是保障系统安全的核心环节。本项目基于C#与MySQL构建了一个通用、可复用的权限框架,涵盖身份验证、角色权限控制、数据库操作与系统安全性设计。通过ASP.NET Core的身份验证机制、Entity Framework进行数据访问,结合中间件实现权限校验,并采用安全编码与日志记录提升系统可靠性。项目还包含完整的CRUD逻辑、ViewModel数据传递、单元测试及部署扩展方案,适用于各类需要精细化权限控制的应用场景。
更多推荐


所有评论(0)