ASP.NET CoreでカスタムAuthorizeAttributeを作成するにはどうすればよいですか?


428

ASP.NET Coreでカスタム認証属性を作成しようとしています。以前のバージョンでは、オーバーライドすることが可能bool AuthorizeCore(HttpContextBase httpContext)でした。しかし、これはには存在しませんAuthorizeAttribute

カスタムAuthorizeAttributeを作成するための現在のアプローチは何ですか?

達成しようとしていること:ヘッダー認証でセッションIDを受け取っています。そのIDから、特定のアクションが有効かどうかがわかります。


方法はわかりませんが、MVCはオープンソースです。githubリポジトリをプルして、IAuthorizationFilterの実装を探すことができます。今日時間があれば、あなたを探して実際の答えを投稿しますが、約束はありません。githubリポジトリ: github.com/aspnet/Mvc
bopapa_1979

OK、時間切れですが、AuthorizeAttributeを使用するMVC RepoのAuthorizationPolicyの使用をaspnet / Securityリポジトリの github.com/aspnet/Securityで探してください。または、MVCリポジトリで、気になるセキュリティ関連要素が存在していると思われる名前空間(Microsoft.AspNet.Authorization)を探します。申し訳ありませんが、これ以上役立つことはできません。幸運を!
bopapa_1979 2015

回答:


446

ASP.Net Coreチームが推奨するアプローチは、ここで完全に文書化されている新しいポリシー設計を使用することです。新しいアプローチの背後にある基本的な考え方は、新しい[Authorize]属性を使用して「ポリシー」を指定することです(たとえば[Authorize( Policy = "YouNeedToBe18ToDoThis")]、ポリシーをアプリケーションのStartup.csに登録して、コードブロックを実行します(つまり、ユーザーに年齢の主張があることを確認します)。 18歳以上の場合)。

ポリシー設計はフレームワークへのすばらしい追加であり、ASP.Net Security Coreチームはその導入を称賛する必要があります。とはいえ、すべての場合に適しているわけではありません。このアプローチの欠点は、特定のコントローラーまたはアクションが特定のクレームタイプを必要とすることを単純に主張するという最も一般的なニーズに対する便利なソリューションを提供できないことです。アプリケーションに、個々のRESTリソースに対するCRUD操作を制御する数百の個別の権限(「CanCreateOrder」、「CanReadOrder」、「CanUpdateOrder」、「CanDeleteOrder」など)がある場合、新しいアプローチでは、1対1の繰り返しが必要です。ポリシー名とクレーム名の間のマッピング(例:options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));)、または実行時にこれらの登録を実行するコードを記述します(たとえば、データベースからすべてのクレームタイプを読み取り、前述の呼び出しをループで実行します)。ほとんどの場合のこのアプローチの問題は、不必要なオーバーヘッドであることです。

ASP.Net Core Securityチームは独自のソリューションを作成しないことをお勧めしますが、場合によってはこれが最も賢明なオプションになることがあります。

以下は、IAuthorizationFilterを使用して、特定のコントローラーまたはアクションのクレーム要件を表現する簡単な方法を提供する実装です。

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}


[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}

79
これは正しい回答としてマークする必要があります。ここでは、Microsoftの人々が開発者のフィードバックをどのように検討しているかがわかります。彼らがこれに関して「閉鎖的」である理由は理解できません。無数の異なる権限を持つことは非常に一般的な状況であり、それぞれに1つのポリシーをコーディングする必要があるのは、やりすぎです。私はこれを長い間探していました...(vNextがまだここに賭けられていたとき、ほぼ2年前にすでにこの質問をしました:stackoverflow.com/questions/32181400/…しかし、私たちはまだそこに行き詰まっています)
Vi100 2017年

3
これは良いものです。Web APIには認証ミドルウェアがありますが、ロールごとの認証許可には細かいセキュリティがあります。そのため、[MyAuthorize(MyClaimTypes.Permission、MyClaimValueTypes.Write、MyPermission.Employee)]のような属性をスローするだけで十分です。
Mariano Peinador 2017

