在 ServiceStack 中进行 属性 级别授权的最佳方式?

Best way to do property level authorization in ServiceStack?

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

但是,由于我的应用程序有用户,并且用户拥有资源,所以我需要一种方法来限制未授权用户执行某些操作。此外,我希望能够为每个离散实体创建一个服务,该服务可以根据用户的授权级别正确地确定写入数据库的内容是安全的,对用户 return 来说是安全的。

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

但是,根据用户是谁,我希望限制哪些数据是 returned:

因此,一种简单的方法是创建 3 个不同的响应 DTO,每个响应类型对应一个。然后在服务本身中,我可以检查用户是谁,检查他们与资源的关系,以及 return 适当的响应。这种方法的问题是我会重复很多次,并且会创建 DTO,这些 DTO 只是 "master" DTO 的子集。

对我来说,理想的解决方案是用一些方法来装饰 DTO 上的每个 属性,属性如下:

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

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

直接方法最简单,但您也可以利用 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
}

最终对我来说最实用的解决方案实际上非常简单。基本思想是,无论哪个服务需要行级授权,都应该实现一个 GetUserRole 方法,在我的例子中,return 是用户最许可的角色。

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。稍后当您 return 将 DTO 返回给客户端时,您尚未设置的任何属性都不会被序列化或将被序列化为空值。

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