如何编写异步策略处理程序,注入作用域服务

How to write an asynchronous Policy handler, injecting a scoped service

我正在尝试使用自定义身份存储提供程序为 ASP.NET Core 3.1 Web 应用程序编写自定义策略。

当我在 MSDN Article:

once you hold a reference to the user, you can always find the username from the claims and run a query against any database or external service

我开始编写自己的策略(截至目前是一个简单的角色要求),将 UserManager 注入构造函数:

public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
    private UserManager<AppUser> UserManager;

    public RoleHandler(UserManager<AppUser> usermanager)
    {
        UserManager = usermanager;
    }
}

现在我有几个问题:

在单例中注入作用域服务

策略应该在整个应用程序生命周期内持续存在,因此这将是一个单例:

services.AddSingleton<IAuthorizationHandler, RoleHandler>();

但是在策略服务器中注入的 UserManager 是一个范围内的服务,这是不允许的。解决方案非常简单,将策略服务的配置从单例更改为范围服务

services.AddScoped<IAuthorizationHandler, RoleHandler>();

但我不知道这是否会导致任何问题。

编写异步策略处理程序

这是我对 HandleRequirementAsync 方法的实现:

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
    AppUser user = UserManager.FindByIdAsync(context.User.Identity.Name).Result;

    if (user != null)
    {
        bool result = UserManager.IsInRoleAsync(user, requirement.Role.ToString()).Result;
        if (result) context.Succeed(requirement);
    }

    return Task.CompletedTask;
}

我使用了 Task.Result 但它阻塞了线程。我不能使用 await,因为那样会使方法返回 Task<Task> 而不是 Task,而且我无法更改它。我该如何解决这个问题?

不要 return Task.CompletedTask.

当您将方法声明为 async 时,它 隐式 return 在第一个 await 被命中时 Task :

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
    AppUser user = await UserManager.FindByIdAsync(context.User.Identity.Name);

    if (user != null)
    {
        bool result = await UserManager.IsInRoleAsync(user, requirement.Role.ToString());
        if (result) context.Succeed(requirement);
    }
}

Task.CompletedTask 通常在需要同步实现 Task returning 方法时使用,而你不是。

我的 HandleRequirementAsync 还调用 httpClient.GetAsync(Blazor 服务器,.NET 5),将异步添加到 HandleRequirementAsync 并执行 await hpptClient.GetAsync() 来破坏授权。使用带有延迟的异步方法,尝试在浏览器中输入路由地址,它会重定向到未授权的页面,即使 context.Succeed(requirement) 已执行。

对我来说可行的解决方案是保持 HandleRequirementAsync 不变,返回 Task.CompletedTask。对于我们需要调用的异步方法,只需要使用从非异步方法调用异步方法的模式即可。

我用的是

我的示例异步方法:

public async Task<IList<Permission>> GetGroupPermissions(int userId)
{
    HttpResponseMessage response = await _httpClient.GetAsync(string.Format("Auth/GroupPermissions/{0}", userId));

    try
    {
        var payload = await response.Content.ReadFromJsonAsync<List<Permission>>();
        response.EnsureSuccessStatusCode();

        return payload;
    }
    catch
    {
        return new List<Permission>();
    }

}

HandleRequirementAsync:

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
    var t2 = (Task.Run(() => GetGroupPermissions(userId)));
    t2.Wait();
    var userGroupPermissions = t2.Result;
    

    if (!userGroupPermissions.Contains(requirement.Permission))
    {
        //context.Fail(); //no need to fail, other requirement might success
        return Task.CompletedTask;
    }

    context.Succeed(requirement);

    return Task.CompletedTask;
}