4
@Derek Greer:これが最良の答えです。ただし、Authorize Action Filterの後に実行されるActionFilterを実装しています。アクションフィルターを実装して承認する方法はありますか?
Jacob Phan 2017

6
@JacobPhanそのとおりです。これは、IAuthorizationFilterインターフェイスを使用して実装する方が適切です。変更を反映するようにコードを更新しました。
デレク・グリア

3
そうnew ForbidResult()、それは、関連する認可スキームを持っていないため、作業は(例外/ 500が発生する)しません。このケースでは何を使用しますか?
Sinaesthetic

252

私はasp.netのセキュリティ担当者です。まず、これはまだミュージックストアのサンプルまたは単体テストの外に文書化されておらず、公開されているAPIに関してすべて改良されていることをお詫び申し上げます。詳細なドキュメントはこちらです。

カスタムの承認属性を記述したくない。あなたがそれをする必要があるならば、我々は何か間違ったことをしました。代わりに、承認要件を記述する必要があります

承認はアイデンティティに作用します。IDは認証によって作成されます。

ヘッダーのセッションIDを確認したいというコメントで、セッションIDはIDの基礎になります。このAuthorize属性を使用したい場合は、認証ミドルウェアを記述してそのヘッダーを取得し、それを認証済みに変換しClaimsPrincipalます。次に、承認要件の中で確認します。承認の要件は、好きなだけ複雑にすることができます。たとえば、現在のIDに対する生年月日を取得し、ユーザーが18歳以上の場合に承認するものは次のとおりです。

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
        public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
            {
                context.Fail();
                return;
            }

            var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
            int age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            if (age >= 18)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
    }
}

次に、ConfigureServices()関数でそれを配線します

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});

最後に、それをコントローラーまたはアクションメソッドに適用します。

[Authorize(Policy = "Over18")]

84
私はそれをどうやって...きめ細かいアクセス制御をどのように実装するのでしょうか?ManageStoreミュージックストアのサンプルの要件としましょう。サンプルにあるように、それを行うには、「すべてを許可するか、何も許可しない」方法しかありません。次に、可能なすべての順列に対して新しいポリシーを作成する必要がありますか?つまり、詳細なクレームが必要な場合は、「Users / Read」、「Users / Create」、「Users / AssignRole」、「Users / Delete」ですか。[ClaimsAutzorization("User", "Read", "Create", "Delete", "Assign")]属性ではなくクレームを管理するためだけに、それを機能させるためのほとんどの設定作業とポリシーの豊富さのように聞こえますか?
Tseng 2015

84
私はそれをコメントしなければなりません、これはすべてカスタム認証方法を実装するよりも複雑です。承認をどのように実行したいかはわかっていますが、MVC 5で記述してMVC 6に書き込むと、コアの「もの」自体を実装するよりも理解するのが実際には複雑な多くの「完了」コードが追加されます。コードを直接書く代わりに何かを理解しようとするページの前に座ってくれます。これは、Microsoft(またはNo-Sql)以外のRDBMSを使用する人々にとっても大きな痛みです。
Felype、2015

17
私の観点からは、これはすべてのシナリオを解決するわけではありません。MVC 6以前は、カスタムのAuthorize Attributeを使用して、独自の「許可システム」を実装していました。すべてのアクションにAuthorize属性を追加して、特定の必要な権限を1つ(Enum-Valueとして)渡すことができます。権限自体は、DB内のグループ/ユーザーにマップされました。だから、ポリシーでこれを処理する方法がわかりません!?
Gerwald 2016年

43
私は、これらのコメントの他の多くの人と同様に、許可のための属性の使用がWeb API 2で可能なことに対して大いに打ち消されてきたことに非常に失望しています。残念ながら、あなたの「要件」の抽象化は、以前に使用できたケースをカバーできません基本的な承認アルゴリズムを通知する属性コンストラクターパラメーター。以前はのようなことをするのは脳死に簡単[CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]でした。コンストラクターのパラメーターを変更するだけで、単一のカスタム属性を無限の方法で使用できます。
NathanAldenSr 2016年

