在ServiceStack中进行属性级别授权的最佳方法是什么?

时间:2015-03-11 15:48:57

标签: c# .net angularjs entity-framework servicestack

我目前正在Angular中开发SPA,因此我使用ServiceStack创建了一个REST服务。我还使用ServiceStack的默认身份验证和授权解决方案,它允许我使用Authenticate属性来装饰服务,还允许我授权角色。

但是,由于我的应用程序有用户和用户拥有资源,我需要一种方法来限制非授权用户执行某些操作。此外,我希望能够为每个离散实体创建一个服务,它可以正确地找出写入数据库的安全性以及根据授权级别返回给用户的安全性。

举个例子,我们说我已经创建了一个服务来处理Group实体上的操作。我允许在Group上执行的操作之一是获取详细信息:

  • 路线:api/groups/{Id}
  • 回复:NameDescriptionCoverImageUrlMembers

但是,根据用户的身份,我希望限制返回的数据:

  • 未经过身份验证:NameCoverImageUrl
  • 经过身份验证:NameCoverImageUrlDecription
  • 请求群组成员:完全访问权限
  • 网站管理员:完全访问权限

因此,这样做的一个简单方法是创建3个不同的响应DTO,每个响应类型一个。然后在服务本身,我可以检查用户是谁,检查他们与资源的关系,并返回适当的响应。这种方法的问题在于我会重复自己,并且会创建DTO,它们只是" master"的子集。 DTO。

对我来说,理想的解决方案是通过以下属性来装饰DTO上的每个属性:

  • [CanRead("Admin", "Owner", "Member")]
  • [CanWrite("Admin", "Owner")]

然后在请求期间的某个地方,它会根据用户的身份限制写入数据库的内容,并且只会序列化" master"的子集。允许用户阅读的DTO。

有谁知道我如何在ServiceStack中获得理想的解决方案,或者更好的东西?

2 个答案:

答案 0 :(得分:3)

直接方法最简单,但您也可以利用custom filters attributes

[Route("/groups/{Id}"]
public class UpdateGroup
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string CoverImageUrl { get; set; }
    public string Description { get; set; }
}

[RequiresAnyRole("Admin", "FullAccess")]
[Route("/admin/groups/{Id}"]
public class AdminUpdateGroup
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string CoverImageUrl { get; set; }
    public string Description { get; set; }
    //... other admin properties
}

服务实施:

public object Any(UpdateGroup request)
{
    var session = base.SessionAs<AuthUserSession>();
    if (session.IsAuthenticated) {
        //.. update Name, CoverImageUrl, Description
    }
    else {
        //.. only update Name, CoverImageUrl
    }
}

public object Any(AdminUpdateGroup request)
{
    //... Full Access
}

答案 1 :(得分:0)

最终对我来说最实用的解决方案实际上非常简单。基本思想是,无论哪种服务需要行级授权,都应该实现GetUserRole方法,在我的情况下,该方法返回用户最宽松的角色。

protected string GetUserRole(Domain.Group entity)
{
    var session = SessionAs<AuthUserSession>();
    var username = session.UserName;

    if (session.Roles.Contains("Admin"))
    {
        return "Admin";
    }

    if (entity.Id == default(int) || entity.Leader.Username.Equals(username))
    {
        return "Leader";
    }

    // More logic here...

    return session.IsAuthenticated ? "User" : "Anonymous";
}

然后我可以使用用户的角色找出让他们写的内容:

var entityToWriteTo = ... // code that gets your entity
var userRole = GetUserRole(entityToWriteTo);

if (new[] {"Admin"}.Contains(userRole))
{
    // write to admin-only entity properties
}

if (new[] {"Admin", "Leader"}.Contains(userRole))
{
    // write to admin or leader entity properties
}

// Etc.

同样的逻辑适用于读取:您根据角色有条件地设置属性来填充DTO。稍后当您将DTO返回给客户端时,您未设置的任何属性都不会被序列化,也不会使用空值序列化。

最终,此解决方案允许您为资源使用单个服务,而不是使用自己的请求DTO创建多个服务。当然,你可以做的重构使这个解决方案更加简化。例如,您可以将所有读取和写入隔离到代码的一部分,这将使服务本身不受角色检查等因素的影响。