ASP.NET Core 基于策略的身份验证不起作用

Mik*_*oud 4 c# asp.net-mvc authorization asp.net-core

我必须在本地数据库中管理用户角色,而不是在 Azure AD 中。但是,我还需要对控制器进行基于策略的授权,因为我们既有管理区域又有客户区域。

Session为了处理这个问题,我添加了一个授权过滤器,该过滤器从数据库加载用户的角色,Identity向 中添加Principal,然后继续移动。这Identity会添加适当的Role Claim.

在离开授权过滤器之前,按预期IsInRole返回true,并且有两个Identities.

我的授权过滤器如下所示:

public class MyAuthFilter : IAsyncAuthorizationFilter
{
    private readonly IUserService userService;

    public MyAuthFilter(IUserService userService)
    {
        this.userService = userService;
    }

    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (user.Identity.IsAuthenticated)
        {
            AuthUserViewModel authUserViewModel;

            var sessionViewModelJson = context.HttpContext.Session.GetString(user.AzureObjectId());
            if (string.IsNullOrEmpty(sessionViewModelJson))
            {
                authUserViewModel = await ConstructSessionViewModel(context);
            }
            else
            {
                authUserViewModel = JsonConvert.DeserializeObject<AuthUserViewModel>(sessionViewModelJson);
            }

            user.AddIdentity(authUserViewModel?.Role);
        }
    }

    private async Task<AuthUserViewModel> ConstructSessionViewModel(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;
        var parsedObjectId = Guid.Parse(user.AzureObjectId());

        var findUserResult = await userService.FindByAzureObjectId(new FindByAzureObjectIdRequest
        {
            AzureObjectId = parsedObjectId
        });

        if (findUserResult.Success)
        {
            var userModel = findUserResult.User;

            var viewModel = new AuthUserViewModel
            {
                AzureObjectId = parsedObjectId,
                UserId = userModel.Id,
                SchoolId = userModel.SchoolId.GetValueOrDefault(),
                Name = userModel.Name,
                Email = userModel.Email,
                PhoneNumber = userModel.PhoneNumber,
                Role = userModel.Role
            };

            context.HttpContext.Session.SetString(user.AzureObjectId(), JsonConvert.SerializeObject(viewModel));

            return viewModel;
        }

        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

AddIdentity扩展方法如下所示:

public static void AddIdentity(this ClaimsPrincipal principal, string role)
{
    if (string.IsNullOrWhiteSpace(role) || principal.IsInRole(role))
    {
        return;
    }

    switch (role)
    {
        case Roles.School:
            principal.AddIdentity(new SchoolIdentity());
            break;
        case Roles.Admin:
            principal.AddIdentity(new AdminIdentity());
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

在本例中,SchoolIdentity添加的是:

public class SchoolIdentity : ClaimsIdentity
{
    public SchoolIdentity()
    {
        AddClaim(new SchoolPortalClaim());
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,SchoolPortalClaim看起来像这样:

public class SchoolPortalClaim : Claim
{
    public SchoolPortalClaim() : base(ClaimTypes.Role, "School")
    {
    }

    public SchoolPortalClaim(BinaryReader reader) : base(reader)
    {
    }

    public SchoolPortalClaim(BinaryReader reader, ClaimsIdentity subject) : base(reader, subject)
    {
    }

    protected SchoolPortalClaim(Claim other) : base(other)
    {
    }

    protected SchoolPortalClaim(Claim other, ClaimsIdentity subject) : base(other, subject)
    {
    }

    public SchoolPortalClaim(string type, string value) : base(type, value)
    {
    }

    public SchoolPortalClaim(string type, string value, string valueType) : base(type, value, valueType)
    {
    }

    public SchoolPortalClaim(string type, string value, string valueType, string issuer) : base(type, value, valueType, issuer)
    {
    }

    public SchoolPortalClaim(string type, string value, string valueType, string issuer, string originalIssuer) : base(type, value, valueType, issuer, originalIssuer)
    {
    }

    public SchoolPortalClaim(string type, string value, string valueType, string issuer, string originalIssuer, ClaimsIdentity subject) : base(type, value, valueType, issuer, originalIssuer, subject)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

当策略执行时就会出现问题:

services.AddAuthorization(options =>
{
    options.AddPolicy(Policies.School,
        policy => policy.RequireAssertion(
            context => context.User.IsInRole(Roles.School)));
});
Run Code Online (Sandbox Code Playgroud)

没有授权过滤context.UserIdentity添加的。

我如何让它向下游移动?

Controller问题的看起来像这样:

[Area(Areas.School)]
[Authorize(Policy = Policies.School)]
public class HomeController : BaseController
{
    public HomeController(IUserService userService) :
        base(userService)
    {
    }

    public IActionResult Index()
    {
        return RedirectToAction("Index", "Presentation", new {Area = "School"});
    }
}
Run Code Online (Sandbox Code Playgroud)

Kir*_*kin 6

这里的根本问题是RequireAssertion回调在之前被调用IAsyncAuthorizationFilter.OnAuthorizationAsync,这意味着ClaimsIdentity您添加的内容OnAuthorizationAsync尚未在您需要时添加。

IClaimsTransformation您可以使用声明一个方法的自定义实现,而不是使用自定义 authz 过滤器TransformAsync。此方法获取当前值,并允许您根据需要ClaimsPrincipal返回相同的或新的。ClaimsPrincipal

这是一个骨架示例:

public class MyClaimsTransformation : IClaimsTransformation
{
    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        // Your existing logic to add the relevant ClaimsIdentity.
        // You might want to check if the ClaimsPrincipal already contains either
        // SchoolIdentity or AdminIdentity here, as this operation may run
        // more than once.
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

要注册此实现,请在以下位置使用类似的内容ConfigureServices

services.AddSingleton<IClaimsTransformation, MyClaimsTransformation>();
Run Code Online (Sandbox Code Playgroud)