ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

.NET 6前后端分离权限框架实战:RBAC与JWT鉴权解析

.NET 6前后端分离权限框架实战:RBAC与JWT鉴权解析 简介一套基于.Net6.0构建的前后端分离权限管理及快速开发框架源码面向C#/.NET开发者与中小项目团队用于快速落地带权限体系的管理系统。核心模块覆盖组织机构、角色用户、权限授权、多系统/多应用管理、定时任务、业务单据编码规则及代码生成器整合Asp.Net Core MVC、EF、Dapper、WebAPI、Swagger与Vue等主流技术架构层次清晰、易于横向扩展适合作为中后台项目的起步基座。压缩包共1332个文件大小仅5.8MB主要包含533个C#源码文件、138个Vue组件、180个JavaScript脚本、319个SVG图标以及若干SQL脚本、配置文件和工程文件代码与资源分类存放便于按模块查阅和二次开发。资源内还可看到基于仓储模式的基础封装、通用扩展方法及日志配置等配合数据库脚本与接口文档可快速搭建运行环境。目前已有1277人学习/下载适合希望减少重复造轮子、直接获取可扩展基础框架的.NET开发人员。1. 一个 .NET 6 前后端分离的权限框架到底帮你省了什么收到一个“基于.Net6.0的权限管理及快速开发框架前后端分离”的压缩包时很多人第一反应是“又一个脚手架”随手解压就扔一边。但如果你正带着一个两三人的小组做管理系统、后台平台或者公司在赶交付节点这个包大概率能替你省掉两周的重复劳动。它通常已经内置了登录认证、用户角色、菜单按钮权限、操作日志和代码生成器把前后端分离项目里最不动脑子的部分做成了现成的。适合从零起步的新项目、轻度定制的中后台项目也适合给新人练手理解权限闭环。当然前提是你愿意先花半小时看懂它的目录结构否则会把一个好框架用成黑匣子。2. 先拆架构再动手模块划分、技术选型和目录定位2.1 为什么这套框架押在 .NET 6 而不是老技术栈接触过 .NET Framework 时代老后台的人应该还记得当时部署要装 IIS、配虚拟目录、处理 ApplicationPool 回收一天里有大半时间耗在环境上。.NET 6 不一样它跨平台Kestrel 起来就能跑扔到 Linux 上配合 Nginx 反代也顺理成章。更重要的是.NET 6 是微软的 LTS长期支持版本对“快速开发框架”这个定位来说稳定性比炫技重要得多。这类压缩包往往更愿意押在 LTS 上而不是追 .NET 8、.NET 9 的新特性。框架要做的是把登录、权限、代码生成这套基础设施铺好越稳越好新语法新特性反而可能引入兼容性风险。所以你会发现即使网上已经有更新版本很多团队手里的生产项目依然停在 .NET 6——不是没钱升级是没必要为升级而升级。ASP.NET Core 在 .NET 6 里已经把依赖注入、配置系统、日志、鉴权中间件全部内置第三方容器都不用引。数据访问层常见做法是 EF Core 或 SqlSugar 这类半自动 ORM登录模块则基于官方的 JWT 相关库实现。不管包内具体选哪个你需要抠的参数点基本一致连接串、JWT 密钥、跨域源。带着这个前提去读代码就不会被具体实现带偏。2.2 前后端分离项目里权限模块到底拆在哪几层前后端分离的核心矛盾是前端管“看得到什么”后端管“能不能访问”。如果权限只写死在 Controller 里菜单一变前后端要同时改维护成本直接翻倍。我一般要求团队把这类框架从下往上拆成四层每层只干一件事。层职责你会在这里看到的东西前端展示层路由守卫、菜单渲染、按钮显隐Vue/React 的路由配置、全局指令API 层暴露 RESTful 接口、做鉴权入口Controller、AuthorizationFilter、Swagger应用层登录校验、权限判断、业务编排Service、JWT 签发、缓存处理数据层实体映射、数据库访问DbContext、Repository、Sys_User 等实体这样拆的好处是权限判断放在 API 层和应用层的交界处。前端只依赖接口返回的“当前用户的权限码列表”后端只认当前登录人的令牌。两边的契约就是权限码例如system:user:add。前端拿它控制菜单和按钮后端拿它过滤接口。以后要加新功能不动两边结构只增加一条菜单记录和一组权限码就行。有一种常见情况是压缩包内部不是多工程而是单项目按文件夹分层。只要命名空间清晰这种结构在中小团队里反而更好维护代码生成器输出路径也直观。别为了架构优雅非拆成五个 csproj快速开发框架的定位决定它要的是“打开就能看懂、改起来不绕路”。2.3 拿到压缩包先找这几个关键位置目录检查清单解压之后先别急着dotnet run花十分钟对着这个清单把关键位置摸一遍。# 这类框架解压后的典型布局绕不开这几个位置 src/ Api/ # 后端启动项目入口在 Program.cs Application/ # 业务服务层登录、权限判断都在这 Domain/ # 实体和数据模型 Infrastructure/ # 数据库上下文、仓储实现 web/ src/ router/ # 前端路由和菜单权限强相关 views/ # 页面组件代码生成器往这输出 stores/ # 全局状态存 token 和用户信息 sql/ init.sql # 数据库初始化脚本 docs/ README.md # 部署说明和环境要求这四个地方是最容易出问题的Program.cs看中间件注册顺序appsettings.json看连接串和 JWT 配置sql/看初始化脚本是否幂等前端router/看登录路由和守卫在哪。打开Program.cs或Startup.cs重点看中间件管道的注册顺序。常见惯例是UseRouting → UseCors → UseAuthentication → UseAuthorization → UseEndpoints。顺序错乱的结果很直接不加[AllowAnonymous]的接口全员 401或者 CORS 完全没生效。这类问题很难从报错文本里直接看出来属于权限框架里最典型的“玄学现场”。你不需要背下来但得知道改之前先看一眼这里。3. 跑通最小系统数据库准备、启动参数与第一个登录3.1 数据库准备建库、执行初始化脚本、核对表数量先处理数据库。以 MySQL 为例建库和执行初始化脚本的命令如下。# 以 MySQL 为例先建库再导脚本 mysql -uroot -p --default-character-setutf8mb4 \ -e CREATE DATABASE IF NOT EXISTS permission_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p --default-character-setutf8mb4 \ permission_system sql/init.sql mysql -uroot -p -e USE permission_system; SHOW TABLES;--default-character-setutf8mb4是为了避免中文字段乱码建库时指定一次导数据时再指定一次。排序规则选utf8mb4_general_ci就好它比utf8mb4_unicode_ci快一点对权限表这种短字段足够用。第三句用来核对导入后的表数量和docs/README.md里描述的一致再往后走。这里有个小坑很多初始化脚本不是幂等的第二次执行会报“Table already exists”。我一般拿到脚本后先看一眼开头如果没有DROP TABLE IF EXISTS就在自己库里执行前手动补上或者干脆删库重建。别嫌麻烦这一步能省后面反复折腾的时间。连接串在src/Api/appsettings.json里改MySQL 大概长这样{ ConnectionStrings: { Default: Serverlocalhost;Port3306;Databasepermission_system;Uidroot;Pwdyour_password;Charsetutf8mb4; } }Charsetutf8mb4是 MySQL 连接串里最容易漏的漏了以后查出来的中文仍然是乱码。如果你用的是 SQL Server把整段换成Serverlocalhost;Databasepermission_system;Uidsa;Pwdxxx;TrustServerCertificateTrue;就行。别只看一个文件有的框架有appsettings.Development.json会覆盖主配置搜索整个src目录里的Server或Data Source确认一遍再启动。3.2 同时启动后端和前端最小命令集后端和前端需要两个终端窗口分别启动。# 后端还原依赖并启动监听 5000 端口 cd src/Api dotnet restore dotnet run --urls http://localhost:5000 # 前端装依赖并启动开发服务器 cd web npm install --registryhttps://registry.npmmirror.com npm run dev后端用--urls固定端口是有原因的前后端分离项目里 CORS 白名单和前端代理都按固定端口写换端口就要改配置第一次跑别给自己找事。npm install在国内用 npmmirror 镜像会快很多你要是在公司内网没有镜像源就保持默认源多等一会别 CtrlC 中断会导致 node_modules 残缺。启动后验证两件事浏览器打开http://localhost:5000/swagger能看到接口列表说明后端 OK打开前端 dev server 地址能看到登录页说明前端 OK。如果后端启动报 HTTPS 证书错误检查appsettings.json里 Kestrel 的 HTTPS 配置第一次本地跑直接用http://localhost:5000就行别卡在证书上。3.3 登录接口与初始化账号第一个令牌怎么拿到大多数框架会在初始化脚本里写死一个管理员账号常见是admin密码要么是123456要么是Admin123。不确定就去sql/init.sql里搜INSERT INTO Sys_User密码字段旁边往往有注释。打开 Swagger 找登录接口或者直接命令行测curl -X POST http://localhost:5000/api/auth/login \ -H Content-Type: application/json \ -d {userName:admin,password:123456}响应的 JSON 里会同时给AccessToken和RefreshToken。注意看字段名有些框架叫token和refreshToken有些叫access_token。把这两个值存好后面每个请求都在 Header 加Authorization: Bearer AccessToken。为什么要给两个令牌而不是一个AccessToken短命一般 20 到 120 分钟防泄密RefreshToken长命几天到一周用来续期。如果框架只返回一个 token那你就得把过期时间调长但这会放大泄漏风险后面第五章会专门讲。4. 权限管理的核心实现RBAC 模型、JWT 鉴权与关键参数4.1 权限模型绕不开的 RBAC 五张核心表权限管理这块绝大多数这类框架用的都是 RBAC基于角色的访问控制模型。核心就五张表名字可能带前缀但实体关系是一样的。表作用关键字段Sys_User用户表Id, UserName, PasswordHash, StatusSys_Role角色表Id, RoleName, RoleCodeSys_Menu菜单/按钮表Id, ParentId, PermCode, Type(菜单/按钮)Sys_User_Role用户与角色关联UserId, RoleIdSys_Role_Menu角色与菜单/按钮关联RoleId, MenuId为什么用关联表而不是在用户表里直接存角色 ID因为多对多关系需要可扩展。一个用户有多个角色一个角色对应多个菜单直接塞字段会让后续查询和变更都很难受。注意Sys_Menu这个表它不只是菜单Type字段区分是菜单还是按钮。按钮不渲染成路由而是渲染成操作项比如“新增”“删除”“导出”。每个叶子节点挂一个PermCode这才是前后端唯一的沟通契约。登录成功后服务端把用户所辖的所有权限码收集起来压进 JWT 的 claim 里。后端做接口鉴权时从令牌里看权限不用每次查库代价是改了权限不能立即失效所以需要缓存清理机制这个在第五章展开。4.2 JWT 签发与校验最小可运行的 C# 代码JWT 签发是整个权限链路的起点。一个最小可运行的签发方法长这样using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; public string GenerateToken(User user, Liststring roles, Liststring permissions) { var claims new ListClaim { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName), }; // 角色和权限码都放进 claim供后续鉴权读取 foreach (var role in roles) claims.Add(new Claim(ClaimTypes.Role, role)); foreach (var perm in permissions) claims.Add(new Claim(permission, perm)); var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config[Jwt:SecretKey])); var creds new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token new JwtSecurityToken( issuer: _config[Jwt:Issuer], // 签发方一般填项目名 audience: _config[Jwt:Audience], // 接收方一般填前端应用名 claims: claims, expires: DateTime.Now.AddMinutes(Convert.ToInt32(_config[Jwt:ExpireMinutes])), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); }注意角色 claim 用ClaimTypes.Role而不是自定义字符串这样才能兼容 ASP.NET Core 默认的[Authorize(Roles admin)]特性。权限码 claim 用自定义的permission类型和角色区分开一个是粗粒度角色级一个是细粒度操作级。SecretKey至少 32 个字符因为HmacSha256要求密钥长度够短了算法会直接抛异常。校验侧的核心是一个授权过滤器框架里一般叫PermissionFilter或PermissionAttribute原理都一样public class PermissionFilter : IAuthorizationFilter { private readonly string _permissionName; public PermissionFilter(string permissionName) { _permissionName permissionName; } public void OnAuthorization(AuthorizationFilterContext context) { var user context.HttpContext.User; if (user?.Identity?.IsAuthenticated ! true) { context.Result new UnauthorizedResult(); return; } var permissions user.FindAll(permission).Select(c c.Value); if (!permissions.Contains(_permissionName)) { context.Result new ForbidResult(); return; } } } // 用法在 Controller 或 Action 上标注 [PermissionFilter(system:user:add)] [HttpPost] public IActionResult AddUser(UserDto dto) { /* ... */ }这段代码里最关键的是user.FindAll(permission)它读的是登录时 JWT 里塞的权限码。权限码必须和前端菜单表里的PermCode完全一致差一个冒号、差一个大小写都等于没有权限。所以我在项目里规定权限码统一小写英文加冒号分隔比如system:user:add、report:export禁止用中文和空格。实际框架可能用全局过滤器注册的方式而不是在 Action 上逐个标注这个不影响理解。注册时在Program.cs里加一行builder.Services.AddControllers(options options.Filters.Add(typeof(PermissionAttribute)))就行。有一个容易忽略的点如果接口只是角色级权限就够直接用默认[Authorize(Roles admin)]别硬套权限码过滤器否则每次加角色都要改代码。4.3 前端如何配合路由守卫与按钮级权限指令后端控制接口前端控制交互体验。没有前端配合的权限框架是不完整的路由守卫管“能不能进这个页面”按钮指令管“能不能看到这个按钮”。// 路由守卫没有令牌一律去登录页 router.beforeEach((to, from, next) { const token localStorage.getItem(access_token); if (!token) { next({ path: /login, query: { redirect: to.fullPath } }); return; } const needPerm to.meta.permission; if (needPerm !store.state.user.permissions.includes(needPerm)) { next(/403); return; } next(); });路由配置里要给需要权限的页面加meta: { permission: system:user }这个字符串和菜单表的PermCode一致。只做路由守卫不够因为用户可能直接输入 URL 访问真正的校验在后端PermissionFilter兜底。按钮级权限用自定义指令更干净// 按钮级权限v-permissionsystem:user:add Vue.directive(permission, { mounted(el, binding) { const permissions store.state.user.permissions; if (!permissions.includes(binding.value)) { el.parentNode?.removeChild(el); } } });前端隐藏按钮只是体验优化不是安全手段。即使前端不拦截后端的PermissionFilter照样会拒绝请求。我在项目里反复跟团队强调前端是面子后端是里子两边都要做但千万不要以为前端隐藏了就万事大吉。5. 落地避坑从解压到上线的 5 个常见问题与排查5.1 数据库初始化脚本重复执行报重现象第二次执行init.sql报Table xxx already exists或者中途脚本中断再执行时一直报错。原因脚本默认只在全新数据库上执行一次没有DROP TABLE IF EXISTS做幂等处理。一些框架提供的脚本是直接从开发库导出的甚至带着业务测试数据。解决执行前先看脚本头部如果没有幂等处理手动在每张表前面补DROP TABLE IF EXISTS或者干脆把库删了重建。我一般保留一份自己的幂等版本以后换环境直接跑不慌。5.2 前端能起登录接口请求被跨域拦截现象前端页面正常打开但请求登录接口时浏览器控制台报Cross-Origin Request Blocked网络面板显示状态 504 或直接失败。原因前后端分离项目里前端源比如http://localhost:8080不在后端 CORS 白名单里。还有一种是中间件注册顺序问题UseCors放在了UseAuthorization后面导致 CORS 头没有正常输出。解决改appsettings.json里的Cors:Origins把前端 dev server 的地址加进去然后确认Program.cs里app.UseCors()在app.UseAuthentication()之前。如果你用 Vite 开发也可以配置前端代理把/api请求转发到后端从根上绕开跨域但部署到生产环境时后端 CORS 依然要配对。5.3 用着用着突然 401刷新就回登录页现象登录后操作正常过了一段时间任意接口突然 401刷新页面直接被踢回登录页。原因AccessToken过期了但前端没有做自动刷新。很多框架样板代码只存一个 token过期就只能重新登录。解决前端在 axios 响应拦截器里统一捕获 401然后调用刷新令牌接口换新 token。刷新成功就重放原请求失败才跳登录页。注意刷新令牌接口本身要排除在拦截器逻辑外不然会形成死循环。具体实现时access_token和refresh_token要分开存储刷新后把两个都更新掉。5.4 权限码改了接口依然 403现象给角色勾选了某个菜单权限也保存了但对应接口仍然返回 403 Forbidden。原因JWT 里的权限 claim 是登录时生成的旧的令牌没过期新的权限码根本不在里面。另一种可能是权限码大小写或冒号不一致比如后端要求system:user:add前端传的是system:user:Add。解决开发阶段把 JWTExpireMinutes调短比如 15 分钟改完权限重新登录就能生效。权限码规范要定死全小写加冒号分隔前端菜单和后端特性标注都用同一份常量。如果框架开了 Redis 缓存用户权限改完权限还要清理对应用户的缓存 key不然新权限要等缓存过期才生效。5.5 Swagger 能通但前端页面登录失败现象后端接口在 Swagger 里测试返回正常前端表单提交却报错但后端日志里没有异常。原因前端请求体的字段名和后端 DTO 不一致。比如后端定义的是userName前端传的是username或者后端用的是Password前端传的是pwd。Swagger 里能看到正确的字段名但前端代码没跟着改。解决打开浏览器 Network 面板看请求 Payload 和后端返回的响应文本和后端 DTO 的JsonProperty逐一对照。这类问题在前后端分离项目里是最频繁的翻车点别一上来就怀疑 JWT 或鉴权配置先从最基础的字段名查起。6. 把快速开发落到实处代码生成器的使用边界与验证方法6.1 代码生成器生成的样板三个必改点这类框架宣称的“快速开发”核心就是代码生成器。典型操作流程是后端配置好数据库连接生成器读取表结构一键生成实体类、Service、Controller 和 Vue 页面重启后端后 Swagger 里多出一组接口前端也能看到对应页面。生成出来的东西只能算“能跑的样板”距离“能上线”还差三个必改点。第一实体映射在表结构变更后要重新生成不要手动改生成的文件用 partial 类扩展业务逻辑。第二Service 里只有最基础的 CRUD真正的业务校验比如编号唯一、状态流转、审批逻辑要写在 partial 类的扩展方法里。第三生成的 Controller 默认只带登录鉴权权限码是占位符要改成真实的PermissionFilter(system:xxx:xxx)。6.2 怎么验证这套框架值不值得你用一个有效的验证方法拿一张三五十字段的真实业务表用代码生成器跑一遍看生成出来的前端列表页、表单页、后端接口十分钟内能不能改完就提测。如果答案是能说明框架能扛起“快速开发”这面旗如果模板高度绑定示例业务改起来像在解耦一个巨型类那就要认真评估改造代价。这类框架的价值曲线落在“CRUD 密集、流程简单”的中后台项目上效果最好到复杂业务里它只是个起点。我拿这类框架做过两个内部系统最大的教训是别什么都想要。框架省下的是体力省不下的是你对业务表和权限粒度的设计。最稳的做法是先把权限码命名规范定死再让团队按规范用生成器补页面这样接手的人不会一头雾水。希望帮到你。本文还有配套的精品资源点击获取
返回列表