Loading

使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)

介绍

第1部分介绍了如何使用登录凭据对用户进行身份验证。在第2部分中,我们将了解如何为用户实现授权。换句话说,授予用户使用应用程序的某些部分或全部功能的权限。用户权限(授权)分为3个级别处理:控制器,操作方法和查看页面。为此,我们将编写自定义属性和少量扩展方法。第2部分包括单独的LoginDemo.sln项目,该项目使用下面涵盖的所有主题进行授权。

用户角色

让我们设置三个不同的角色:

  1. Director
  2. Supervisor
  3. Analyst

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

  1.  
    using System;
  2.  
    using System.Collections.Generic;
  3.  
    using System.Linq;
  4.  
    using System.Threading.Tasks;
  5.  
     
  6.  
    namespace LoginDemo.CustomAttributes
  7.  
    {
  8.  
    public static class Roles
  9.  
    {
  10.  
    public const string DIRECTOR = "DIRECTOR";
  11.  
    public const string SUPERVISOR = "SUPERVISOR";
  12.  
    public const string ANALYST = "ANALYST";
  13.  
    }
  14.  
    }

让我们向用户集合添加权限。

  1.  
    //Using hard coded collection list as Data Store for demo purpose.
  2.  
    //In reality, User data comes from Database or some other Data Source - JRozario
  3.  
    private List UserList = new List
  4.  
    {
  5.  
    new User { USERID = "jsmith@email.com", PASSWORD = "test",
  6.  
    EMAILID = "jsmith@email.com", FIRST_NAME = "John",
  7.  
    LAST_NAME = "Smith", PHONE = "356-735-2748",
  8.  
    ACCESS_LEVEL = Roles.DIRECTOR.ToString(), WRITE_ACCESS = "WRITE_ACCESS" },
  9.  
    new User { USERID = "srob@email.com", PASSWORD = "test",
  10.  
    FIRST_NAME = "Steve", LAST_NAME = "Rob",
  11.  
    EMAILID = "srob@email.com", PHONE = "567-479-8537",
  12.  
    ACCESS_LEVEL = Roles.SUPERVISOR.ToString(), WRITE_ACCESS = "WRITE_ACCESS" },
  13.  
    new User { USERID = "dwill@email.com", PASSWORD = "test",
  14.  
    FIRST_NAME = "DJ", LAST_NAME = "Will",
  15.  
    EMAILID = "dwill@email.com", PHONE = "599-306-6010",
  16.  
    ACCESS_LEVEL = Roles.ANALYST.ToString(), WRITE_ACCESS = "WRITE_ACCESS" },
  17.  
    new User { USERID = "JBlack@email.com", PASSWORD = "test",
  18.  
    FIRST_NAME = "Joe", LAST_NAME = "Black",
  19.  
    EMAILID = "JBlack@email.com", PHONE = "764-460-8610",
  20.  
    ACCESS_LEVEL = Roles.ANALYST.ToString(), WRITE_ACCESS = "" }
  21.  
    };

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

用户权限使用可用于装饰控制器和操作方法的特性来处理。当在控制器顶部修饰特性时,该特性将应用于该控制器中的所有操作方法。同样,每种操作方法也可以修饰。

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

让我们编写用于授权的自定义特性:

  1.  
    using Microsoft.AspNetCore.Mvc;
  2.  
    using Microsoft.AspNetCore.Mvc.Filters;
  3.  
    using System;
  4.  
    using System.Collections.Generic;
  5.  
    using System.Linq;
  6.  
    using System.Security.Claims;
  7.  
    using System.Threading.Tasks;
  8.  
     
  9.  
    namespace LoginDemo.CustomAttributes
  10.  
    {
  11.  
    public class AuthorizeAttribute : TypeFilterAttribute
  12.  
    {
  13.  
    public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
  14.  
    {
  15.  
    Arguments = new object[] { claim };
  16.  
    }
  17.  
    }
  18.  
     
  19.  
    public class AuthorizeFilter : IAuthorizationFilter
  20.  
    {
  21.  
    readonly string[] _claim;
  22.  
     
  23.  
    public AuthorizeFilter(params string[] claim)
  24.  
    {
  25.  
    _claim = claim;
  26.  
    }
  27.  
     
  28.  
    public void OnAuthorization(AuthorizationFilterContext context)
  29.  
    {
  30.  
    var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
  31.  
    var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;
  32.  
     
  33.  
    if (IsAuthenticated)
  34.  
    {
  35.  
    bool flagClaim = false;
  36.  
    foreach (var item in _claim)
  37.  
    {
  38.  
    if (context.HttpContext.User.HasClaim(item, item))
  39.  
    flagClaim = true;
  40.  
    }
  41.  
    if (!flagClaim)
  42.  
    context.Result = new RedirectResult("~/Dashboard/NoPermission");
  43.  
    }
  44.  
    else
  45.  
    {
  46.  
    context.Result = new RedirectResult("~/Home/Index");
  47.  
    }
  48.  
    return;
  49.  
    }
  50.  
    }
  51.  
    }