61
また、自称の「Lead ASP.NET security guy」が実際にマジックストリング(の意味をハッキングするIAuthorizeData.Policy)とカスタムポリシープロバイダーを使用して、この露骨な見落としを克服するのではなく、フレームワーク内で対処することを提案していることにもショックを受けます。私たちが独自の実装を作成することになっていないと思いましたか?承認をゼロから(再度)再実装する以外に、選択肢のいくつかを残しましたが、今回はWeb APIの古いAuthorize属性の利点さえありませんでした。次に、アクションフィルターまたはミドルウェアレベルでそれを行う必要があります。
NathanAldenSr 2016年

104

ASP.NET Core 2では、を継承できAuthorizeAttributeます。実装する必要もありますIAuthorizationFilter(またはIAsyncAuthorizationFilter):

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}

4
だから、あなただけのためにこれを使用することができ否定しない、許可を付与し、それを?
MEMark 2018

1
@MEMarkを付与するとは、別の承認属性をオーバーライドすることを意味しますか?
ジーザス2018

2
AFAIK、デフォルトではアクセスが許可されているため、明示的に拒否する必要があります(AuthorizeAttributeを追加するなど)。:詳細については、この質問をチェックstackoverflow.com/questions/17272422/...
gius

16
また、推奨される例では、AuthorizeAttributeから継承する必要はありません。AttributeおよびIAuthorizationFilterから継承できます。この方法では、非標準の認証メカニズムが使用されている場合に次の例外が発生しません。InvalidOperationException:authenticationSchemeが指定されておらず、DefaultChallengeSchemeが見つかりませんでした。
アナトリエ

13
OnAuthorization実装で非同期メソッドを待機する必要がある場合は、実装する必要がIAsyncAuthorizationFilterありIAuthorizationFilterます。そうしないと、フィルターが同期的に実行され、フィルターの結果に関係なくコントローラーアクションが実行されます。
Codemunkie、

34

Derek Greer GREATの回答に基づいて、列挙型でそれを行いました。

これが私のコードの例です:

public enum PermissionItem
{
    User,
    Product,
    Contact,
    Review,
    Client
}

public enum PermissionAction
{
    Read,
    Create,
}


public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(PermissionItem item, PermissionAction action)
    : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { item, action };
    }
}

public class AuthorizeActionFilter : IAuthorizationFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

        if (!isAuthorized)
        {
            context.Result = new ForbidResult();
        }
    }
}

public class UserController : BaseController
{
    private readonly DbContext _context;

    public UserController( DbContext context) :
        base()
    {
        _logger = logger;
    }

    [Authorize(PermissionItem.User, PermissionAction.Read)]
    public async Task<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}

1
これをありがとう。私はわずかに異なる実装と検証のための要求で、この記事を作成しstackoverflow.com/questions/49551047/...
アントンSwanevelder

2
MumboJumboFunction <3
Marek Urbanowicz

31

コントローラーとアクションのカスタム属性を検索する独自のAuthorizationHandlerを作成し、それらをHandleRequirementAsyncメソッドに渡すことができます。

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
        if (action != null)
        {
            attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
            attributes.AddRange(GetAttributes(action.MethodInfo));
        }

        return HandleRequirementAsync(context, requirement, attributes);
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

次に、コントローラーまたはアクションで必要なカスタム属性に使用できます。たとえば、権限の要件を追加します。カスタム属性を作成するだけです。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}

次に、ポリシーに追加する要件を作成します

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}

次に、カスタム属性のAuthorizationHandlerを作成し、前に作成したAttributeAuthorizationHandlerを継承します。ControllerおよびActionから累積されたHandleRequirementsAsyncメソッドのすべてのカスタム属性のIEnumerableが渡されます。

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

