65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET CORE 使用 JWT 进行令牌身份验证和授权(不使用 Cookie)– 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (22投票s)

2019年10月7日

CPOL

14分钟阅读

viewsIcon

79289

downloadIcon

2473

本文介绍了如何在 ASP.NET CORE 中使用 JWT 实现令牌身份验证和授权。

目录

引言

第一部分介绍了如何使用登录凭据对用户进行身份验证。在第二部分中,我们将介绍如何为用户实现授权。换句话说,授予用户使用应用程序的某些部分或全部部分的权限。用户权限(授权)在 3 个级别处理:控制器、操作方法和视图页面。为此,我们将编写自定义特性和一些扩展方法。第二部分包含一个独立的 LoginDemo.sln 项目,其中实现了下面所有关于授权的主题。

用户角色

让我们设置三种不同的角色

  1. 总监
  2. 主管
  3. 分析师

创建一个 staticRoles.cs 来定义用户角色。这些是参数值,将传递到自定义特性。

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Threading.Tasks;

        namespace LoginDemo.CustomAttributes
        {
            public static class Roles
            {
                public const string DIRECTOR = "DIRECTOR";
                public const string SUPERVISOR = "SUPERVISOR";
                public const string ANALYST = "ANALYST";
            }
        }
    

让我们向 User 集合添加“WRITE”权限。

//Using hard coded collection list as Data Store for demo purpose. 
//In reality, User data comes from Database or some other Data Source - JRozario
private List UserList = new List
{
    new User { USERID = "jsmith@email.com", PASSWORD = "test", 
    EMAILID = "jsmith@email.com", FIRST_NAME = "John", 
    LAST_NAME = "Smith", PHONE = "356-735-2748", 
    ACCESS_LEVEL = Roles.DIRECTOR.ToString(), WRITE_ACCESS = "WRITE_ACCESS" },
    new User { USERID = "srob@email.com", PASSWORD = "test", 
    FIRST_NAME = "Steve", LAST_NAME = "Rob", 
    EMAILID = "srob@email.com", PHONE = "567-479-8537", 
    ACCESS_LEVEL = Roles.SUPERVISOR.ToString(), WRITE_ACCESS = "WRITE_ACCESS" },
    new User { USERID = "dwill@email.com", PASSWORD = "test", 
    FIRST_NAME = "DJ", LAST_NAME = "Will", 
    EMAILID = "dwill@email.com", PHONE = "599-306-6010", 
    ACCESS_LEVEL = Roles.ANALYST.ToString(), WRITE_ACCESS = "WRITE_ACCESS" },
    new User { USERID = "JBlack@email.com", PASSWORD = "test", 
    FIRST_NAME = "Joe", LAST_NAME = "Black", 
    EMAILID = "JBlack@email.com", PHONE = "764-460-8610", 
    ACCESS_LEVEL = Roles.ANALYST.ToString(), WRITE_ACCESS = "" }
};   

如何创建自定义授权特性?

用户权限通过特性进行处理,这些特性可用于装饰控制器和操作方法。当一个特性被装饰在控制器顶部时,它将应用于该控制器中的所有操作方法。同样,每个操作方法也可以被装饰。

为了创建自定义授权特性,我们将使用 IAuthorizationFilter 接口和 TypeFilterAttributeIAuthorizationFilter 通过 OnAuthorization 方法实现实际的授权过滤。TypeFilterAttribute 用于依赖注入。更多信息,请参阅 Microsoft 文档。

让我们编写自定义授权特性

        using Microsoft.AspNetCore.Mvc;
        using Microsoft.AspNetCore.Mvc.Filters;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Security.Claims;
        using System.Threading.Tasks;

        namespace LoginDemo.CustomAttributes
        {
            public class AuthorizeAttribute : TypeFilterAttribute
            {
                public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
                {
                    Arguments = new object[] { claim };
                }
            }

            public class AuthorizeFilter : IAuthorizationFilter
            {
                readonly string[] _claim;

                public AuthorizeFilter(params string[] claim)
                {
                    _claim = claim;
                }

                public void OnAuthorization(AuthorizationFilterContext context)
                {
                    var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
                    var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;
            
                    if (IsAuthenticated)
                    {
                        bool flagClaim = false;
                        foreach (var item in _claim)
                        {
                            if (context.HttpContext.User.HasClaim(item, item))
                                flagClaim = true;
                        }
                        if (!flagClaim)
                            context.Result = new RedirectResult("~/Dashboard/NoPermission");
                    }
                    else
                    {
                        context.Result = new RedirectResult("~/Home/Index");
                    }
                    return;
                }
            }
        }    

