【问题标题】:Use same authorization policies for client and server in Blazor在 Blazor 中为客户端和服务器使用相同的授权策略
【发布时间】:2019-08-01 03:30:36
【问题描述】:

这个问题是我的ASP.NET Core hosted Blazor 问题报告here 的后续问题。

使用 .NET Core 3.0 的当前预览版 (6),ASP.NET Core hosted Blazor 项目模板创建以下三个项目:

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

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

对于那个用例,我在 MyProject.Shared 项目中创建了策略,之后我可以在客户端项目中使用它们。但是,它们在服务器项目中不可用。

问题:

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

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

  • 认为Microsoft.AspNetCore.App 的框架引用和NuGet 包Microsoft.AspNetCore.Authorization (3.0.0) 之间的兼容性是理所当然的吗?
  • 认为Microsoft.AspNetCore.App 的框架引用和NuGet 包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)

接着是这条神秘的错误消息:

\.nuget\packages\microsoft.aspnetcore.blazor.build\3.0.0-preview6.19307.2\targets\Blazor.MonoRuntime.targets(439,5): error MSB3073: The command "dotnet "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.blazor.mono\0.10.0-preview6.19303.4\build\netstandard1.0\../../tools/illink/illink.dll" -l none --disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true -d "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.blazor.mono\0.10.0-preview6.19303.4\build\netstandard1.0\../../tools/mono/bcl/" -d "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.blazor.mono\0.10.0-preview6.19303.4\build\netstandard1.0\../../tools/mono/bcl/Facades/" -o "S:\git\hou-units\src\UNITS\UNITS.Client\obj\Debug\netstandard2.0\blazor\linker/" -x "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.blazor.build\3.0.0-preview6.19307.2\targets\BuiltInBclLinkerDescriptor.xml" -x "S:\git\hou-units\src\UNITS\UNITS.Client\obj\Debug\netstandard2.0\blazor\linker.descriptor.xml" -a "C:\Users\Stefan\.nuget\packages\jetbrains.annotations\2019.1.3\lib\netstandard2.0\JetBrains.Annotations.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.authentication\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.authentication.abstractions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.authentication.cookies\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.Cookies.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.authentication.core\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Authentication.Core.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.authorization\3.0.0-preview6.19307.2\lib\netstandard2.0\Microsoft.AspNetCore.Authorization.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.blazor\3.0.0-preview6.19307.2\lib\netstandard2.0\Microsoft.AspNetCore.Blazor.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.components\3.0.0-preview6.19307.2\lib\netstandard2.0\Microsoft.AspNetCore.Components.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.components.browser\3.0.0-preview6.19307.2\lib\netstandard2.0\Microsoft.AspNetCore.Components.Browser.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.cryptography.internal\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Cryptography.Internal.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.cryptography.keyderivation\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Cryptography.KeyDerivation.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.dataprotection\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.DataProtection.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.dataprotection.abstractions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.DataProtection.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.hosting.abstractions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.hosting.server.abstractions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Hosting.Server.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.abstractions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.extensions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.features\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Features.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.identity\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Identity.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.aspnetcore.metadata\3.0.0-preview6.19307.2\lib\netstandard2.0\Microsoft.AspNetCore.Metadata.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.webutilities\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.WebUtilities.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.configuration\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.configuration.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.configuration.binder\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.dependencyinjection\3.0.0-preview6.19304.6\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\3.0.0-preview6.19304.6\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.fileproviders.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.FileProviders.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.hosting.abstractions\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Hosting.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.identity.core\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Identity.Core.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.logging\2.2.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.logging.abstractions\3.0.0-preview6.19304.6\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.objectpool\2.2.0\lib\netstandard2.0\Microsoft.Extensions.ObjectPool.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.options\3.0.0-preview6.19304.6\lib\netstandard2.0\Microsoft.Extensions.Options.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.extensions.primitives\3.0.0-preview6.19304.6\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.webencoders\2.2.0\lib\netstandard2.0\Microsoft.Extensions.WebEncoders.dll" -a "C:\Users\Stefan\.nuget\packages\microsoft.jsinterop\3.0.0-preview6.19304.6\lib\netstandard2.0\Microsoft.JSInterop.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.net.http.headers\2.2.0\lib\netstandard2.0\Microsoft.Net.Http.Headers.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.win32.registry\4.5.0\lib\netstandard2.0\Microsoft.Win32.Registry.dll" -a "C:\Users\Stefan\.nuget\packages\mono.webassembly.interop\3.0.0-preview6.19304.6\lib\netstandard2.0\Mono.WebAssembly.Interop.dll" -a "C:\Users\Stefan\.nuget\packages\newtonsoft.json\12.0.2\lib\netstandard2.0\Newtonsoft.Json.dll" -a "C:\Users\Stefan\.nuget\packages\system.buffers\4.5.0\lib\netstandard2.0\System.Buffers.dll" -a "C:\Users\Stefan\.nuget\packages\system.componentmodel.annotations\4.6.0-preview6.19303.8\lib\netstandard2.0\System.ComponentModel.Annotations.dll" -a "C:\Users\Stefan\.nuget\packages\system.memory\4.5.3\lib\netstandard2.0\System.Memory.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\system.numerics.vectors\4.5.0\lib\netstandard2.0\System.Numerics.Vectors.dll" -a "C:\Users\Stefan\.nuget\packages\system.runtime.compilerservices.unsafe\4.6.0-preview6.19303.8\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\system.security.accesscontrol\4.5.0\lib\netstandard2.0\System.Security.AccessControl.dll" -a "C:\Users\Stefan\.nuget\packages\system.security.cryptography.cng\4.4.0\lib\netstandard2.0\System.Security.Cryptography.Cng.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\system.security.cryptography.pkcs\4.5.0\lib\netstandard2.0\System.Security.Cryptography.Pkcs.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\system.security.cryptography.xml\4.5.0\lib\netstandard2.0\System.Security.Cryptography.Xml.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\system.security.permissions\4.5.0\lib\netstandard2.0\System.Security.Permissions.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\system.security.principal.windows\4.5.0\lib\netstandard2.0\System.Security.Principal.Windows.dll" -a "C:\Program Files\dotnet\sdk\NuGetFallbackFolder\system.text.encodings.web\4.5.0\lib\netstandard2.0\System.Text.Encodings.Web.dll" -a "C:\Users\Stefan\.nuget\packages\system.text.json\4.6.0-preview6.19303.8\lib\netstandard2.0\System.Text.Json.dll" -a "C:\Users\Stefan\.nuget\packages\system.threading.tasks.extensions\4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll" -a "S:\git\hou-units\src\UNITS\UNITS.Shared\bin\Debug\netstandard2.0\UNITS.Shared.dll" -a "S:\git\hou-units\src\UNITS\UNITS.Client\obj\Debug\netstandard2.0\UNITS.Client.dll"" exited with code -532462766.