正如你所看到的,在上面的代码中有两个类:AuthorizeAttributeAuthorizeFilter

AuthorizeAttribute

  1.  
    public class AuthorizeAttribute : TypeFilterAttribute
  2.  
    {
  3.  
    public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
  4.  
    {
  5.  
    Arguments = new object[] { claim };
  6.  
    }
  7.  
    }

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

AuthorizeFilter

public class AuthorizeFilter : IAuthorizationFilter

AuthorizeFilter类使用OnAuthorization方法实现IAuthroizationFilter接口。

  1.  
    public AuthorizeFilter(params string[] claim)
  2.  
    {
  3.  
    _claim = claim;
  4.  
    }

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

public void OnAuthorization(AuthorizationFilterContext context)

OnAuthorization方法自带AuthorizationFilterContext。从这个上下文中,我们可以获取HttpContext和带有声明的用户标识(User Identity)。

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

通过context对象,我们可以知道用户是否已通过身份验证。使用相同的context对象,我们可以获得用户声明标识集合。

  1.  
    if (IsAuthenticated)
  2.  
    {
  3.  
    bool flagClaim = false;
  4.  
    foreach (var item in _claim)
  5.  
    {
  6.  
    if (context.HttpContext.User.HasClaim(item, item))
  7.  
    flagClaim = true;
  8.  
    }
  9.  
    if (!flagClaim)
  10.  
    context.Result = new RedirectResult("~/Dashboard/NoPermission");
  11.  
    }
  12.  
    else
  13.  
    {
  14.  
    context.Result = new RedirectResult("~/Home/Index");
  15.  
    }
  16.  
    return;

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

在此,context.HttpContext.User.Identity对象在claims对象集合中带有用户权限。在第1部分中,我们创建了具有用户声明的令牌并将其加载到context.HttpContext.User标识对象中。请参阅第1部分中“ Middleware app.UseAuthentication()” 下的内容。

在循环内部,我们使用HasClaim方法检查参数值是否在用户标识声明集合中。在第1部分中,解释了如何创建User Claims Identity对象集合并将其分配给HttpContextHttpContext从中获取用户声明并对照传递的参数值进行检查是相同的。

如果在Claims集合中找到参数值,那么将授权用户并让其通过Http Request。如果没有,我们会将用户重定向到一个页面,该页面显示您没有权限。在本文中,它被重定向到No Permission页面。

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

现在我们已经创建了一个自定义特性进行授权,现在该在控制器中使用它了。

  1.  
    public class DashboardController : Controller
  2.  
    {
  3.  
    [Authorize(Roles.DIRECTOR)]
  4.  
    public IActionResult DirectorPage()
  5.  
    {
  6.  
    return View("DirectorPage");
  7.  
    }
  8.  
     
  9.  
    [Authorize(Roles.SUPERVISOR)]
  10.  
    public IActionResult SupervisorPage()
  11.  
    {
  12.  
    ViewBag.Message = "Permission controlled through action Attribute";
  13.  
    return View("SupervisorPage");
  14.  
    }
  15.  
     
  16.  
    [Authorize(Roles.ANALYST)]
  17.  
    public IActionResult AnalystPage()
  18.  
    {
  19.  
    return View("AnalystPage");
  20.  
    }
  21.  
    }

操作方法用[Authorize]特性修饰。我们将用户角色作为参数传递。DirectorSupervisorAnalyst每个都具有单独的操作方法。

  1.  
    [Authorize(Roles.DIRECTOR)]
  2.  
    public IActionResult DirectorPage()
  3.  
    {
  4.  
    return View("DirectorPage");
  5.  
    }