如您所见,上面的代码中有两个类:AuthorizeAttributeAuthorizeFilter

AuthorizeAttribute

        public class AuthorizeAttribute : TypeFilterAttribute
        {
            public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
            {
                Arguments = new object[] { claim };
            }
        }    

此类实现了 TypeFilterAttribute,它由依赖项容器用于创建 AuthorizeFilter 的对象。AuthorizeAttribute 构造函数接受 string 数组作为参数。由于它是一个数组,我们可以传递用逗号(,)分隔的角色值 [例如:“Director”、“Supervisor”、“Analyst”]。

AuthorizeFilter

        public class AuthorizeFilter : IAuthorizationFilter    

AuthorizeFilter 类实现了 IAuthroizationFilter 接口,其中包含 OnAuthorization 方法。

        public AuthorizeFilter(params string[] claim)
        {
            _claim = claim;
        }    

AuthorizeFilter 构造函数接受 string 数组作为参数,该参数从 AuthorizeAttribute 传递。

        public void OnAuthorization(AuthorizationFilterContext context)    

OnAuthorization 方法接收 AuthorizationFilterContext。从这个“context”对象,我们可以获取 HttpContext 和包含声明的用户身份。

        var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
        var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;    

从“context”对象,我们可以得知用户是否已进行身份验证。使用同一个“context”对象,我们可以获取用户声明的身份集合。

        if (IsAuthenticated)
            {
                bool flagClaim = false;
                foreach (var item in _claim)
                {
                    if (context.HttpContext.User.HasClaim(item, item))
                        flagClaim = true;
                }
                if (!flagClaim)
                    context.Result = new RedirectResult("~/Dashboard/NoPermission");
            }
            else
            {
                context.Result = new RedirectResult("~/Home/Index");
            }
            return;    

在此块中,我们检查用户是否已进行身份验证。如果没有,我们将用户重定向到登录页面。然后,我们循环遍历“claim”数组集合(这个数组集合将是用户角色,我们会将其作为参数从控制器传递,例如:Authorize[new string[] “Director”, “Supervisor”])。

在这里,context.HttpContext.User.Identity 对象在声明对象集合中包含用户权限。在第一部分中,我们创建了具有用户声明的令牌并将其加载到 context.HttpContext.User 身份对象中。请参阅第一部分中的“Middleware app.UseAuthentication()”。

在循环内部,我们使用“HasClaim”方法检查参数值是否在用户身份声明集合中。在第一部分中,我们解释了如何创建用户声明身份对象集合并将其分配给 HttpContext。正是从同一个 HttpContext 中,我们获取用户声明并与传入的参数值进行比较。

如果参数值在声明集合中找到,则用户已授权,允许通过 HTTP 请求。否则,我们将用户重定向到一个显示“您无权访问”的页面。在本文中,重定向到“无权限”页面。

如何在控制器和操作方法级别设置权限?

既然我们已经创建了自定义授权特性,现在是时候在控制器中使用它了。

        public class DashboardController : Controller
        {
            [Authorize(Roles.DIRECTOR)]
            public IActionResult DirectorPage()
            {
                return View("DirectorPage");
            }

            [Authorize(Roles.SUPERVISOR)]
            public IActionResult SupervisorPage()
            {
                ViewBag.Message = "Permission controlled through action Attribute";
                return View("SupervisorPage");
            }

            [Authorize(Roles.ANALYST)]
            public IActionResult AnalystPage()
            {
                return View("AnalystPage");
            }
        }    

操作方法使用 [Authorize] 特性进行装饰。我们正在传递用户角色作为参数。DirectorSupervisorAnalyst 各自拥有独立的操作方法。

        [Authorize(Roles.DIRECTOR)]
        public IActionResult DirectorPage()
        {
            return View("DirectorPage");
        }    

在此“DirectorPage”操作方法中,我们设置了 Authorize 特性,值为 Roles.DIRECTOR。这意味着只有拥有 Director 角色的用户才能查看该页面。如果 SupervisorAnalyst 尝试访问该页面,他们将从 AuthorizeFilter 类被重定向到“无权限”页面。