最後に、Startup.cs ConfigureServicesメソッドで、カスタムAuthorizationHandlerをサービスに追加し、ポリシーを追加します。

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Permission", policyBuilder =>
            {
                policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
            });
        });

これで、コントローラーとアクションをカスタム属性で簡単に装飾できます。

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}

1
これをできるだけ早く見ていきます。
NathanAldenSr 2016年

5
これはかなりオーバーエンジニアリングされています...パラメータを受け取る単純なAuthorizationFilterAttributeを使用して同じことを解決しました。これについて考える必要はありません。「公式」のソリューションよりも巧妙です(私はかなり貧弱です)。
Vi100 2016年

2
@ Vi100 ASP.NET CoreでAuthorizationFiltersに関する多くの情報を見つけることができませんでした。公式ドキュメントページには、現在このトピックに取り組んでいると記載されています。 docs.microsoft.com/en-us/aspnet/core/security/authorization/...
ショーン

4
@ Vi100私が知りたい、これを達成する簡単な方法がある場合は、ソリューションを共有してください。
Shawn、

2
上記のUnderlyingSystemTypeの使用に注意してください。コンパイルはできませんが、削除しても機能するようです。
ティータイム2017年

25

カスタムAuthorizeAttributeを作成するための現在のアプローチは何ですか

簡単:自分で作成しないでくださいAuthorizeAttribute

純粋な承認シナリオ(特定のユーザーのみにアクセスを制限するなど)の場合、推奨されるアプローチは、新しい承認ブロックを使用することです。 -L92

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

認証の場合、ミドルウェアレベルで処理するのが最適です。

正確に何を達成しようとしていますか?


1
ヘッダー認証でセッションIDを受け取ります。そのIDから、特定のアクションが有効かどうかがわかります。
jltrem

1
その場合、それは認可の問題ではありません。あなたの「セッションID」は、実際には呼び出し元のIDを含むトークンだと思います。これは、ミドルウェアレベルで必ず行う必要があります。
ケビン・シャレー

3
これは認証(ユーザーが誰であるかを確立する)ではありませんが、承認(ユーザーがリソースにアクセスできるかどうかを決定する)です。それで、これを解決するために私がどこを探すように提案しているのですか?
jltrem

3
@jltrem、同意、あなたが話していることは認証ではなく認可です。
bopapa_1979 2015

2
@Pinpoint私は違います。その情報を別のシステムに問い合わせます。そのシステムは、認証(ユーザーを決定)し、承認(そのユーザーがアクセスできるものを通知)します。現在、各コントローラーアクションのメソッドを呼び出して、他のシステムにセッションを検証させることで、ハッキングを行っています。これを属性によって自動的に実行させたいのですが。
jltrem

4

許可フェーズで無記名トークンを検証したい場合は、現在のセキュリティプラクティスを使用して、

これをStartup / ConfigureServicesに追加します

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

そして、これはあなたのコードベースで、

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}

public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{

    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}

コードが届かない場合は、context.Succeed(...)とにかく失敗します(401)。

そして、あなたのコントローラーであなたは使うことができます

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

JwtBearerミドルウェアがすでにこれを処理しているのに、なぜトークンの独自の検証を実行することを選択するのですか?また、認証/トークンの検証/期限切れの失敗に対する正しいコンテンツをWWW-Authenticate応答ヘッダーに入れます。認証パイプラインにアクセスしたい場合は、AddJwtBearerオプション(OnAuthenticationFailed、OnChallenge、OnMessageReceived、およびOnTokenValidated)を利用できる特定のイベントがあります。
ダレンルイス

これは、私が見た他のどのソリューションよりも非常に簡単です。特に、単純なAPIキーの使用例の場合。1つの更新:3.1では、エンドポイントのルーティングに関する問題のため、AuthorizationFilterContextへのキャストは無効になりました。HttpContextAccessorを介してコンテキストを取得する必要があります。
JasonCoder

2

現在の方法はAuthenticationHandlersです

