本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在企业级应用中,权限管理是保障系统安全的核心环节。本项目基于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! 💻🔒

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:在企业级应用中,权限管理是保障系统安全的核心环节。本项目基于C#与MySQL构建了一个通用、可复用的权限框架,涵盖身份验证、角色权限控制、数据库操作与系统安全性设计。通过ASP.NET Core的身份验证机制、Entity Framework进行数据访问,结合中间件实现权限校验,并采用安全编码与日志记录提升系统可靠性。项目还包含完整的CRUD逻辑、ViewModel数据传递、单元测试及部署扩展方案,适用于各类需要精细化权限控制的应用场景。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