【.Net Core】JWT與用戶授權(細化到Action)

原文鏈接:https://www.cnblogs.com/FlyLolo/p/ASPNETCore2_27.html

一、概述

  首先說一下認證(authentication)與授權(authorization),它們經常在一起工作,所以有時候會分不清楚。並且這兩個英文單詞長得也像兄弟。舉例來說,我刷門禁卡進入公司,門禁【認證】了我是這裏的員工,可以進入;但進入公司以後,我並不是所有房間都可以進,比如“機房重地,閒人免進”,我能進入哪些房間,需要公司的【授權】。這就是認證和授權的區別。

  ASP.NET Core提倡的是基於聲明(Claim)的授權,關於這個Claim,上一章用到過,有如下這樣的代碼,但沒有介紹:

Claim[] claims = new Claim[] { new Claim(ClaimTypes.NameIdentifier, user.Code), new Claim(ClaimTypes.Name, user.Name) };

這是一個聲明的集合,它包含了兩個 聲明,用於保存了用戶的唯一ID和用戶名。當然我們還可以添加更多的Claim。對應Claim,還有ClaimsIdentity 和ClaimsPrincipal 兩個類型。

ClaimsIdentity相當於是一個證件,例如上例的門禁卡;ClaimsPrincipal 則是證件的持有者,也就是我本人;那麼對應的Claim就是門禁卡內存儲的一些信息,例如證件號、持有人姓名等。

我除了門禁卡還有身份證、銀行卡等,也就是說一個ClaimsPrincipal中可以有多個ClaimsIdentity,而一個ClaimsIdentity中可以有多個Claim。ASP.NET Core的授權模型大概就是這樣的一個體系。

ASP.NET Core支持多種授權方式,包括兼容之前的角色授權。下面通過幾個例子說明一下(例子依然以上一章的代碼爲基礎)。

二、基於角色授權

  ASP.NET Core兼容之前的角色授權模式,如何使用呢?由於不是本文的重點,這裏只是簡要說一下。修改FlyLolo.JWT.Server的TokenHelper臨時爲張三添加了一個名爲“TestPutBookRole”的權限(實際權限來源此處不做展示)。

        public ComplexToken CreateToken(User user)
        {
            Claim[] claims = new Claim[] { new Claim(ClaimTypes.NameIdentifier, user.Code), new Claim(ClaimTypes.Name, user.Name) };

            //下面對code爲001的張三添加了一個Claim,用於測試在Token中存儲用戶的角色信息,對應測試在FlyLolo.JWT.API的BookController的Put方法,若用不到可刪除
            if (user.Code.Equals("001"))
            {
                claims = claims.Append(new Claim(ClaimTypes.Role, "TestPutBookRole")).ToArray();
            }
            
            return CreateToken(claims);
        }

修改FlyLolo.JWT.API的BookController,添加了一個Action如下

        /// <summary>
        /// 測試在JWT的token中添加角色,在此驗證  見TokenHelper
        /// </summary>
        /// <returns></returns>
        [HttpPut]
        [Authorize(Roles = "TestPutBookRole")]
        public JsonResult Put()
        {
            return new JsonResult("Put  Book ...");
        }

訪問這個Action,只有用張三登錄後獲取的Token能正常訪問。

 

三、基於聲明授權

對於上例來說,本質上也是基於聲明(Claim)的授權,因爲張三的"TestPutBookRole"角色也是作爲一個Claim添加到證書中的。只不過採用了特定的ClaimTypes.Role。那麼是否可以將其他的普通Claim作爲授權的依據呢?當然是可以的。

這裏涉及到了另一個單詞“Policy”,翻譯爲策略?也就是說,可以把一系列的規則(例如要求姓名爲李四,賬號爲002,國籍爲中國等等)組合在一起,形成一個Policy,只有滿足這個Policy的纔可以被授權訪問。

下面我們就新建一個Policy,在Startup的ConfigureServices中添加授權代碼:

services.AddAuthorization(options=>options.AddPolicy("Name",policy=> {
   policy.RequireClaim(ClaimTypes.Name, "張三");
   policy.RequireClaim(ClaimTypes.NameIdentifier,"001");
}));

在BookController中添加一個Action如下

