您当前的位置: 首页 >  .net

寒冰屋

暂无认证

  • 0浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

寒冰屋 发布时间:2019-11-18 21:57:49 ,浏览量:0

目录

介绍

用户角色

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

AuthorizeAttribute

AuthorizeFilter

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

检查用户权限的扩展方法

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

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

如何处理未授权的请求?

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

识别Ajax调用的扩展方法

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

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

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

LoginDemo.sln项目

Login Page

Landing Page

  • 下载项目(在Visual Studio 2017中开发的LoginDemo项目)-2.1 MB 
介绍

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

用户角色

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

  1. Director
  2. Supervisor
  3. Analyst

创建一个static类Roles.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";
    }
}

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

//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接口和TypeFilterAttribute、IAuthorizationFilter为我们的授权使用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;
        }
    }
}

正如你所看到的,在上面的代码中有两个类:AuthorizeAttribute和AuthorizeFilter。

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

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

AuthorizeFilter
public class AuthorizeFilter : IAuthorizationFilter

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

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

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

public void OnAuthorization(AuthorizationFilterContext context)

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

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

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

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

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

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

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]特性修饰。我们将用户角色作为参数传递。Director,Supervisor和Analyst每个都具有单独的操作方法。

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

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

Roles.DIRECTOR是我们要传递给AuthorizeFilter类的参数。Authorize过滤器类采用“Roles.DIRECTOR”参数值,并检查是否在User Claims Identity集合中找到该值。如果找到了,则允许用户通过操作方法,并且允许用户看到“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”条件下检查操作方法内的用户角色怎么办?如果我们要检查“查看页面”中的用户角色怎么办?在这种情况下,我们不能使用过滤器特性。因此,我们将拥有扩展方法。一种用于控制器操作方法,另一种用于查看页面。

让我们创建一个static类PermissionExtension.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;
        }
    }
}

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

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

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

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

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

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

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、Surpervisor 和Analyst各自不同的顶部“菜单”项?为此,我们需要了解视图页面级别的用户权限。我们想要这样的东西,如果用户是Director,则显示/隐藏菜单,按钮等。 

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

@if (User.Identity.HavePermission("WRITE_ACCESS") == true)
{
                
关注
打赏
1665926880
查看更多评论
0.0570s