在此'DirectorPage'操作方法中,我们使用Roles.DIRECTOR设置了Authorize特性。这意味着,只有具有Director角色的用户才能查看此页面。如果SupervisorAnalyst尝试访问该页面,则它们将从AuthorizeFilter类中重定向到No Permission页面。

Roles.DIRECTOR是我们要传递给AuthorizeFilter类的参数。Authorize过滤器类采用Roles.DIRECTOR参数值,并检查是否在User Claims Identity集合中找到该值。如果找到了,则允许用户通过操作方法,并且允许用户看到DirectorPage。如果登录的用户是Supervisor Analyst,则他们将看不到DirectorPage

如果我们希望允许多个用户访问操作方法,则可以使用逗号分隔值来传递参数值,如下所示:

  1.  
    [Authorize(Roles.DIRECTOR, Roles.SUPERVISOR, Roles.ANALYST)]
  2.  
    public IActionResult AllRoles()
  3.  
    {
  4.  
    return View();
  5.  
    }

要在控制器级别设置权限,我们可以如下进行设置。如果在控制器级别设置了Authorize特性,则将为该控制器中的所有操作方法设置权限。

  1.  
    namespace LoginDemo.Controllers
  2.  
    {
  3.  
    [Authorize(Roles.DIRECTOR, Roles.SUPERVISOR, Roles.ANALYST)]
  4.  
    public class YourController : Controller
  5.  
    {
  6.  
    public IActionResult Action1()
  7.  
    {
  8.  
    return View();
  9.  
    }
  10.  
     
  11.  
    public IActionResult Action2()
  12.  
    {
  13.  
    return View();
  14.  
    }
  15.  
     
  16.  
    public IActionResult Action3()
  17.  
    {
  18.  
    return View();
  19.  
    }
  20.  
      }
  21.  
    }

检查用户权限的扩展方法

过滤器特性只能在控制器中使用。但是在某些地方,我们需要检查用户权限,而不能仅依靠过滤器属性。如果我们想在If条件下检查操作方法内的用户角色怎么办?如果我们要检查查看页面中的用户角色怎么办?在这种情况下,我们不能使用过滤器特性。因此,我们将拥有扩展方法。一种用于控制器操作方法,另一种用于查看页面。

让我们创建一个staticPermissionExtension.cs以具有控制器的扩展方法。

  1.  
    namespace LoginDemo.CustomAttributes
  2.  
    {
  3.  
    public static class PermissionExtension
  4.  
    {
  5.  
    public static bool HavePermission(this Controller c, string claimValue)
  6.  
    {
  7.  
    var user = c.HttpContext.User as ClaimsPrincipal;
  8.  
    bool havePer = user.HasClaim(claimValue, claimValue);
  9.  
    return havePer;
  10.  
    }
  11.  
    public static bool HavePermission(this IIdentity claims, string claimValue)
  12.  
    {
  13.  
    var userClaims = claims as ClaimsIdentity;
  14.  
    bool havePer = userClaims.HasClaim(claimValue, claimValue);
  15.  
    return havePer;
  16.  
    }
  17.  
    }
  18.  
    }

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

这些扩展方法是为了方便起见,并将东西放在一个地方。您也可以在操作方法和视图页面中直接使用User.HasClaims()

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

假设您有一种操作方法,并且想将其用于多个角色。但是您想要执行不同的逻辑或从不同的来源或基于角色的特定操作获取数据。在这种情况下,我们需要知道操作方法内部的登录用户角色。

在以下操作方法中,所有登录用户均具有权限。我们检查action方法内部的用户角色。在action方法内部,我想根据角色返回不同的视图。对于Supervisor,它返回SupervisorPage视图,对于Analyst,它返回AnalystPage视图。仅举例来说,它可用于实现不同的逻辑,从不同的源获取数据等。

这是使用扩展方法HavePermission()控制权限的简单if条件。

  1.  
    public IActionResult SupervisorAnalystPage()
  2.  
    {
  3.  
    ViewBag.Message = "Permission controlled inside action method";
  4.  
    if (this.HavePermission(Roles.SUPERVISOR))
  5.  
    return View("SupervisorPage");
  6.  
     
  7.  
    if (this.HavePermission(Roles.ANALYST))
  8.  
    return View("AnalystPage");
  9.  
     
  10.  
    return new RedirectResult("~/Dashboard/NoPermission");
  11.  
    }

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

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

如何控制视图页面内部的用户权限?