[HttpDelete]
[Authorize(Policy = "TestPolicy")]
public JsonResult Delete()
{
    return new JsonResult("Delete Book ...");
}

可以通過張三和李四的賬號測試一下,只有使用張三的賬號獲取的Token能訪問成功。

 

 

四、基於策略自定義授權

上面介紹了兩種授權方式,現在有個疑問,通過角色授權,只適合一些小型項目,將幾個功能通過角色區分開就可以了。

通過聲明的方式,目測實際項目中需要在Startup中先聲明一系列的Policy,然後在Controller或Action中使用。

這兩種方式都感覺不好。例如經常存在這樣的需求:一個用戶可以有多個角色,每個角色對應多個可訪問的API地址(將授權細化到具體的Action)。用戶還可以被特殊的授予某個API地址的權限。

這樣的需求採用上面的兩種方式實現起來都很麻煩,好在ASP.NET Core提供了方便的擴展方式。

1.樣例數據

將上面的需求彙總一下,最終可以形成如下形式的數據:

/// <summary>
/// 虛擬數據,模擬從數據庫或緩存中讀取用戶相關的權限
/// </summary>
public static class TemporaryData
{
    public readonly static List<UserPermissions> UserPermissions = new List<UserPermissions> {
        new UserPermissions {
            Code = "001",
            Permissions = new List<Permission> {
                new Permission { Code = "A1", Name = "student.create", Url = "/api/student",Method="post" },
                new Permission { Code = "A2", Name = "student.delete", Url = "/api/student",Method="delete"}
            }
        },
        new UserPermissions {
            Code = "002",
            Permissions = new List<Permission> {
                new Permission { Code = "B1", Name = "book.create", Url = "/api/book" ,Method="post"},
                new Permission { Code = "B2", Name = "book.delete", Url = "/api/book" ,Method="delete"}
            }
        },
    };

    public static UserPermissions GetUserPermission(string code)
    {
        return UserPermissions.FirstOrDefault(m => m.Code.Equals(code));
    }
}

涉及到的兩個類如下:

    public class Permission
    {
        public string Code { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }

        public string Method { get; set; }
    }

    public class UserPermissions
    {
        public string Code { get; set; }
        public List<Permission> Permissions { get; set; }
    }

2.自定義處理程序

 

下面就是根據樣例數據來制定相應的處理程序了。這涉及到IAuthorizationRequirement和AuthorizationHandler兩個內容。

IAuthorizationRequirement是一個空的接口,主要用於提供授權所需要滿足的“要求”,或者說是“規則”。AuthorizationHandler則是對請求和“要求”的聯合處理。

新建一個PermissionRequirement實現IAuthorizationRequirement接口。

public class PermissionRequirement: IAuthorizationRequirement
{
    public List<UserPermissions> UsePermissionList { get { return TemporaryData.UserPermissions; } }
}

很簡單的內容。它的“要求”也就是用戶的權限列表了,用戶的權限列表中包含當前訪問的API,則授權通過,否則不通過。

判斷邏輯放在新建的PermissionHandler中:

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var code = context.User.Claims.FirstOrDefault(m => m.Type.Equals(ClaimTypes.NameIdentifier));
        if (null != code)
        {
            UserPermissions userPermissions = requirement.UsePermissionList.FirstOrDefault(m => m.Code.Equals(code.Value.ToString()));

            var Request = (context.Resource as AuthorizationFilterContext).HttpContext.Request;

            if (null != userPermissions && userPermissions.Permissions.Any(m => m.Url.ToLower().Equals(Request.Path.Value.ToLower()) && m.Method.ToLower().Equals(Request.Method.ToLower()) ))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
        else
        {
            context.Fail();
        }

        return Task.CompletedTask;
    }
}

邏輯很簡單不再描述。

 

 

3.使用自定義的處理程序

在Startup的ConfigureServices中添加授權代碼

services.AddAuthorization(options => options.AddPolicy("Permission", policy => policy.Requirements.Add(new PermissionRequirement())));
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

將BookController的Delete Action修改一下:

[HttpDelete]
//[Authorize(Policy = "TestPolicy")]
[Authorize(Policy = "Permission")]
public JsonResult Delete()
{
    return new JsonResult("Delete Book ...");
}
測試一下只有李四可以訪問這個Action。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章