You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于.NET Core的JWT认证API与MVC客户端令牌校验优化咨询

嘿,这个重复校验的问题确实挺磨人的,我给你几个ASP.NET Core MVC里常用的优化方案,帮你把这些冗余代码彻底干掉:

方案一:自定义Action过滤器(最直接的封装方式)

把令牌校验逻辑封装成一个Action过滤器,然后全局注册或者给需要的控制器/方法添加特性,就能自动触发校验了。

步骤1:编写过滤器类

public class CheckJwtTokenFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // 排除登录控制器,避免循环跳转
        if (context.Controller is LoginController)
        {
            base.OnActionExecuting(context);
            return;
        }

        var jwtToken = context.HttpContext.Session.GetString("JwtToken");
        var returnUrl = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;

        if (string.IsNullOrEmpty(jwtToken))
        {
            // 令牌为空,跳转到登录页并携带原页面地址
            context.Result = new RedirectToActionResult("Login", "Account", new { returnUrl });
            return;
        }

        // 可选:额外校验令牌是否过期/有效
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var jwtSecurityToken = tokenHandler.ReadJwtToken(jwtToken);
            if (jwtSecurityToken.ValidTo < DateTime.UtcNow)
            {
                context.Result = new RedirectToActionResult("Login", "Account", new { returnUrl });
            }
        }
        catch
        {
            // 令牌解析失败,直接跳登录
            context.Result = new RedirectToActionResult("Login", "Account", new { returnUrl });
        }

        base.OnActionExecuting(context);
    }
}

步骤2:注册过滤器

  • 全局生效:所有控制器自动应用校验,在Program.cs里添加:
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<CheckJwtTokenFilter>();
});
  • 局部生效:只给特定控制器/方法加特性:
[CheckJwtToken]
public class DashboardController : Controller
{
    // 控制器逻辑...
}
方案二:自定义授权策略(贴合ASP.NET Core授权体系)

如果想更贴合框架的授权机制,可以把令牌校验做成一个自定义授权策略,扩展性更强。

步骤1:定义授权要求和处理器

// 空的授权要求,仅作为标记
public class JwtTokenRequirement : IAuthorizationRequirement { }

// 授权处理器,实现校验逻辑
public class JwtTokenHandler : AuthorizationHandler<JwtTokenRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, JwtTokenRequirement requirement)
    {
        var httpContext = context.Resource as HttpContext;
        if (httpContext == null)
        {
            context.Fail();
            return Task.CompletedTask;
        }

        // 排除登录路径
        if (httpContext.Request.Path.StartsWithSegments("/Account/Login"))
        {
            context.Succeed(requirement);
            return Task.CompletedTask;
        }

        var jwtToken = httpContext.Session.GetString("JwtToken");
        var returnUrl = Uri.EscapeDataString(httpContext.Request.Path + httpContext.Request.QueryString);

        if (!string.IsNullOrEmpty(jwtToken))
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var jwtSecurityToken = tokenHandler.ReadJwtToken(jwtToken);
                if (jwtSecurityToken.ValidTo >= DateTime.UtcNow)
                {
                    context.Succeed(requirement);
                    return Task.CompletedTask;
                }
            }
            catch { }
        }

        // 令牌无效/不存在,跳转登录页
        httpContext.Response.Redirect($"/Account/Login?returnUrl={returnUrl}");
        context.Succeed(requirement); // 手动标记成功,避免后续授权拦截
        return Task.CompletedTask;
    }
}

步骤2:注册授权策略

在Program.cs里添加:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireValidJwtToken", policy =>
    {
        policy.Requirements.Add(new JwtTokenRequirement());
    });
});

// 注册授权处理器
builder.Services.AddScoped<IAuthorizationHandler, JwtTokenHandler>();

步骤3:在控制器上使用

[Authorize(Policy = "RequireValidJwtToken")]
public class OrdersController : Controller
{
    // 控制器逻辑...
}
方案三:基类控制器(简单直接的继承方式)

如果不想用过滤器或授权策略,也可以创建一个基类控制器,把校验逻辑写在基类的OnActionExecuting方法里,所有需要校验的控制器都继承这个基类。

public class BaseAuthenticatedController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!(this is LoginController))
        {
            var jwtToken = HttpContext.Session.GetString("JwtToken");
            var returnUrl = Request.Path + Request.QueryString;

            if (string.IsNullOrEmpty(jwtToken))
            {
                context.Result = RedirectToAction("Login", "Account", new { returnUrl });
                return;
            }

            // 可选:校验令牌有效性
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var jwtSecurityToken = tokenHandler.ReadJwtToken(jwtToken);
                if (jwtSecurityToken.ValidTo < DateTime.UtcNow)
                {
                    context.Result = RedirectToAction("Login", "Account", new { returnUrl });
                    return;
                }
            }
            catch
            {
                context.Result = RedirectToAction("Login", "Account", new { returnUrl });
                return;
            }
        }

        base.OnActionExecuting(context);
    }
}

然后其他控制器继承基类:

public class ProfileController : BaseAuthenticatedController
{
    // 控制器逻辑...
}

额外小建议

关于令牌存储:你用Session是可行的,但生产环境建议配置分布式缓存(比如Redis)来存储Session,避免单机部署时的Session丢失问题。另外也可以考虑把JWT存在HttpOnly的Cookie里,安全性更高,也更容易和ASP.NET Core的认证系统集成。

内容的提问来源于stack exchange,提问作者ironcurtain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:20:04