startup.csに追加

services.AddAuthentication("BasicAuthentication").AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IUserService userService)
            : base(options, logger, encoder, clock)
        {
            _userService = userService;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");

            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }

            if (user == null)
                return AuthenticateResult.Fail("Invalid User-name or Password");

            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }

IUserServiceは、ユーザー名とパスワードがある場所で作成するサービスです。基本的に、クレームのマッピングに使用するユーザークラスを返します。

var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            }; 

次に、これらのクレームとマップしたデータをクエリできます。それらはかなりの数ですが、ClaimTypesクラスを見てください。

これを拡張メソッドで使用して、任意のマッピングを取得できます

public int? GetUserId()
{
   if (context.User.Identity.IsAuthenticated)
    {
       var id=context.User.FindFirst(ClaimTypes.NameIdentifier);
       if (!(id is null) && int.TryParse(id.Value, out var userId))
            return userId;
     }
      return new Nullable<int>();
 }

この新しい方法は、私はより良いと思います

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            var authToken = actionContext.Request.Headers.Authorization.Parameter;
            // decoding authToken we get decode value in 'Username:Password' format
            var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
            // spliting decodeauthToken using ':'
            var arrUserNameandPassword = decodeauthToken.Split(':');
            // at 0th postion of array we get username and at 1st we get password
            if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
            {
                // setting current principle
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
            }
            else
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }

    public static bool IsAuthorizedUser(string Username, string Password)
    {
        // In this method we can handle our database logic here...
        return Username.Equals("test") && Password == "test";
    }
}

この素晴らしい答えはただの魅力のように機能します!ブログ、ドキュメント、スタックで基本認証とロール承認を検索して6時間程度たどり着いた後、私が見つけた最良の回答であるため、ありがとうございました。
PiotrŚródka

@PiotrŚródka、どういたしまして。答えは少し「簡略化」されていることに注意してください。テキストに「:」があるかどうかをテストしてください。悪意のあるユーザーがインデックスで終了するだけではうまくいかないため、サービスをクラッシュさせてしまう可能性があります。範囲の例外。いつものように、外部ソースから与えられたものをテストします
Walter Vehoeven

2

これを書いている時点では、asp.netコア2以降のIClaimsTransformationインターフェイスでこれを実現できると思います。ここに投稿するのに十分共有可能な概念実証を実装しました。

public class PrivilegesToClaimsTransformer : IClaimsTransformation
{
    private readonly IPrivilegeProvider privilegeProvider;
    public const string DidItClaim = "http://foo.bar/privileges/resolved";

    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)
    {
        this.privilegeProvider = privilegeProvider;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is ClaimsIdentity claimer)
        {
            if (claimer.HasClaim(DidItClaim, bool.TrueString))
            {
                return principal;
            }

            var privileges = await this.privilegeProvider.GetPrivileges( ... );
            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));

            foreach (var privilegeAsRole in privileges)
            {
                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));
            }
        }

        return principal;
    }
}

これをコントローラーで使用[Authorize(Roles="whatever")]するには、メソッドに適切なものを追加するだけです。

[HttpGet]
[Route("poc")]
[Authorize(Roles = "plugh,blast")]
public JsonResult PocAuthorization()
{
    var result = Json(new
    {
        when = DateTime.UtcNow,
    });

    result.StatusCode = (int)HttpStatusCode.OK;

    return result;
}

今回のケースでは、すべてのリクエストにJWTであるAuthorizationヘッダーが含まれています。これはプロトタイプで、来週のプロダクションシステムではこれに非常に近いことを行うと思います。

将来の有権者は、投票するときに執筆日を考慮してください。今日の時点で、このworks on my machine.™おそらく実装でより多くのエラー処理とロギングが必要になるでしょう。


ConfigureServicesはどうですか?何かを追加する必要がありますか?
ダニエル

他で議論されているように、はい。
返金不可返品不可

1

アプリでの認証用。認可属性で渡されたパラメータに基づいてサービスを呼び出す必要がありました。