Roles.DIRECTOR 是我们传递给 AuthorizeFilter 类的参数。Authorize 过滤器类接收“Roles.DIRECTOR”参数值,并检查该值是否在用户声明身份集合中找到。如果找到,则用户被允许通过操作方法,并允许查看“DirectorPage”。如果登录用户是 Supervisor 或 Analyst,则他们无法看到“DirectorPage”。

如果我们希望允许多个用户访问操作方法,则可以像下面这样使用逗号(,)分隔的值来传递参数值。

        [Authorize(Roles.DIRECTOR, Roles.SUPERVISOR, Roles.ANALYST)]
        public IActionResult AllRoles()
        {
            return View();
        }    

要在控制器级别设置权限,可以像下面这样设置。当 Authorize 特性设置在控制器级别时,该权限将应用于该控制器中的所有操作方法。

        namespace LoginDemo.Controllers
        {
	        [Authorize(Roles.DIRECTOR, Roles.SUPERVISOR, Roles.ANALYST)]    
            public class YourController : Controller
            {
                public IActionResult Action1()
                {
                    return View();
                }

                public IActionResult Action2()
                {
                    return View();
                }

                public IActionResult Action3()
                {
                    return View();
                }
            }
        }    

检查用户权限的扩展方法

过滤器特性只能在控制器中使用。但在某些情况下,我们需要检查用户权限,而不能仅依赖于过滤器特性。如果我们想在操作方法内的“If”条件中检查用户权限怎么办?如果我们想在视图页面中检查用户权限怎么办?在这种情况下,我们不能使用过滤器特性。因此,我们将使用扩展方法。一个用于控制器操作方法,一个用于视图页面。

让我们创建一个 staticPermissionExtension.cs 来为控制器提供扩展方法。

        namespace LoginDemo.CustomAttributes
        {
            public static class PermissionExtension
            {
                public static bool HavePermission(this Controller c, string claimValue)
                {
                    var user = c.HttpContext.User as ClaimsPrincipal;
                    bool havePer = user.HasClaim(claimValue, claimValue);
                    return havePer;
                }
	            public static bool HavePermission(this IIdentity claims, string claimValue)
                {
                    var userClaims = claims as ClaimsIdentity;
                    bool havePer = userClaims.HasClaim(claimValue, claimValue);
                    return havePer;
                }
            }
        }    

为方便控制器和视图页面使用而实现的扩展方法。这些方法以声明值为参数,并检查该声明是否存在于 HttpContext.User 声明对象集合中。要在控制器中调用该方法,我们使用“this Controller”作为第一个参数,使其成为一个扩展方法。对于视图页面的使用,它是“this IIdentity”。

这些扩展方法是为了方便并将事物集中管理。您也可以直接在操作方法和视图页面中使用“User.HasClaims()”。

如何在操作方法中检查权限(内联代码)?

假设您有一个操作方法,并希望将其用于多个角色。但您想根据角色执行不同的逻辑或从不同的源获取数据,或者做一些特定的事情。在这种情况下,我们需要在操作方法中了解当前登录的用户的角色。

在下面的操作方法中,所有登录用户都有权限。我们在 action 方法中检查用户角色。在 action 方法内部,我希望根据角色返回不同的视图。对于“Supervisor”,它返回“SupervisorPage”视图;对于“Analyst”,它返回“AnalystPage”视图。这仅为例,它可用于实现不同的逻辑、从不同的源获取数据等。

这是一个简单的 if 条件,带有扩展方法 HavePermission(),用于控制权限。

        public IActionResult SupervisorAnalystPage()
        {
            ViewBag.Message = "Permission controlled inside action method";
            if (this.HavePermission(Roles.SUPERVISOR))
                return View("SupervisorPage");
            
            if (this.HavePermission(Roles.ANALYST))
                return View("AnalystPage");
            
            return new RedirectResult("~/Dashboard/NoPermission");
        }    

上面,我们创建了 HavePermission() 扩展方法。使用该扩展方法,我们通过简单的“if”条件来控制权限。扩展方法检查 HttpContext 用户声明对象集合中的用户角色。

        this.HavePermission(Roles.SUPERVISOR)
        this.HavePermission(Roles.ANALYST))    

如何在视图页面中控制用户权限?

如果我们想根据用户角色/权限在视图页面中控制某些内容怎么办?如何为 Director、Supervisor 和 Analyst 显示和隐藏不同的顶部“菜单”项?为此,我们需要在视图页面级别了解用户权限。我们希望实现类似这样的功能:如果用户是 Director,则显示/隐藏一个菜单、按钮等。

