将RequestFilter数据添加到Context(请求范围),在服务中检索

时间:2014-01-04 18:51:57

标签: authentication servicestack

我为我的服务实施了Basic Auth。由于ServiceStack的AuthFeature与会话概念紧密结合,因此我实现了一个自定义RequestFilter,它执行无状态基本身份验证(凭据在每个请求中都进入)。我们的身份验证策略在内部考虑角色和权限。

除了身份验证之外,我们还需要强制执行授权(例如,用户正在操纵他拥有的产品)。我们正在使用FluentValidation进行所有服务验证。

授权验证包括使用请求参数交叉检查身份验证数据。问题是,我应该在哪里放置BasicAuthRequestFilter中生成的身份验证数据?我应该在高速缓存中键入值对,例如,将RequestContext(或唯一标识请求范围的任何其他对象)与Authentication对象相关联吗?

我可以在请求Dto中插入AuthData,这可以直接在RequestFilter处获得,但这会弄乱我们的服务合同设计。我们在一个单独的DLL中定义dtos,其中只定义了服务输入/输出细节。

有什么建议吗? 提前谢谢。

1 个答案:

答案 0 :(得分:4)

我也使用自己的自定义身份验证机制,并为我的服务提供自定义角色信息。我通过在自定义ServiceRunner中对请求进行身份验证来执行此操作,然后可以将信息直接传递到我的自定义Service基础。这最终意味着访问有关用户权限的信息非常容易。

创建自定义ServiceRunner

public class ServiceRunner<T> : ServiceStack.ServiceHost.ServiceRunner<T>
{
    public ServiceRunner(IAppHost appHost, ActionContext actionContext) : base(appHost, actionContext)
    {
    }

    public override object Execute(IRequestContext requestContext, object instance, T request)
    {
        // Check if the instance is of type AuthenticatedBase
        var authenticatedBase = instance as AuthenticatedBase;

        // If the request is not using the AuthenticatedBase, then allow it to run, as normal.
        if(authenticatedBase == null)
            return base.Execute(requestContext, instance, request);

        /* 
         * Authentication required. Do you authorization check here.
         * i.e.
         * var authorization = requestContext.GetHeader("Authorization");
         * bool authorised = ... some condition;
        */

        /* You have access to your service base so if you injected the Db connection
         * in you app config using IoC, then you can access the Db here.
         * i.e.
         * authenticatedBase.Db
         */

         /*
          * Not authorized?
          * throw new UnauthorizedException();
          */

         /*
          * If authorized:
          * Then simple set the details about their permissions
          */

         authenticatedBase.AuthData = new AuthData { Id = 123, Roles = [], Username = "" };

        // Pass back the authenticated base
        return base.Execute(requestContext, authenticatedBase, request);
    }
}

通过将应用程序添加到AppHost

,将应用程序配置为使用它
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
    return new ServiceRunner<TRequest>(this, actionContext);
}

创建一个自定义类来保存您的身份验证数据,即用户会话信息,例如:

public class AuthData
{
    public int Id { get; set; }
    public string Username { get; set; }
    public int[] Roles { get; set; }
    ...
}

然后创建自定义服务基础

public class AuthenticatedBase : Service
{
    public AuthData AuthData { get; set; }
}

然后在服务中使用AuthData只是扩展AuthenticatedBase

的情况
public class CustomerHandler : AuthenticatedBase
{
    public object Get(ListCustomers request)
    {
        // You can access the AuthData now in the handler
        var roles = AuthData.Role; // Check they have the role required to list customers
        ...
    }
}

您可能想知道为什么在ServiceRunner上使用RequestFilter会遇到麻烦,但主要优点是可以直接访问服务基础的实例,这是不可用的到RequestFilter。

RequestFilter在服务基础实例化之前运行,因此您无法从那里填充它。 See order of operations for more information

通过访问ServiceBase,我们可以填充值(在本例中为AuthData),并且我们可以访问注入的依赖项,例如数据库连接。

我希望你觉得这很有用。您应该能够将大部分现有RequestFilter复制到服务运行器中。如果您需要任何进一步的帮助,请告诉我。


更新以支持属性:

由于您无法避免使用属性方法来处理身份验证需求,因此您仍然可以使用此方法:

  • 继续按照以前的方式进行身份验证和访问过滤。
  • 在您现有的身份验证机制中,使用req.Items.Add设置AuthData,即其中req是您的请求对象

    req.Items.Add("AuthData", new AuthData { Username = "", Roles = [] ... });
    
  • 然后访问服务基地中的AuthData项目:

    public class AuthenticatedBase : Service
    {
        public AuthData AuthData 
        { 
            get { return base.Request.Items["AuthData"] as AuthData; }
        }
    }