ASP.NET Core 2.2:二十七.JWTとユーザ認証(Actionにドリルダウン)

17716 ワード

ASP.NET Core 2.2:二十七.JWTとユーザ認証(Actionにドリルダウン)


前章ではASP.NET CoreではJWTを適用してユーザ認証およびTokenのリフレッシュを行い,本章では次のステップに進み,ユーザ認証を行う.触れた例も以上の章に基づいている.(ASP.NET Coreシリーズカタログ)

一、概説


まず、認証(authentication)と認証(authorization)についてお話しします.それらはよく一緒に働いているので、区別がつかないことがあります.そしてこの2つの英語の単語も兄弟に似ています.例えば、私は門禁カードを使って会社に入って、門禁【認証】は私がここの従業員で、入ることができます;しかし、会社に入ってから、私はすべての部屋に入ることができるわけではありません.例えば、「機械室は重くて、暇な人は入らないでください」など、私はどの部屋に入ることができて、会社の「授権」が必要です.これが認証とライセンスの違いです.
  ASP.NET Coreが提唱しているのは声明(Claim)に基づくライセンスで、このClaimについては前章で使用したことがありますが、以下のようなコードがありますが、紹介されていません.
Claim[] claims = new Claim[] { new Claim(ClaimTypes.NameIdentifier, user.Code), new Claim(ClaimTypes.Name, user.Name) };

これは、ユーザーの一意のIDとユーザー名を保存するための2つの宣言を含む宣言の集合です.もちろん、より多くのClaimを追加することもできます.Claimに対応し、ClaimsIdentityとClaimsPrincipalの2種類があります.
ClaimsIdentityは、前例のゲートカードなどの証明書に相当します.ClaimsPrincipalは証明書の所有者で、私自身です.では、対応するClaimは、証明書番号、所有者の名前など、ゲートカードに格納されている情報です.
私はゲートカードのほかに身分証明書、銀行カードなどがあります.つまり、1つのClaimsPrincipalには複数のClaimsIdentityがあり、1つの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は、次のアクションを追加しました.
        /// 
        ///    JWT token     ,       TokenHelper
        /// 
        /// 
        [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");
}));

BookControlに次のアクションを追加します.
[HttpDelete]
[Authorize(Policy = "TestPolicy")]
public JsonResult Delete()
{
    return new JsonResult("Delete Book ...");
}

張三と李四のアカウントでテストすることができますが、張三のアカウントを使って取得したTokenだけがアクセスできます.

四、ポリシーに基づくカスタム授権


上記の2つのライセンス方式について説明しましたが、ロールライセンスを通じて、いくつかの小さなプロジェクトにのみ適しており、いくつかの機能をロールで区切ることができます.
宣言により、実際のプロジェクトではStartupで一連のPolicyを宣言し、ControllerまたはActionで使用する必要があります.
どちらも気分が悪い.例えば、1人のユーザが複数のロールを有し、各ロールが複数のアクセス可能なAPIアドレスに対応する(特定のActionに権限を細分化する)というニーズがしばしば存在する.ユーザはまた、特定のAPIアドレスに権限を付与することもできる.
このようなニーズは上記の2つの方法で実現するのは面倒で、ASP.NET Coreは便利な拡張方式を提供しています.

1.サンプルデータ


上記の要件を要約すると、最終的には次のような形式のデータが形成されます.
/// 
////// 
public static class TemporaryData
{
    public readonly static List UserPermissions = new List {
        new UserPermissions {
            Code = "001",
            Permissions = new List {
                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 {
                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 Permissions { get; set; }
    }

 

2.カスタムハンドラ


次に、サンプルデータに基づいて対応する処理手順を作成します.これはIAuthorizationRequirementとAuthorizationHandlerの2つの内容に関連している.
IAuthorizationRequirementは、主にライセンスに必要な「要件」、または「ルール」を提供するための空のインタフェースです.AuthorizationHandlerは,要求と「要求」の連携処理である.
新しいPermissionRequirement実装IAuthorizationRequirementインタフェース.
public class PermissionRequirement: IAuthorizationRequirement
{
    public List UsePermissionList { get { return TemporaryData.UserPermissions; } }
}

簡単な内容です.その「要求」はユーザーの権限リストであり、ユーザーの権限リストに現在アクセスしているAPIが含まれている場合、許可は通過します.そうしないと通過しません.
判断ロジックは新しいPermissionHandlerに置かれます.
public class PermissionHandler : AuthorizationHandler
{
    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();

BookControllerのDelete Actionを変更します.
[HttpDelete]
//[Authorize(Policy = "TestPolicy")]
[Authorize(Policy = "Permission")]
public JsonResult Delete()
{
    return new JsonResult("Delete Book ...");
}

李四だけがこのActionにアクセスできることをテストします.
 
コードアドレス:https://github.com/FlyLolo/JWT.Demo/releases/tag/2.0