假设我们想根据用户角色/权限禁用和启用“保存”按钮。这可以通过上面的 HavePermission() 扩展方法来完成。在此示例中,对于没有 WRITE 访问权限的用户,按钮被禁用。在此视图页面中,有一个表单可以接收用户输入。只有具有 WRITE 访问权限的用户才能保存此表单。对于其他人,“保存”按钮被禁用,他们无法保存表单数据。

            @if (User.Identity.HavePermission("WRITE_ACCESS") == true)
            {
                <button type="button"</button<>
            }
            else
            {
                <button type="button" disabled="disabled"<</button>
            }    

上面,我们创建了两个 HavePermission() 扩展方法,一个用于控制器,一个用于视图页面。在这里,HavePermission() 检查 HttpContext 用户声明对象集合中的“WRITE_ACCESS”声明。如果为 true,则启用保存按钮,否则禁用。这样,我们可以控制用户视图页面内容的“显示什么?”和“如何显示?”基于他们的角色/权限。

        @if (User.Identity.HavePermission("WRITE_ACCESS") == true)
    

以 Analyst JBlack@email.com(没有 WRITE 权限)登录演示项目。在登陆页面,点击“查看页面级别权限如何工作”。

您可以看到“保存”按钮被禁用,用户无法保存表单数据。

对于其他用户,他们拥有 WRITE 权限,“保存”按钮已启用,可以保存表单数据。

如何处理未经授权的请求?

如何限制未登录应用程序但直接尝试通过提供页面 URL 来访问页面的用户?假设应用程序 URL 是 http://www.yourapplication.com,它将用户带到登录页面。但是,有一个“Supervisor”页面,用户知道该页面的 URL。如果“Supervisor”页面的 URL 是 http://www.yourapplication.com/supervisor,并且用户在未登录的情况下直接在浏览器中输入该 URL,则属于未经授权的访问。让我们看看如何处理这种情况。

我们将实现一个过滤器特性(类似于 AuthorizeFilter)。但我们将它用于控制器级别。创建一个类 UnAuthorized.cs。实现 IAuthorizationFilter 接口并在 OnAuthroization() 方法中处理请求。我们无需为此过滤器传递任何参数值。此过滤器仅检查身份验证。

        namespace LoginDemo.CustomAttributes
        {
            public class UnAuthorizedAttribute : TypeFilterAttribute
            {
                public UnAuthorizedAttribute() : base(typeof(UnauthorizedFilter))
                {
                    //Empty constructor
                }
            }
            public class UnauthorizedFilter : IAuthorizationFilter
            {
                public void OnAuthorization(AuthorizationFilterContext context)
                {
                    bool IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
                    if (!IsAuthenticated)
                    {
                        context.Result = new RedirectResult("~/Home/Index");
                    }
                }
            }
        }    

AuthorizationFilterContext 获取用户身份对象,从中我们可以发现用户是否已进行身份验证。

        bool IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
    

如果用户未进行身份验证或未登录,则将用户重定向到登录页面。

        if (!IsAuthenticated)
        {
            context.Result = new RedirectResult("~/Home/Index");
        }    

您可以通过简单地将 [UnAuthorized] 特性添加到顶部来将其用于控制器级别,这将应用于该控制器中的所有操作方法。Authorize 特性也检查 IsAuthenticated,所以有人可能会问 [UnAuthorized] 特性的用途是什么。可能存在一种情况,权限未受控制,这意味着所有用户都可以拥有操作方法的权限,并且该操作方法没有 [Authorize] 特性。在下面的控制器中,SupervisorAnalystPage() 操作方法没有 [Authorize] 特性。任何知道 URL 的用户都可以尝试访问它。使用 [UnAuthorized] 特性的另一个原因是为了处理 Ajax 调用,我们将在下一个主题中看到。

        namespace LoginDemo.Controllers
        {
            [UnAuthorized]
            public class DashboardController : Controller
            {
                [Authorize(Roles.DIRECTOR)]
                public IActionResult DirectorPage()
                {
                    return View("DirectorPage");
                }

                [Authorize(Roles.SUPERVISOR)]
                public IActionResult SupervisorPage()
                {
                    ViewBag.Message = "Permission controlled through action Attribute";
                    return View("SupervisorPage");
                }

                [Authorize(Roles.ANALYST)]
                public IActionResult AnalystPage()
                {
                    return View("AnalystPage");
                }
	
	            public IActionResult SupervisorAnalystPage()
                {
                    ViewBag.Message = "Permission controlled inside action method";
                    if (this.HavePermission(Roles.SUPERVISOR))
                        return View("SupervisorPage");
            
                    if (this.HavePermission(Roles.ANALYST))
                        return View("AnalystPage");
            
                    return new RedirectResult("~/Dashboard/NoPermission");
                }
            }
        }    