たとえば、ログインした医師が患者の予定を表示できるかどうかを確認する場合は、 "View_Appointment"をカスタム認証属性に渡し、DBサービスでその権利を確認し、結果に基づいて確認します。このシナリオのコードは次のとおりです。

    public class PatientAuthorizeAttribute : TypeFilterAttribute
    {
    public PatientAuthorizeAttribute(params PatientAccessRights[] right) : base(typeof(AuthFilter)) //PatientAccessRights is an enum
    {
        Arguments = new object[] { right };
    }

    private class AuthFilter : IActionFilter
    {
        PatientAccessRights[] right;

        IAuthService authService;

        public AuthFilter(IAuthService authService, PatientAccessRights[] right)
        {
            this.right = right;
            this.authService = authService;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var allparameters = context.ActionArguments.Values;
            if (allparameters.Count() == 1)
            {
                var param = allparameters.First();
                if (typeof(IPatientRequest).IsAssignableFrom(param.GetType()))
                {
                    IPatientRequest patientRequestInfo = (IPatientRequest)param;
                    PatientAccessRequest userAccessRequest = new PatientAccessRequest();
                    userAccessRequest.Rights = right;
                    userAccessRequest.MemberID = patientRequestInfo.PatientID;
                    var result = authService.CheckUserPatientAccess(userAccessRequest).Result; //this calls DB service to check from DB
                    if (result.Status == ReturnType.Failure)
                    {
                        //TODO: return apirepsonse
                        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
                    }
                }
                else
                {
                    throw new AppSystemException("PatientAuthorizeAttribute not supported");
                }
            }
            else
            {
                throw new AppSystemException("PatientAuthorizeAttribute not supported");
            }
        }
    }
}

APIアクションでは、次のように使用します。

    [PatientAuthorize(PatientAccessRights.PATIENT_VIEW_APPOINTMENTS)] //this is enum, we can pass multiple
    [HttpPost]
    public SomeReturnType ViewAppointments()
    {

    }

1
SignalRのハブメソッドに同じ属性を使用する場合、IActionFilterが問題になることに注意してください。SignalRハブはIAuthorizationFilterを期待しています
ilkerkaran

情報をありがとう。現在、アプリケーションでSignalRを使用していないため、SignalRでテストしていません。
Abdullah

同じ原則だと思いますが、ヘッダーの承認エントリを使用する必要があるため、実装は異なります
Walter Vehoeven

0

