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架构中认证与授权的完整流程。从用户请求到返回结果,每一步都清晰可见。

MVC认证与授权流程 用户发起请求 认证中间件(验证Token) 通过? 返回401未授权 授权中间件(检查权限) 有权限? 返回403禁止访问 Controller执行业务逻辑 认证相关 认证判断 授权相关

18.5 实战建议:如何设计权限系统

如果你要从零开始设计一个MVC项目的权限系统,我建议按以下步骤来:

  1. 先做认证,再做授权。 没有认证,授权无从谈起。先搞定登录、Token发放、Token验证。
  2. 定义角色和权限。 列出系统中有哪些角色,每个角色能做什么。用表格记录下来,方便后续扩展。
  3. 把权限挂到Controller的Action上。 用Attribute标记,一目了然。
  4. 写一个权限检查的Helper。 比如 CheckPermission(userId, resourceId, action),方便在业务代码里调用。
  5. 别忘了日志。 每次权限被拒绝,记录一下。方便排查问题,也方便安全审计。

一个简单的权限表设计:

角色 权限 说明
Admin 所有操作 系统管理员,不受限制
Editor 创建、修改、删除自己的文章 不能管理用户
Viewer 只能查看 只读权限

嗯,最后说一句:权限系统不是一锤子买卖。业务变了,权限就得跟着变。保持代码的灵活性,比一开始设计得「完美」更重要。


公众号:蓝海资料掘金营,微信deep3321