如何处理和身份验证/授权 Ajax 调用?

在我们实现代码之前,让我们先看看“为什么?”、“在哪里?”和“如何?”执行此操作。

用户可以使应用程序处于空闲状态直到会话过期。当会话过期时,令牌将不可用,用户将从应用程序注销。但页面仍然在浏览器中打开。用户不知道会话已过期且已注销,他们可能会点击页面上的任何内容。如果该点击是常规的 HTTP 调用(页面会重新加载或其他操作),则用户将自动重定向到登录页面。如果该点击碰巧是 Ajax 调用,那么页面将保持不变,并且不会发生重定向到登录页面的情况。

附注:Ajax 调用也是 HTTP 调用。常规 HTTP 调用和 Ajax 调用之间的区别在于它们的调用方式。大多数常规 HTTP 调用直接由浏览器执行。Ajax 调用是从代码(javascript/jquery)发出的,然后浏览器执行调用。对于常规 HTTP 调用,返回结果可以是视图页面或 Json 结果等。但对于 Ajax 调用,只能是 Json 结果。

  • 用于识别 Ajax 调用的扩展方法
  • [UnAuthorized] 过滤器特性中处理 Ajax 调用身份验证
  • [Authorize] 过滤器特性中处理 Ajax 调用授权
  • 在 jquery 中捕获 Ajax 调用的 HTTP 状态码并重定向到登录页面

用于识别 Ajax 调用的扩展方法

首先,我们需要知道一个请求是 Ajax 调用还是常规 HTTP 调用。我们可以通过 HTTP Headers 来确定。其次,我们将创建一个扩展方法,以便可以将其与 Http Request 对象一起使用。

创建一个 staticAjaxExtension.cs 和一个 static 方法“IsAjaxRequest”。添加以下代码

        using Microsoft.AspNetCore.Http;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Threading.Tasks;

        namespace LoginDemo.CustomAttributes
        {
            public static class AjaxExtension
            {
                //HttpRequest Extension method to 
                //check if the incoming request is an AJAX call - JRozario 
                public static bool IsAjaxRequest(this HttpRequest request)
                {
                    if (request == null)
                        throw new ArgumentNullException("request");

                    if (request.Headers != null)
                        return request.Headers["X-Requested-With"] == "XMLHttpRequest";
                    return false;
                }
            }
        }    

这是一个使用 HttpRequest 对象实现的简单扩展方法。我们可以通过 HTTP Headers 中的一个“X-Requested-With”来判断一个请求是否为 Ajax 调用。如果此 Header 的值为“XMLHttpRequest”,则它是 Ajax 调用。

        return request.Headers["X-Requested-With"] == "XMLHttpRequest";    

处理 [UnAuthorized] 过滤器特性中的 Ajax 调用身份验证

现在我们有了一个 IsAjaxRequest() 扩展方法来判断一个请求是否为 Ajax 调用。让我们使用扩展方法向 [UnAuthorized] 过滤器特性添加一个条件。

        using Microsoft.AspNetCore.Mvc;
        using Microsoft.AspNetCore.Mvc.Filters;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Net;
        using System.Security.Claims;
        using System.Threading.Tasks;

        namespace LoginDemo.CustomAttributes
        {
            public class UnAuthorizedAttribute : TypeFilterAttribute
            {
                public UnAuthorizedAttribute() : base(typeof(UnauthorizedFilter))
                {
                    //Empty constructor
                }
            }
            public class UnauthorizedFilter : IAuthorizationFilter
            {
                public void OnAuthorization(AuthorizationFilterContext context)
                {
                    bool IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
                    if (!IsAuthenticated)
                    {
                        if (context.HttpContext.Request.IsAjaxRequest())
                        {
                            context.HttpContext.Response.StatusCode = 
                            (int)HttpStatusCode.Forbidden; //Set HTTP 403 Forbidden - JRozario
                        }
                        else
                        {
                            context.Result = new RedirectResult("~/Home/Index");
                        }
                    }
                }
            }
        }    