承認された回答(https://stackoverflow.com/a/41348219/4974715)は、「CanReadResource」がクレームとして使用されているため、現実的に保守可能または適切ではありません(ただし、実際にはIMOのポリシーである必要があります)。回答でのアプローチは、それが使用された方法でOKではありません。アクションメソッドが多くの異なるクレームセットアップを必要とする場合、その回答では、次のようなものを繰り返し記述する必要があるためです...

[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")] 
[ClaimRequirement(MyClaimTypes.AnotherPermision, "AnotherClaimVaue")]
//and etc. on a single action.

では、どれだけのコーディングが必要になるか想像してみてください。理想的には、「CanReadResource」は、ユーザーがリソースを読み取ることができるかどうかを判断するために多くのクレームを使用するポリシーであると想定されています。

私がしていることは、列挙型としてポリシーを作成し、ループしてこのように要件を設定することです...

services.AddAuthorization(authorizationOptions =>
        {
            foreach (var policyString in Enum.GetNames(typeof(Enumerations.Security.Policy)))
            {
                authorizationOptions.AddPolicy(
                    policyString,
                    authorizationPolicyBuilder => authorizationPolicyBuilder.Requirements.Add(new DefaultAuthorizationRequirement((Enumerations.Security.Policy)Enum.Parse(typeof(Enumerations.Security.Policy), policyWrtString), DateTime.UtcNow)));

      /* Note that thisn does not stop you from 
          configuring policies directly against a username, claims, roles, etc. You can do the usual.
     */
            }
        }); 

DefaultAuthorizationRequirementクラスは次のようになります...

public class DefaultAuthorizationRequirement : IAuthorizationRequirement
{
    public Enumerations.Security.Policy Policy {get; set;} //This is a mere enumeration whose code is not shown.
    public DateTime DateTimeOfSetup {get; set;} //Just in case you have to know when the app started up. And you may want to log out a user if their profile was modified after this date-time, etc.
}

public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
{
    private IAServiceToUse _aServiceToUse;

    public DefaultAuthorizationHandler(
        IAServiceToUse aServiceToUse
        )
    {
        _aServiceToUse = aServiceToUse;
    }

    protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
    {
        /*Here, you can quickly check a data source or Web API or etc. 
           to know the latest date-time of the user's profile modification...
        */
        if (_aServiceToUse.GetDateTimeOfLatestUserProfileModication > requirement.DateTimeOfSetup)
        {
            context.Fail(); /*Because any modifications to user information, 
            e.g. if the user used another browser or if by Admin modification, 
            the claims of the user in this session cannot be guaranteed to be reliable.
            */
            return;
        }

        bool shouldSucceed = false; //This should first be false, because context.Succeed(...) has to only be called if the requirement specifically succeeds.

        bool shouldFail = false; /*This should first be false, because context.Fail() 
        doesn't have to be called if there's no security breach.
        */

        // You can do anything.
        await doAnythingAsync();

       /*You can get the user's claims... 
          ALSO, note that if you have a way to priorly map users or users with certain claims 
          to particular policies, add those policies as claims of the user for the sake of ease. 
          BUT policies that require dynamic code (e.g. checking for age range) would have to be 
          coded in the switch-case below to determine stuff.
       */

        var claims = context.User.Claims;

        // You can, of course, get the policy that was hit...
        var policy = requirement.Policy

        //You can use a switch case to determine what policy to deal with here...
        switch (policy)
        {
            case Enumerations.Security.Policy.CanReadResource:
                 /*Do stuff with the claims and change the 
                     value of shouldSucceed and/or shouldFail.
                */
                 break;
            case Enumerations.Security.Policy.AnotherPolicy:
                 /*Do stuff with the claims and change the 
                    value of shouldSucceed and/or shouldFail.
                 */
                 break;
                // Other policies too.

            default:
                 throw new NotImplementedException();
        }

        /* Note that the following conditions are 
            so because failure and success in a requirement handler 
            are not mutually exclusive. They demand certainty.
        */

        if (shouldFail)
        {
            context.Fail(); /*Check the docs on this method to 
            see its implications.
            */
        }                

        if (shouldSucceed)
        {
            context.Succeed(requirement); 
        } 
     }
}

上記のコードは、データストアのポリシーへのユーザーの事前マッピングを有効にすることもできます。したがって、ユーザーのクレームを作成するときは、基本的に、ユーザーに直接または間接的に事前にマップされたポリシーを取得します(たとえば、ユーザーに特定のクレーム値があり、そのクレーム値が特定されてポリシーにマップされているため)そのクレーム値を持つユーザーにも自動マッピングを提供し、ポリシーをクレームとして登録することで、承認ハンドラで、ユーザーのクレームに要件が含まれているかどうかを簡単に確認できます。請求。これは、ポリシー要件を満たすための静的な方法です。たとえば、「名」要件は本質的に非常に静的です。そう、

[Authorize(Policy = nameof(Enumerations.Security.Policy.ViewRecord))] 

動的要件は、年齢範囲などのチェックに関するものである可能性があり、そのような要件を使用するポリシーをユーザーに事前にマッピングすることはできません。

動的ポリシーのクレームチェックの例(たとえば、ユーザーが18歳を超えているかどうかをチェックするため)は、@ blowdart(https://stackoverflow.com/a/31465227/4974715)の回答にすでに達しています

PS:私はこれを私の電話でタイプしました。タイプミスやフォーマットの欠如はご容赦ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.