如果我们想根据用户角色/权限来控制查看页面中的内容,该怎么办?如何显示和隐藏DirectorSurpervisor Analyst各自不同的顶部菜单项?为此,我们需要了解视图页面级别的用户权限。我们想要这样的东西,如果用户是Director,则显示/隐藏菜单,按钮等。 

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

  1.  
    @if (User.Identity.HavePermission("WRITE_ACCESS") == true)
  2.  
    {
  3.  
    <button type="button"</button<>
  4.  
    }
  5.  
    else
  6.  
    {
  7.  
    <button type="button" disabled="disabled"<</button>
  8.  
    }

在上面,我们创建了两种HavePermission()扩展方法,一种用于Controller,另一种用于View Page。在此HavePermission()检查HttpContext用户声明对象集合中的WRITE_ACCESS声明。如果为true,则启用保存按钮,否则为禁用。这样,我们可以根据用户的角色/权限控制显示什么?如何显示?查看页面内容。

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

以没有WRITE权限的Analyst JBlack@email.com登录到演示项目。在登录页面中,单击查看页面级别权限的工作原理 ”

您会看到保存按钮被禁用,用户无法保存表单数据。

对于其他用户,他们具有WRITE权限,并且启用了保存按钮以保存表单数据。

如何处理未授权的请求?

如何限制未登录应用程序但直接尝试通过提供页面URL来访问页面的用户?话说应用程序URLhttp://www.yourapplication.com,它使用户登录页面,是有一个“Supervisor”页面,用户知道该页面的URL,如果“Supervisor”页面的URLhttp://www.yourapplication.com/Supervisor,用户直接在浏览器中输入URL而不登录,则是未经授权的访问。让我们看看如何处理这个问题。

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

  1.  
    namespace LoginDemo.CustomAttributes
  2.  
    {
  3.  
    public class UnAuthorizedAttribute : TypeFilterAttribute
  4.  
    {
  5.  
    public UnAuthorizedAttribute() : base(typeof(UnauthorizedFilter))
  6.  
    {
  7.  
    //Empty constructor
  8.  
    }
  9.  
    }
  10.  
    public class UnauthorizedFilter : IAuthorizationFilter
  11.  
    {
  12.  
    public void OnAuthorization(AuthorizationFilterContext context)
  13.  
    {
  14.  
    bool IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
  15.  
    if (!IsAuthenticated)
  16.  
    {
  17.  
    context.Result = new RedirectResult("~/Home/Index");
  18.  
    }
  19.  
    }
  20.  
    }
  21.  
    }

AuthorizationFilterContext中获取用户身份对象,从中可以确定用户是否已通过身份验证。

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

如果该用户未通过身份验证或登录,请将该用户重定向到登录页面。

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

您只需在顶部添加[UnAuthorized]特性即可在控制器级别使用它,该属性适用于该控制器中的所有操作方法。Authorize特性还会检查IsAuthenticated,因此可以询问[UnAuthorized]特性的用途。在某些情况下,权限不受控制,这意味着所有用户都可以对某个操作方法具有权限,并且该操作方法没有[Authorize]特性。在下面的控制器中,SupervisorAnalystPage()操作方法没有[Authorize]特性。任何知道URL的用户都可以尝试访问它。具有[UnAuthorized]特性的另一个原因是处理Ajax调用,我们将在下一个主题中看到它。

  1.  
    namespace LoginDemo.Controllers
  2.  
    {
  3.  
    [UnAuthorized]
  4.  
    public class DashboardController : Controller
  5.  
    {
  6.  
    [Authorize(Roles.DIRECTOR)]
  7.  
    public IActionResult DirectorPage()
  8.  
    {
  9.  
    return View("DirectorPage");
  10.  
    }
  11.  
     
  12.  
    [Authorize(Roles.SUPERVISOR)]
  13.  
    public IActionResult SupervisorPage()
  14.  
    {
  15.  
    ViewBag.Message = "Permission controlled through action Attribute";
  16.  
    return View("SupervisorPage");
  17.  
    }
  18.  
     
  19.  
    [Authorize(Roles.ANALYST)]
  20.  
    public IActionResult AnalystPage()
  21.  
    {
  22.  
    return View("AnalystPage");
  23.  
    }
  24.  
     
  25.  
    public IActionResult SupervisorAnalystPage()
  26.  
    {
  27.  
    ViewBag.Message = "Permission controlled inside action method";
  28.  
    if (this.HavePermission(Roles.SUPERVISOR))
  29.  
    return View("SupervisorPage");
  30.  
     
  31.  
    if (this.HavePermission(Roles.ANALYST))
  32.  
    return View("AnalystPage");
  33.  
     
  34.  
    return new RedirectResult("~/Dashboard/NoPermission");
  35.  
    }
  36.  
    }
  37.  
    }

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