如您所见,条件 context.HttpContext.Request.IsAjaxRequest() 检查它是否为 Ajax 调用。如果它是 Ajax 调用,我们不会重定向调用。重定向对 Ajax 调用不起作用。Ajax 调用的返回结果只能是数据。在这里,我们将 HTTP 响应状态码设置为 403 - Forbidden。这表示 Ajax 调用遇到了错误,调用不成功。此演示中的所有 Ajax 调用都用 Jquery 编写。Jquery Ajax 调用可以从 HTTP 响应对象(Header)中获取 HTTP 状态码。如果它是常规 HTTP 调用,则重定向到登录页面。我们完成了对 Ajax 请求的身份验证。接下来,我们需要将用户重定向到登录页面。

在 [Authorize] 过滤器特性中处理 Ajax 调用授权

对于身份验证,我们将 HTTP 状态码设置为 403 Forbidden。对于授权,我们将设置 HTTP 状态码为 401 Unauthorized。逻辑与之前相同,我们使用扩展方法 IsAjaxRequest() 检查它是否为 Ajax 调用。如果是 Ajax 调用,则将 HTTP 状态码设置为 401 Unauthorized。如果是常规 HTTP 调用,则重定向到“无权限”页面。

        using Microsoft.AspNetCore.Mvc;
        using Microsoft.AspNetCore.Mvc.Filters;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Net;
        using System.Security.Claims;
        using System.Threading.Tasks;

        namespace LoginDemo.CustomAttributes
        {
            public class AuthorizeAttribute : TypeFilterAttribute
            {
                public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
                {
                    Arguments = new object[] { claim };
                }
            }

            public class AuthorizeFilter : IAuthorizationFilter
            {
                readonly string[] _claim;

                public AuthorizeFilter(params string[] claim)
                {
                    _claim = claim;
                }

                public void OnAuthorization(AuthorizationFilterContext context)
                {
                    var IsAuthenticated = 
                           context.HttpContext.User.Identity.IsAuthenticated;
                    var claimsIndentity = 
                           context.HttpContext.User.Identity as ClaimsIdentity;
            
                    if (IsAuthenticated)
                    {
                        bool flagClaim = false;
                        foreach (var item in _claim)
                        {
                            if (context.HttpContext.User.HasClaim(item, item))
                                flagClaim = true;
                        }
                        if (!flagClaim)
                        {
                            if (context.HttpContext.Request.IsAjaxRequest())
                                context.HttpContext.Response.StatusCode = 
                                (int)HttpStatusCode.Unauthorized; //Set HTTP 401 
                                                                  //Unauthorized - JRozario
                            else
                                context.Result = 
                                     new RedirectResult("~/Dashboard/NoPermission");
                        }
                    }
                    else
                    {
                        if (context.HttpContext.Request.IsAjaxRequest())
                        {
                            context.HttpContext.Response.StatusCode = 
                              (int)HttpStatusCode.Forbidden; //Set HTTP 403 - JRozario
                        }
                        else
                        {
                            context.Result = new RedirectResult("~/Home/Index");
                        }
                    }
                    return;
                }
            }
        }    

在 Jquery 中捕获 Ajax 调用的 HTTP 状态码并重定向到登录页面

在这里,我们将从 [UnAuthorized] 过滤器特性中设置的 HTTP 状态码中获取信息。这应该在进行 Ajax 调用的地方完成。如前所述,在此演示中,我们使用 Jquery 进行 Ajax 调用。Jquery 提供了一个 .ajaxError 方法来捕获 Ajax 调用期间发生的错误。但是,我们是否要为每个 Ajax 调用编写 .ajaxError 方法?不,.ajaxError 方法可以全局实现,用于应用程序中的所有 Ajax 调用。唯一需要注意的是,.ajaxError 方法必须在该页面上可用。我们所要做的就是在通用页面 _Layout.cshtml(这是主页面)中添加它。将此脚本添加到您的 _Layout.cshtml 或您使用的任何主页面。

        <script>
            $(document).ajaxError(function (xhr, result) {
                //Catch Ajax error with http status code 403 Forbidden 
                //and 401 Unauthorized – Jrozario
                if (result.status === 403) {
                    window.location.href = '/Home/Index';
                }
                if (result.status === 401) {
                    window.location.href = '/Dashboard/NoPermission';
                }
            });
        </script>    