可以在on GitHub找到示例项目。

【问题讨论】:

    标签: c# blazor asp.net-core-3.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.
    

    上面这两个代码sn-ps是为了满足你对dynamically show or hide views or parts of views的需求

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

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

    希望这会有所帮助...

    【讨论】:

    • 感谢 Isaac 的回复,但在我的问题中,我并不是在问如何在 *.razor 文件中使用授权。我知道我必须写&lt;AuthorizeView Policy="content-editor"&gt;。我在询问客户端了解此策略的方式 - 据我所知,它伴随着在客户端而不是服务器中注册授权策略。这让我想到了关于共享政策的实际问题。
    • 更具体地说,感谢您的链接,客户端中的AuthorizationPolicyProvider of the AuthorizeViewCore 如何使用与服务器相同的策略?在观察我的问题中列出的依赖障碍时。
    • 我认为你必须添加 services.AddAuthorizationCore();在您的客户端项目的 Startup.ConfigureServices 方法中。
    • 是的,这也很好,没有问题。我对原始问题进行了编辑,指出将所有内容放入共享项目时会发生什么。恕我直言,IdentityConstants 不可用仍然是一团糟。
    【解决方案2】:

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

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

    先决条件

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

    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。创建扩展方法

    此扩展方法自动将您的Policies 类中的所有public 策略应用到services.AddAuthorizationCore 方法中的AuthorizationOptions(参见步骤#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-policy 将阻止访问您的受保护资源,同时仍为您预期的策略名称提供策略。这样一来,您就不会在浏览器或 Visual Studio 控制台中出现“找不到策略”消息。

    当然,如果您愿意,您可以随时删除日志记录。

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

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

    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>
    

    【讨论】:

      猜你喜欢
      • 2019-10-03
      • 2016-05-20
      • 1970-01-01
      • 2011-09-06
      • 2013-08-23
      • 1970-01-01
      • 2020-01-27
      • 2023-01-29
      • 2011-07-02
      相关资源
      最近更新 更多