在执行代码之前,让我们看看为什么?”“在哪里?如何?来做到这一点。

用户可以将应用程序保持空闲状态,直到会话过期。会话到期时,令牌不可用,并且用户从应用程序注销。但是该页面仍在浏览器中打开。用户不知道会话已过期并且用户已注销,因此可以单击页面中的任何内容。如果该点击是常规的http调用(发生页面重新加载之类的事情),则该用户将自动重定向到登录页面。如果该点击恰好是Ajax调用,则该页面将保持原样,并且不会重定向到登录页面。

PSAjax调用也是HTTP调用。常规HTTP调用和Ajax调用的调用方式不同。通常,常规的HTTP调用直接由浏览器执行。Ajax调用是通过代码(javascript/jquery)进行的,然后浏览器执行该调用。对于常规HTTP调用,返回结果可以是View PageJson结果,等等。但是对于Ajax调用,它只是Json结果。

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

识别Ajax调用的扩展方法

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

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

  1.  
    using Microsoft.AspNetCore.Http;
  2.  
    using System;
  3.  
    using System.Collections.Generic;
  4.  
    using System.Linq;
  5.  
    using System.Threading.Tasks;
  6.  
     
  7.  
    namespace LoginDemo.CustomAttributes
  8.  
    {
  9.  
    public static class AjaxExtension
  10.  
    {
  11.  
    //HttpRequest Extension method to
  12.  
      //check if the incoming request is an AJAX call - JRozario
  13.  
    public static bool IsAjaxRequest(this HttpRequest request)
  14.  
    {
  15.  
    if (request == null)
  16.  
    throw new ArgumentNullException("request");
  17.  
     
  18.  
    if (request.Headers != null)
  19.  
    return request.Headers["X-Requested-With"] == "XMLHttpRequest";
  20.  
    return false;
  21.  
    }
  22.  
    }
  23.  
    }

这是使用HttpRequest对象实现的简单扩展方法。我们可以发现请求是否是带有HttpX-Requested-With之一的Ajax调用。如果此标头值为XMLHttpRequest,则为Ajax调用。

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

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

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

  1.  
    using Microsoft.AspNetCore.Mvc;
  2.  
    using Microsoft.AspNetCore.Mvc.Filters;
  3.  
    using System;
  4.  
    using System.Collections.Generic;
  5.  
    using System.Linq;
  6.  
    using System.Net;
  7.  
    using System.Security.Claims;
  8.  
    using System.Threading.Tasks;
  9.  
     
  10.  
    namespace LoginDemo.CustomAttributes
  11.  
    {
  12.  
    public class UnAuthorizedAttribute : TypeFilterAttribute
  13.  
    {
  14.  
    public UnAuthorizedAttribute() : base(typeof(UnauthorizedFilter))
  15.  
    {
  16.  
    //Empty constructor
  17.  
    }
  18.  
    }
  19.  
    public class UnauthorizedFilter : IAuthorizationFilter
  20.  
    {
  21.  
    public void OnAuthorization(AuthorizationFilterContext context)
  22.  
    {
  23.  
    bool IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
  24.  
    if (!IsAuthenticated)
  25.  
    {
  26.  
    if (context.HttpContext.Request.IsAjaxRequest())
  27.  
    {
  28.  
    context.HttpContext.Response.StatusCode =
  29.  
    (int)HttpStatusCode.Forbidden; //Set HTTP 403 Forbidden - JRozario
  30.  
    }
  31.  
    else
  32.  
    {
  33.  
    context.Result = new RedirectResult("~/Home/Index");
  34.  
    }
  35.  
    }
  36.  
    }
  37.  
    }
  38.  
    }

如您所见,检查它是否是Ajax调用的条件context.HttpContext.Request.IsAjaxRequest()。如果是Ajax调用,则不会重定向该调用。重定向将不适用于Ajax调用。Ajax调用的返回结果只能是数据。在这里,我们将Http响应状态代码设置为403 –禁止。这表示Ajax调用最终以Error结束,并且调用失败。该演示中的所有Ajax调用均使用jquery编写。jQuery Ajax调用可以从HTTP响应对象(标头)获取Http状态代码。如果是常规Http调用,则重定向到登录页面。我们已经完成了对Ajax请求的身份验证。接下来,我们需要将用户重定向到登录页面。

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