.ajaxError 在参数之一中获取 HTTP 标头,这里将其声明为“result”。在 [UnAuthorized] 过滤器特性中设置的 HTTP 标头和状态码 403 Forbidden 在“result”参数中返回。如果 result.status 为 403,则我们从客户端重定向用户到登录页面。

        if (result.status === 403) {
            window.location.href = '/Home/Index';
        }    

在演示项目中,有一个页面 CheckAjaxCalls.cshtml,您可以在其中检查 Ajax 调用的身份验证/授权。要检查身份验证,请以 Director jsmith@email.com 登录演示项目。您应该会看到下面的页面。

点击链接“查看身份验证和授权如何处理 Ajax 调用”。在此页面上,您可以同时检查身份验证和授权。

上面的屏幕有 3 个按钮:“结束会话”、“身份验证 Ajax 调用”和“授权 Ajax 调用”。所有 3 个按钮都有 JavaScript 函数(OnClick_EndSessionOnClick_AuthenticateAjaxCallOnClick_AuthorizeAjaxCall),它们会进行 Ajax 调用。下面是带有 HTML 标签的完整代码。

        <script>
            function OnClick_EndSession() {
                $.ajax({
                    type: 'GET',
                    url: '/Home/EndSession',
                    data: {},
                    cache: false,
                    success: function (result) { }
                });
                alert("End of User Session, 
                Click on Ajax Call button to autneticate Ajax calls, 
                It should take you to login page.");
            }

            function OnClick_AuthenticateAjaxCall() {
                $.ajax({
                    type: 'GET',
                    url: '/Dashboard/AuthenticateAjaxCalls',
                    data: {},
                    cache: false,
                    success: function (result) {
                        if (result != "")
                            alert("Your session is still active, 
                            end session to see how authentication for Ajax call works!");
                    }
                });
            }

            function OnClick_AuthorizeAjaxCall() {
                $.ajax({
                    type: 'GET',
                    url: '/Dashboard/AuthorizeAjaxCalls',
                    data: {},
                    cache: false,
                    success: function (result) {
                        if (result != "")
                            alert("Your have permission for this Ajax call!");
                    }
                });
            }
        </script>    

下面是页面上 3 个 Ajax 调用的控制器操作方法,返回 Json 结果。此代码在演示项目的 DashboardController.cs 文件中。您可以看到“AuthorizeAjaxCalls”操作方法为 DirectorSupervisor 设置了 [Authorize] 特性。

        //This action method is in Dashboard.cs
        public JsonResult AuthenticateAjaxCalls()
        {
            return Json(new {result = "success" });
        }

        [Authorize(Roles.DIRECTOR, Roles.SUPERVISOR)]
        public JsonResult AuthorizeAjaxCalls()
        {
            return Json(new { result = "success" });
        }

        //This action method is in HomeController.cs 
        public JsonResult EndSession()
        {
            HttpContext.Session.Clear();
            return Json(new {result = "success"});
        } 

 

现在点击“身份验证 Ajax 调用”按钮。如果会话仍然有效,您应该会收到一个警告消息。

要结束会话,请点击“结束会话”,然后点击“身份验证 Ajax 调用”。您应该会被重定向到登录页面。

要检查授权,请以 Analyst JBlack@email.com(属于 Analyst 角色)登录演示项目。在此演示中,Ajax 调用仅针对 Director 和 Supervisor。Analyst 没有 Ajax 调用的权限,因此他们将被重定向到“无权限”页面。登录后,点击链接“查看身份验证和授权如何处理 Ajax 调用”。在下一个页面,点击“授权 Ajax 调用”。由于 JBlack@email.com 是 Analyst,他没有权限,将被重定向到“无权限”页面。

如果您以 Director 或 Supervisor 身份登录,那么在点击“授权 Ajax 调用”时,您会看到一个警告消息。

LoginDemo.sln 项目

完全可用的 LoginDemo.sln 项目可供下载。请参阅下面的屏幕截图,快速浏览 LoginDemo.sln 项目。

登录页面

登陆页面

顶部横幅显示所有页面的已登录用户名及其角色。点击提供的链接以查看不同的场景。

第三部分

在第三部分,我们将介绍如何进行忘记密码和重置密码。

ASP.NET CORE 使用 JWT 进行令牌身份验证和授权(不使用 Cookie)– 第一部分

© . All rights reserved.