为什么使用基于声明的身份验证需要资源/操作而不是类型/值

4
我们以前的软件架构使用基于角色的验证。现在我们想要使用基于声明的授权。事实上,即使我们使用基于角色的技术,我认为我们始终使用了一些模拟声明的东西。
最低级别是特权。一个特权可以是“调用用户服务添加用户”或简称为“UserService.Add”。特权可以分配给组。用户可以是组的成员。最终,通过组成员身份,用户可以拥有一系列的特权。
旧系统使用 UserNamePasswordValidator, IAuthorizationPolicyCodeAccessSecurityAttribute 的组合来编写位于服务方法上方的属性,在调用服务方法时,会检查其有效性。如果用户没有所需特权,则访问将被拒绝。效果很好。
[CompanyRoleRequired(SecurityAction.Demand, Role = "Common.Connect")]
[CompanyRoleRequired(SecurityAction.Demand, Role = "SomeServiceName.Save")]
public void Save(IEnumerable<Data> data)
{
  // code
}

现在我想使用基于声明的授权。保持上述模型不变,我将为每个以前的特权创建一个声明,或者可能为每个具有其操作有效值的服务创建一个声明。例如,我可以添加声明“UserService”而不是“UserService.Add”,并且具有先前特权的人将获得带有值“Add”的声明。我希望向服务开发人员提供相同的访问检查便利性,因此我希望所需的声明在服务方法上方进行注释。Microsoft已经为此提供了ClaimsPrincipalPermissionAttribute
我实现了ClaimsAuthorizationManager,而不是实现IAuthorizationPolicy。 问题1)授权管理器会被调用两次。一次是用于SOAP URL,一次是用于我的属性。我已经搜索了很多,它似乎是设计如此。我没有区分调用并仅检查我的调用的问题,但也许我没有看到某些东西。是否有选项或简单的方法可以避免使用URL调用而只调用属性? 问题2) 访问检查提供了检查主体是否具有声明的能力。显然,声明具有类型/名称和值。我本以为属性会提供这样的接口。然而,该属性想要知道资源和操作。我需要覆盖的访问检查函数也需要检查资源和操作。为什么?我需要在我的AuthorizationManager中将资源/操作映射到声明吗?如果我不认为有必要,那么只需将声明的预期类型和值作为资源和操作放入属性中,并在授权管理器中进行1:1映射,这样做可以吗?如果这样做,我会错过一些重要的安全功能吗?

1
顺便问一下,为什么你在使用旧的Microsoft.IdentityModel命名空间而不是新的4.5 System.Security.Claims?http://msdn.microsoft.com/en-us/library/system.security.claims.claimsauthorizationmanager.checkaccess.aspx有一个在配置文件中使用策略的CheckAccess方法示例。 - Danila Polevshchikov
抱歉,我的链接是错的。我使用了您发布的确切类,它甚至在页面中间展示了我所说的资源/操作模型。我已经在我的问题中编辑了链接。 - nvoigt
2个回答

6

Q1) 不幸的是,由于“自动”呼叫和属性/.CheckAccess使用相同的声明类型,您无法轻松区分两者。我在这里写了一篇文章:http://leastprivilege.com/2011/04/30/what-i-dont-like-about-wifs-claims-based-authorization/

Q2) 您在这里错过了一个概念。思路是不要检查特定的要求 - 而是用“你在做什么”注释代码。编写业务代码的人通常不知道谁有资格调用它(或者需要哪些声明)。您只告诉授权策略,您即将添加客户(以此为例)。授权管理器的工作是确定负责人是否有权执行该操作(通过任何方式)。关注点分离。请参见:http://leastprivilege.com/2011/04/30/what-i-like-about-wifs-claims-based-authorization/


1

我不确定这对你是否有帮助,但是ClaimsAuthorizationManager有一个可以被覆盖的方法(LoadCustomConfiguration),你可以用它来从XML文件中加载你的策略。该策略可能被设计为允许资源、操作和角色之间的映射。我建立了一个内置的访问控制列表,看起来像这样:

public interface IAccessControlList
{
   List<CustomAccessRule> Rules { get; }
}

public class CustomAccessRule
{
    public string Operation { get; set; }
    public List<string> Roles { get; set; }

    public CustomAccessRule(string operation, params string[] roles)
    {
        Operation = operation;
        Roles = roles.ToList();
    }
}

我的索赔授权管理器看起来像这样:

public class CustomClaimsAuthorizationManager : ClaimsAuthorizationManager
{
    private IAccessControlList _accessControlList;

    public CustomClaimsAuthorizationManager(IAccessControlList accessControlList)
    {
        _accessControlList = accessControlList;
    }

    public override bool CheckAccess(AuthorizationContext context)
    {
        string operation = context.Action.First().Value.Split('/').Last();
        CustomAccessRule rule = _accessControlList.Rules.FirstOrDefault(x => x.Operation == operation);
        if (rule == null) return true;
        if (context.Principal.Identities.First().IsInRoles(rule.Roles)) return true;
        throw new MessageSecurityException(string.Format("Username {0} does not have access to operation {1}.", context.Principal.Identities.First().Name, operation));
    }

}

以下是一个服务的访问控制列表实现示例:

public class SampleServiceACL : IAccessControlList
{
    public List<CustomAccessRule> Rules { get; private set; }

    public SampleServiceACL()
    {
        Rules = new List<CustomAccessRule>();
        Rules.Add(new CustomAccessRule("OpenAccount", "Manager", "Owner"));
        Rules.Add(new CustomAccessRule("CloseAccount", "Manager", "Owner"));
        Rules.Add(new CustomAccessRule("SendEmail", "User", "Manager", "Owner"));
    }
}

我正在使用以下方法在服务主机基础级别应用此操作:

    protected override void OnOpening()
    {
        base.OnOpening();

        IdentityConfiguration identityConfiguration = new IdentityConfiguration();
        identityConfiguration.SecurityTokenHandlers.Clear();
        identityConfiguration.ClaimsAuthorizationManager = new CustomClaimsAuthorizationManager(new SampleServiceACL());
        this.Credentials.IdentityConfiguration = identityConfiguration;

        ...
    }

作为结果,我根本不使用属性,所有授权逻辑都集中在声明授权管理器上ACL。

现在,如果您不喜欢这种方法,并且仍在寻求检查特定声明的属性,则可以从CodeAccessSecurityAttribute派生并实际实现该逻辑。 MS提供的内容很好,但这并不意味着您应该坚持使用它。还可以将检查声明的逻辑实现为身份验证的扩展,即:

public static class IdentityExtensions
{
    public static bool IsInRoles(this ClaimsIdentity id, List<string> roles)
    {
        foreach (string role in roles)
            if (id.HasClaim(ClaimTypes.Role, role)) return true;
        return false;
    }
}

所以你可以构建扩展,自定义属性,然后在属性中使用扩展来执行验证逻辑。

再次强调,这只是我已经完成的某些事情。可能不是你要寻找的,但它是一种自定义解决方案。


感谢您的答案,我希望我可以选择多个答案或悬赏。 - nvoigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接