对于身份验证,我们将Http状态代码设置为403禁止。对于授权,我们将Http状态代码设置为401未经授权。使用扩展方法IsAjaxRequest()检查是否为Ajax调用是相同的逻辑。如果是Ajax调用,请将Http 状态码设置为401未经授权。如果是常规Http调用,则重定向到No Permission页面。

  1.  
    using Microsoft.AspNetCore.Mvc;
  2.  
    using Microsoft.AspNetCore.Mvc.Filters;
  3.  
    using System;
  4.  
    using System.Collections.Generic;
  5.  
    using System.Linq;
  6.  
    using System.Net;
  7.  
    using System.Security.Claims;
  8.  
    using System.Threading.Tasks;
  9.  
     
  10.  
    namespace LoginDemo.CustomAttributes
  11.  
    {
  12.  
    public class AuthorizeAttribute : TypeFilterAttribute
  13.  
    {
  14.  
    public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
  15.  
    {
  16.  
    Arguments = new object[] { claim };
  17.  
    }
  18.  
    }
  19.  
     
  20.  
    public class AuthorizeFilter : IAuthorizationFilter
  21.  
    {
  22.  
    readonly string[] _claim;
  23.  
     
  24.  
    public AuthorizeFilter(params string[] claim)
  25.  
    {
  26.  
    _claim = claim;
  27.  
    }
  28.  
     
  29.  
    public void OnAuthorization(AuthorizationFilterContext context)
  30.  
    {
  31.  
    var IsAuthenticated =
  32.  
      context.HttpContext.User.Identity.IsAuthenticated;
  33.  
    var claimsIndentity =
  34.  
      context.HttpContext.User.Identity as ClaimsIdentity;
  35.  
     
  36.  
    if (IsAuthenticated)
  37.  
    {
  38.  
    bool flagClaim = false;
  39.  
    foreach (var item in _claim)
  40.  
    {
  41.  
    if (context.HttpContext.User.HasClaim(item, item))
  42.  
    flagClaim = true;
  43.  
    }
  44.  
    if (!flagClaim)
  45.  
    {
  46.  
    if (context.HttpContext.Request.IsAjaxRequest())
  47.  
    context.HttpContext.Response.StatusCode =
  48.  
    (int)HttpStatusCode.Unauthorized; //Set HTTP 401
  49.  
      //Unauthorized - JRozario
  50.  
    else
  51.  
    context.Result =
  52.  
      new RedirectResult("~/Dashboard/NoPermission");
  53.  
    }
  54.  
    }
  55.  
    else
  56.  
    {
  57.  
    if (context.HttpContext.Request.IsAjaxRequest())
  58.  
    {
  59.  
    context.HttpContext.Response.StatusCode =
  60.  
      (int)HttpStatusCode.Forbidden; //Set HTTP 403 - JRozario
  61.  
    }
  62.  
    else
  63.  
    {
  64.  
    context.Result = new RedirectResult("~/Home/Index");
  65.  
    }
  66.  
    }
  67.  
    return;
  68.  
    }
  69.  
    }
  70.  
    }

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

在这里,我们将取回在[UnAuthorized]过滤器特性中设置的HTTP状态代码。应该在进行Ajax调用的地方进行。如前所述,在本演示中,我们使用Jquery进行Ajax调用。jQuery附带有一种方法.ajaxError来捕获Ajax调用期间发生的错误。但是我们是否要为每个Ajax调用编写.ajaxError方法?不,可以为整个应用程序全局实现.ajaxError方法,该方法将处理所有Ajax调用的错误。只是一点,.ajaxError方法应该在该页面上可用。我们要做的就是将其添加到通用页_Layout.cshtml(即母版页)中。将此脚本添加到_Layout.cshtml或您使用的任何母版页中。

 

  1.  
    <script>
  2.  
    $(document).ajaxError(function (xhr, result) {
  3.  
    //Catch Ajax error with http status code 403 Forbidden
  4.  
      //and 401 Unauthorized – Jrozario
  5.  
    if (result.status === 403) {
  6.  
    window.location.href = '/Home/Index';
  7.  
    }
  8.  
    if (result.status === 401) {
  9.  
    window.location.href = '/Dashboard/NoPermission';
  10.  
    }
  11.  
    });
  12.  
    </script>

