18、MVC中的认证与授权:讲解如何在MVC架构中实现用户认证和权限控制。
认证和授权,这两个词听起来很像,但干的活完全不同。
认证是「你是谁?」——验证用户身份。授权是「你能干什么?」——决定用户权限。
在MVC架构里,这两件事通常放在一起处理,但逻辑上必须分开。我见过不少项目把登录和权限混在一个Controller里,后期维护起来简直是一场灾难。你想想看,一个用户改了角色,难道还要去改登录逻辑吗?
18.1 认证:从Cookie到Token的演进
早期MVC项目大多用Session-Cookie方式。用户登录后,服务端存一个Session,客户端存一个Cookie。每次请求,服务端根据Cookie里的Session ID去查用户信息。
这种方式在单体应用里挺好用。但一旦上了分布式,Session同步就成了噩梦。我记得有个项目,上线后用户频繁掉线,查了半天,原来是负载均衡把请求分发到了不同的服务器,Session没同步过去。
后来大家开始用Token,尤其是JWT(JSON Web Token)。服务端不再存Session,而是把用户信息加密后发给客户端。客户端每次请求带上Token,服务端解密验证即可。
核心区别:
- Session方式:服务端有状态,需要存储
- Token方式:服务端无状态,只需验证签名
在MVC里,我习惯把认证逻辑放在一个独立的Filter或Middleware里。Controller只关心业务,不关心「这个请求是谁发的」。
// 伪代码:MVC中的认证中间件
public class AuthMiddleware
{
public void OnActionExecuting(ActionExecutingContext context)
{
var token = context.HttpContext.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(token))
{
context.Result = new UnauthorizedResult();
return;
}
// 验证Token,解析用户信息
var user = JwtHelper.ValidateToken(token);
if (user == null)
{
context.Result = new ForbiddenResult();
return;
}
// 把用户信息塞进HttpContext,供后续使用
context.HttpContext.Items["CurrentUser"] = user;
}
}
嗯,这里要注意:Token一定要设置过期时间。我见过有人把Token有效期设成一年,结果用户离职了还能访问系统。说白了,Token过期了,让用户重新登录,这是安全底线。
18.2 授权:基于角色还是基于资源?
认证通过后,接下来就是授权。授权有两种主流方式:基于角色(RBAC)和基于资源(ABAC)。
基于角色(RBAC):给用户分配角色,角色关联权限。比如「管理员」角色可以删除文章,「编辑」角色只能修改。
基于资源(ABAC):直接判断用户对某个资源是否有操作权限。比如「张三只能修改自己创建的文章」。
我个人更推荐RBAC作为基础,ABAC作为补充。为什么呢?因为RBAC简单、直观,适合大部分场景。ABAC虽然灵活,但规则复杂,容易失控。
我的经验:
在MVC里,授权逻辑最好放在Controller的Action上,用Attribute标记。这样一眼就能看出哪些接口需要什么权限。
// 伪代码:基于角色的授权
[Authorize(Roles = "Admin")]
public IActionResult DeleteUser(int userId)
{
// 只有Admin角色才能删除用户
return Ok();
}
// 伪代码:基于资源的授权
[Authorize]
public IActionResult EditArticle(int articleId)
{
var article = _articleService.GetById(articleId);
var currentUser = HttpContext.Items["CurrentUser"] as User;
if (article.CreatedBy != currentUser.Id)
{
return Forbid(); // 不是自己的文章,禁止编辑
}
return View(article);
}
为什么会这样设计?因为基于角色的授权可以统一处理,而基于资源的授权往往需要查数据库。把两者分开,代码更清晰。
18.3 权限控制的常见坑
我在项目中遇到过不少权限控制的坑,挑几个典型的说说。
避坑指南:
- 前端控制权限?别信! 我曾经见过一个项目,只在Vue路由里做了权限判断,后端接口完全开放。结果有人直接调接口删了全站数据。记住:权限必须在后端做,前端只是辅助。
- 权限缓存要谨慎。 用户改了角色,缓存没刷新,结果还能访问旧权限的接口。我建议每次请求都从数据库或缓存中重新获取用户权限,不要依赖Session里的旧数据。
- 不要用硬编码。 比如 if (user.Role == "Admin") 这种写法,一旦角色名变了,你得改代码。用枚举或常量代替。
18.4 一张图看懂认证与授权流程
下面这张SVG图,展示了MVC架构中认证与授权的完整流程。从用户请求到返回结果,每一步都清晰可见。
18.5 实战建议:如何设计权限系统
如果你要从零开始设计一个MVC项目的权限系统,我建议按以下步骤来:
- 先做认证,再做授权。 没有认证,授权无从谈起。先搞定登录、Token发放、Token验证。
- 定义角色和权限。 列出系统中有哪些角色,每个角色能做什么。用表格记录下来,方便后续扩展。
- 把权限挂到Controller的Action上。 用Attribute标记,一目了然。
- 写一个权限检查的Helper。 比如 CheckPermission(userId, resourceId, action),方便在业务代码里调用。
- 别忘了日志。 每次权限被拒绝,记录一下。方便排查问题,也方便安全审计。
一个简单的权限表设计:
| 角色 | 权限 | 说明 |
|---|---|---|
| Admin | 所有操作 | 系统管理员,不受限制 |
| Editor | 创建、修改、删除自己的文章 | 不能管理用户 |
| Viewer | 只能查看 | 只读权限 |
嗯,最后说一句:权限系统不是一锤子买卖。业务变了,权限就得跟着变。保持代码的灵活性,比一开始设计得「完美」更重要。
公众号:蓝海资料掘金营,微信deep3321