对Blazor中的客户端和服务器使用相同的授权策略

时间:2019-07-22 17:06:18

标签: c# blazor asp.net-core-3.0

此问题是我here报道的ASP.NET Core hosted Blazor问题的后续内容。

使用{.1}项目模板,使用.NET Core 3.0的当前预览(6),创建以下三个项目:

  • MyProject.Client( .NET Standard 2.0
  • MyProject.Server( .NET Core 3.0
  • MyProject.Shared( .NET Standard 2.0

我想在服务器项目(以保护端点)以及客户端项目(以动态显示或隐藏视图或视图的一部分)中启用授权。

对于该用例,我在ASP.NET Core hosted Blazor项目中创建了策略,然后可以在客户端项目中使用它们。但是,它们在服务器项目中不可用。

问题:

    • .NET Core 3.0授权策略源自MyProject.Shared定义的Microsoft.AspNetCore.Authorization.AuthorizationPolicy
    • .NET Standard 2.0授权策略派生自<FrameworkReference Include="Microsoft.AspNetCore.App" />定义的Microsoft.AspNetCore.Authorization.AuthorizationPolicy
    • <PackageReference Include="Microsoft.AspNetCore.Authorization" [...] />构造函数需要一组身份验证方案。
    • 对于.NET Core 3.0,这些定义在AuthorizationPolicy定义的Microsoft.AspNetCore.Identity.IdentityConstants中。
    • 对于.NET Standard 2.0,这些定义在<FrameworkReference Include="Microsoft.AspNetCore.App" />定义的Microsoft.AspNetCore.Identity.IdentityConstants中。但是,该软件包将无法提供比<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />更高的版本,例如.NET Core 3.0版本中的Microsoft decided to discontinue certain packages,而是将这些类移至2.2.0中。

我的问题:
在服务器和客户端项目中使用相同策略而不为.NET Standard和.NET Core定义两次的正确方法是什么?

  • 是否认为框架参考<FrameworkReference Include="Microsoft.AspNetCore.App" />与NuGet软件包Microsoft.AspNetCore.App之间具有兼容性?
  • 是否认为框架参考Microsoft.AspNetCore.Authorization (3.0.0)与NuGet软件包Microsoft.AspNetCore.App之间具有兼容性?

编辑:为了在Microsoft.AspNetCore.Identity (2.2.0)项目中正确设置共享策略,我必须引用MyProject.Shared程序包-包括Microsoft.AspNetCore.Identity (2.2.0)。 但是,这会导致IdentityConstants.ApplicationScheme的构建失败:

MyProject.Client

随后显示此错误消息:

2>Processing embedded resource linker descriptor: mscorlib.xml
2>Duplicate preserve in resource mscorlib.xml in mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e of System.Threading.WasmRuntime (All).  Duplicate uses (All)
2>Type Mono.ValueTuple has no fields to preserve
2>Type System.Reflection.Assembly has no fields to preserve
2>Fatal error in IL Linker
2>
2>Unhandled Exception: Mono.Linker.MarkException: Error processing method: 'System.Void System.Security.Permissions.PrincipalPermission::Demand()' in assembly: 'System.Security.Permissions.dll' ---> Mono.Cecil.ResolutionException: Failed to resolve System.Security.Principal.IPrincipal System.Threading.Thread::get_CurrentPrincipal()
2>   at Mono.Linker.Steps.MarkStep.HandleUnresolvedMethod(MethodReference reference)
2>   at Mono.Linker.Steps.MarkStep.MarkMethod(MethodReference reference)
2>   at Mono.Linker.Steps.MarkStep.MarkInstruction(Instruction instruction)
2>   at Mono.Linker.Steps.MarkStep.MarkMethodBody(MethodBody body)
2>   at Mono.Linker.Steps.MarkStep.ProcessMethod(MethodDefinition method)
2>   at Mono.Linker.Steps.MarkStep.ProcessQueue()
2>   --- End of inner exception stack trace ---
2>   at Mono.Linker.Steps.MarkStep.ProcessQueue()
2>   at Mono.Linker.Steps.MarkStep.ProcessPrimaryQueue()
2>   at Mono.Linker.Steps.MarkStep.Process()
2>   at Mono.Linker.Steps.MarkStep.Process(LinkContext context)
2>   at Mono.Linker.Pipeline.ProcessStep(LinkContext context, IStep step)
2>   at Mono.Linker.Pipeline.Process(LinkContext context)
2>   at Mono.Linker.Driver.Run(ILogger customLogger)
2>   at Mono.Linker.Driver.Execute(String[] args, ILogger customLogger)
2>   at Mono.Linker.Driver.Main(String[] args)

可以找到示例项目on GitHub

2 个答案:

答案 0 :(得分:1)

我建议您使用内置于Blazor框架中的身份验证和授权系统。参见https://github.com/aspnet/AspNetCore/tree/master/src/Components/Components/src/Auth

您可以使用支持基于策略的授权的组件

示例:服务器端或客户端Blazor

<AuthorizeView Policy="content-editor">
    You can only see this if you satify the "content-editor" policy.
</AuthorizeView>

使用[Authorize]属性

@page "/"
@attribute [Authorize(Policy = "content-editor")]

You can only see this if you satisfy the 'content-editor' policy.

上面的这两个代码段是为了满足您对dynamically show or hide views or parts of views的需求

当然,您需要在服务器上执行授权检查,以保护客户端应用程序访问的API端点

在文档中查找更多详细信息和示例。这是一个了不起的系统,您可以使用它轻松地对用户进行身份验证和授权。

希望这对您有帮助...

答案 1 :(得分:0)

阅读Chris Sainty's recent article about policy-based authorization后,我想到的正确方法是从类型化策略切换为使用其他重载,从而允许配置策略。

我没有为每个需要AuthenticationScheme的策略创建一个类,而是使用AuthorizationOptions.AddPolicy(string name, AuthorizationPolicy policy)方法,而是实现了一个扩展方法,该扩展方法调用AuthorizationOptions.AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)

先决条件

  • 使用ASP.NET Core托管的Blazor模板。
  • 在共享项目中,您必须引用版本大于或等于Microsoft.AspNetCore.Authorization的NuGet软件包3.0.0

1。定义政策

在共享项目中,创建一个名为Policies的类,您可以在其中使用配置方法定义所有策略。配置方法应使用您的策略名称-例如:

public static class Policies
{
    // This policy is internal and required for the extension method explained further below.
    // You CANNOT use this policy in your controllers or pages.
    internal static void PolicyConfigurationFailedFallback(AuthorizationPolicyBuilder builder)
        => builder.RequireAssertion(context => false);

    public static void TimeMustBeEvening(AuthorizationPolicyBuilder builder)
        => builder.RequireAuthenticatedUser()
                  .RequireAssertion(context => DateTime.UtcNow.Hour >= 18);
}

2。创建扩展方法

此扩展方法自动将public方法中的Policies类中的所有 AuthorizationOptions 策略应用于services.AddAuthorizationCore的{​​{1}}(请参阅步骤#3 ):

public static class AuthorizationOptionsExtension
{
    public static void AddSharedPolicies(this AuthorizationOptions options,
                                         ILogger logger)
    {
        logger.LogInformation($"{nameof(AuthorizationPolicy)} Configuration started ...");
        var policies = FindPolicies();
        options.TryToAddPolicies(policies, logger);
        logger.LogInformation($"{nameof(AuthorizationPolicy)} Configuration completed.");
    }

    private static IEnumerable<PolicyInformation> FindPolicies()
    {
        var policyProvider = typeof(Policies);
        return from method in policyProvider.GetMethods(BindingFlags.Public | BindingFlags.Static)
               // The method should configure the policy builder, not return a built policy.
               where method.ReturnType == typeof(void)
               let methodParameter = method.GetParameters()
               // The method has to accept the AuthorizationPolicyBuilder, and no other parameter.
               where methodParameter.Length == 1 && methodParameter[0].ParameterType == typeof(AuthorizationPolicyBuilder)
               select new PolicyInformation(method.Name, method);
    }

    private static void TryToAddPolicies(this AuthorizationOptions options,
                                         IEnumerable<PolicyInformation> policies,
                                         ILogger logger)
    {
        foreach (var policy in policies)
        {
            try
            {
                options.AddPolicy(policy.Name, builder => { policy.Method.Invoke(null, new object[] {builder}); });
                logger.LogInformation($"Policy '{policy.Name}' was configured successfully.");
            }
            catch (Exception e)
            {
                options.AddPolicy(policy.Name, Policies.PolicyConfigurationFailedFallback);
                logger.LogCritical(e, $"Failed to configure policy '{policy.Name}'. Using fallback policy instead.");
            }
        }
    }

    private class PolicyInformation
    {
        internal string Name { get; }

        internal MethodInfo Method { get; }

        internal PolicyInformation(string name,
                                   MethodInfo method)
        {
            Name = name;
            Method = method;
        }
    }
}

第1步中的PolicyConfigurationFailedFallback策略将阻止对您受保护资源的访问,同时仍为您期望的策略名称提供策略。这样,您就不会最终将浏览器或Visual Studio控制台与“未找到策略”消息群集在一起。

当然,您可以随时取消记录。

3。在客户端和服务器中配置服务

使用扩展方法,两者 ConfigureServices类(客户端和服务器)中的Startup方法都非常缩小:

public void ConfigureServices(IServiceCollection services)
{
    /* ... */

    // For the server-side Startup class you can get the ILogger from the constructor instead.
    // For the client-side Startup class you have to get the ILogger here.
    var logger = services.BuildServiceProvider().GetService<ILogger<Startup>>();
    services.AddAuthorizationCore(options => options.AddSharedPolicies(logger));

    /* ... */
}

4。在控制器中使用策略

简单明了,无需定义包含策略名称的另一个属性,只需使用nameof()运算符即可获取策略名称:

[Authorize(nameof(Policies.TimeMustBeEvening))]
[Route("api/[controller]")]
public class SampleDataController : Controller
{
   /* ... */
}

5。在Blazor页面和组件中使用该策略

类似于将策略应用于控制器的方式,可以在Blazor页面和组件中使用nameof()运算符。

整个Blazor页面:

@page "/fetchdata"
@attribute [Authorize(Policy = nameof(Policies.NameMustBeBlazorAdmin))]

或者对于使用AuthorizeView组件的单个零件:

<AuthorizeView Policy="@nameof(Policies.NameMustBeBlazorAdmin)">
<th>Temp. (F)</th>
<th>Summary</th>
</AuthorizeView>