在其中一个参数中.ajaxError获取Http标头,在此将其声明为result。在result参数中返回在[UnAuthorized]过滤器额性中设置的Http标头和状态码403禁止。如果result.status403,则将用户从客户端重定向到登录页面。

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

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

单击链接查看如何处理Ajax调用的身份验证和授权 ”。在此页面中,您可以检查身份验证和授权。

上面的屏幕中有3个按钮,End SessionAuthenticate Ajax CallAuthorize Ajax Call。所有3按钮都有JavaScript函数(OnClick_EndSessionOnClick_AuthenticateAjaxCallOnClick_AuthorizeAjaxCall),它们使用Ajax调用。以下是带有HTML标签的完整代码。

  1.  
    <script>
  2.  
    function OnClick_EndSession() {
  3.  
    $.ajax({
  4.  
    type: 'GET',
  5.  
    url: '/Home/EndSession',
  6.  
    data: {},
  7.  
    cache: false,
  8.  
    success: function (result) { }
  9.  
    });
  10.  
    alert("End of User Session,
  11.  
    Click on Ajax Call button to autneticate Ajax calls,
  12.  
    It should take you to login page.");
  13.  
    }
  14.  
     
  15.  
    function OnClick_AuthenticateAjaxCall() {
  16.  
    $.ajax({
  17.  
    type: 'GET',
  18.  
    url: '/Dashboard/AuthenticateAjaxCalls',
  19.  
    data: {},
  20.  
    cache: false,
  21.  
    success: function (result) {
  22.  
    if (result != "")
  23.  
    alert("Your session is still active,
  24.  
    end session to see how authentication for Ajax call works!");
  25.  
    }
  26.  
    });
  27.  
    }
  28.  
     
  29.  
    function OnClick_AuthorizeAjaxCall() {
  30.  
    $.ajax({
  31.  
    type: 'GET',
  32.  
    url: '/Dashboard/AuthorizeAjaxCalls',
  33.  
    data: {},
  34.  
    cache: false,
  35.  
    success: function (result) {
  36.  
    if (result != "")
  37.  
    alert("Your have permission for this Ajax call!");
  38.  
    }
  39.  
    });
  40.  
    }
  41.  
    </script>

以下是针对页面中3个按钮的3Ajax调用的Controller操作方法,该方法返回Json结果。此代码位于演示项目的DashboardController.cs文件中。您可以看到AuthorizeAjaxCalls操作方法为DirectorSupervisor设置了[Authorize]特性。

  1.  
    //This action method is in Dashboard.cs
  2.  
    public JsonResult AuthenticateAjaxCalls()
  3.  
    {
  4.  
    return Json(new {result = "success" });
  5.  
    }
  6.  
     
  7.  
    [Authorize(Roles.DIRECTOR, Roles.SUPERVISOR)]
  8.  
    public JsonResult AuthorizeAjaxCalls()
  9.  
    {
  10.  
    return Json(new { result = "success" });
  11.  
    }
  12.  
     
  13.  
    //This action method is in HomeController.cs
  14.  
      public JsonResult EndSession()
  15.  
            {
  16.  
                HttpContext.Session.Clear();
  17.  
                return Json(new {result = "success"});
  18.  
            }

 现在单击Authenticate Ajax Call按钮。如果会话仍处于活动状态,您应该收到一条警报消息。

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

要检查授权,请以具有Analyst角色的JBlack@email.com登录到演示项目。在此演示中,Ajax调用仅适用于DirectorSupervisorAnalyst没有Ajax调用的权限,因此他们将被重定向到No Permission页面。登录后,单击链接查看如何处理Ajax调用的身份验证和授权。在下一页中,单击授权Ajax调用。由于JBlack@email.comAnalyst,所以他没有权限,将被重定向到No Permission页面。

如果您以DirectorSupervisor身份登录,则在单击Authorize Ajax Call” 时会看到一条警告消息。

LoginDemo.sln项目

可以正常运行的LoginDemo.sln项目可供下载。请参见下面的屏幕快照,以快速了解LoginDemo.sln项目。

Login Page

Landing Page

顶部横幅显示登录的用户名及其在所有页面中的角色。单击给定的链接以检查不同的情况。

posted @ 2023-03-08 13:08  RuoVea  阅读(66)  评论(0编辑  收藏  举报