使用基于用户的连接字符串的自定义身份验证正在创建不正确的IDbConnectionFactory

时间:2014-12-19 14:29:23

标签: servicestack

随着越来越多的用户开始使用我们的网络服务,这个问题最近出现在我们的生产环境中。我对此服务进行身份验证的目标是在我们的数据库中拥有一个Web服务用户列表,每个用户都可以访问一个单独的数据库(并通过扩展名单独的连接字符串)。

我们有这样的布局: MasterDB Customer1DB Customer2DB Customer3DB 等。

在MasterDB中,除了需要使用的连接字符串之外,我们还存储了用于访问webservice的所有用户。我所经历的(我通过日志记录得出结论)是Customer1将登录,在创建dbFactory时将使用正确的连接字符串,但在我的服务类中使用dbFactory时,它将使用以前用户的数据库

我不确定如何处理这个问题,因为我对ServiceStack很缺乏经验。下面是我的Global.asax,我的CustomAuthUserSession类,以及一个看到此问题的示例服务:

配置中的Global.asax

private void HandleAuthentication(Funq.Container container)
{
    var userRepo = GetUserRepository(container);
    CreateUserAuthentications(userRepo);
}

private InMemoryAuthRepository GetUserRepository(Funq.Container container)
{
    Plugins.Add(new AuthFeature(
        () => new CustomAuthUserSession(container),
        new IAuthProvider[] { new BasicAuthProvider(), }));

    var userRepo = new InMemoryAuthRepository();
    container.Register<IUserAuthRepository>(userRepo);
    container.Register<ICacheClient>(new MemoryCacheClient());

    return userRepo;
}

private void CreateUserAuthentications(InMemoryAuthRepository userRepo)
{
    IDbConnectionFactory dbFactoryMaster = new OrmLiteConnectionFactory(
        "Data Source=www.oursite.com;Initial Catalog=Master;User=User;Password=asdf1234;enlist=false;",
        SqlServerOrmLiteDialectProvider.Instance);

    using (IDbConnection db = dbFactoryMaster.OpenDbConnection())
    {
        try 
        {
            var users = db.Select<tbl_WebServiceUsers>();

            foreach (tbl_WebServiceUsers user in users)
            {
                tbl_Instances instance = db.Select<tbl_Instances>(u => u.InstanceID == user.InstanceID)[0];
                string hash;
                string salt;

                new SaltedHash().GetHashAndSaltString(user.Password, out hash, out salt);
                userRepo.CreateUserAuth(new UserAuth
                {
                    Id = user.ServiceUserID,
                    UserName = user.UserName,
                    PasswordHash = hash,
                    Salt = salt,
                    RefIdStr = instance.InstanceConnectionString
                }, user.Password);
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

CustomAuthUserSession.cs

using ServiceStack;
using ServiceStack.Auth;
using ServiceStack.Data;
using ServiceStack.Logging;
using ServiceStack.OrmLite;
using ServiceStack.OrmLite.SqlServer;
using ServiceStack.Text;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Web;

namespace GrowerLiveAPI.App
{
    public class CustomAuthUserSession : AuthUserSession
    {
        public string ConnectionString { get; set; }
        public Funq.Container container;

        public CustomAuthUserSession(Funq.Container container)
        {
            this.container = container;
        }

        public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            base.OnAuthenticated(authService, session, tokens, authInfo);
            var service = authService.ResolveService<IUserAuthRepository>();
            var userAuth = service.GetUserAuth(session, tokens);
            ConnectionString = userAuth.RefIdStr;

            authService.SaveSession(this, SessionFeature.DefaultSessionExpiry);

            var log = LogManager.GetLogger(GetType());
            log.Info("Authenticated to webservice using: {0}".Fmt(JsonSerializer.SerializeToString(session.UserAuthName)));

            HandleConnection();
        }

        private void HandleConnection()
        {
            ModifyConnectionString();

            var log = LogManager.GetLogger(GetType());
            log.Info("Connected to database using: {0}".Fmt(JsonSerializer.SerializeToString(ConnectionString)));

            var dbFactory = new OrmLiteConnectionFactory(ConnectionString, SqlServerOrmLiteDialectProvider.Instance);
            container.Register<IDbConnectionFactory>(dbFactory);
        }

        private void ModifyConnectionString()
        {
            string mode = ConfigurationManager.AppSettings["DevelopmentMode"].ToUpper();

            if (mode == "DEVELOPMENT")
            {
                ConnectionString = ConnectionString.Replace("localaddress", "remoteaddress");
            }
            else if (mode == "STAGING")
            {
                ConnectionString = ConfigurationManager.AppSettings["StagingConnectionString"];
            }

        }
    }
}

来自示例服务的一些代码

public class tbl_OrdersDataRepo
{
    public IDbConnectionFactory dbFactory { get; set; }

    public object PostOrder(tbl_Orders order)
    {
        using (IDbConnection db = dbFactory.OpenDbConnection())
        {
            try
            {
                if (IsInsertRequest(order))
                {
                    return InsertOrder(db, order);
                }
                else
                {
                    return UpdateOrder(db, order);
                }
            }
            catch (Exception ex)
            {
                return new Exception(ex.Message);
            }
        }
    }

    private Boolean IsInsertRequest(tbl_Orders order)
    {
        if (order.OrderID > 0)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    private int InsertOrder(IDbConnection db, tbl_Orders order)
    {
        if (order.OrderExternalID > 0)
        {
            tbl_Orders orderWithMappedIDs = mapExternalIDsToInternalIDs(db, order);
            return (int)db.Insert(orderWithMappedIDs, selectIdentity: true);
        }
        else
        {
            return (int)db.Insert(order, selectIdentity: true);
        }
    }

    private int UpdateOrder(IDbConnection db, tbl_Orders order)
    {
        tbl_Orders updatedOrder;

        if (order.OrderExternalID > 0)
        {
            tbl_Orders orderWithMappedIDs = mapExternalIDsToInternalIDs(db, order);
            tbl_Orders internalOrder = getOrder(db, orderWithMappedIDs.OrderID);
            updatedOrder = getUpdatedOrder(internalOrder, orderWithMappedIDs);
        }
        else
        {
            tbl_Orders internalOrder = getOrder(db, order.OrderID);
            updatedOrder = getUpdatedOrder(internalOrder, order);
        }

        db.Update(updatedOrder);
        return updatedOrder.OrderID;
    }

    private tbl_Orders mapExternalIDsToInternalIDs(IDbConnection db, tbl_Orders externalOrder)
    {
        tbl_Orders internalOrder = externalOrder;

        if (externalOrder.CustomerLocationID > 0)
            internalOrder.CustomerLocationID = getInternalCustomerLocationID(db, externalOrder.CustomerLocationID);

        if (externalOrder.NurseryLocationID > 0)
            internalOrder.NurseryLocationID = getInternalNurseryLocationID(db, externalOrder.NurseryLocationID);

        if (externalOrder.UserID > 0)
            internalOrder.UserID = getInternalUserID(db, "harmony");

        if (externalOrder.OrderTypeID > 0)
            internalOrder.OrderTypeID = getInternalOrderTypeID(db, externalOrder.OrderTypeID);

        if (externalOrder.EDIStatusID > 0)
            internalOrder.EDIStatusID = getInternalEDIStatusID(db, externalOrder.EDIStatusID);

        if (externalOrder.OrderDeliveryTimeWindowID > 0)
            internalOrder.OrderDeliveryTimeWindowID = getInternalOrderDeliveryTimeWindowID(db, externalOrder.OrderDeliveryTimeWindowID);

        return internalOrder;
    }

    private int getInternalCustomerLocationID(IDbConnection db, int externalCustomerLocationID)
    {
        var log = LogManager.GetLogger(GetType());
        log.Info("Searching for an ExternalCustomerLocationID: {0}".Fmt(JsonSerializer.SerializeToString(externalCustomerLocationID)));
        log.Info("In the database: {0}".Fmt(JsonSerializer.SerializeToString(db.Database)));

        List<tbl_CustomerLocations> internalCustomers = db.Select<tbl_CustomerLocations>(c => c.CustomerLocationExternalID == externalCustomerLocationID);
        if (internalCustomers.Count > 0)
        {
            tbl_CustomerLocations internalCustomer = internalCustomers[0];
            return internalCustomer.CustomerLocationID;
        }
        else
        {
            throw new Exception("Cannot find a customer location with an id of '" + externalCustomerLocationID + "' in the remote database.  " +
                "Please make sure that the customer location exists in both databases, and that its external id is equal to " + externalCustomerLocationID + ".");
        }
    }

对于这篇文章的篇幅感到抱歉,但我认为太多的信息比太少了。谢谢你的帮助!

1 个答案:

答案 0 :(得分:1)

ServiceStack的IOC和主机配置应该在AppHost.Configure()内配置并在之后保持不变,即IOC依赖性永远不应该在运行时修改。

如果您在会话中存储客户连接字符串,则可以为用户特定数据库打开连接,例如:

public class MyServices : Service
{
     public object Any(Request request)
     {
         var session = base.SessionAs<CustomAuthUserSession>();

         using (var db = TryResolve<IDbConnectionFactory>()
             .OpenDbConnectionString(session.ConnectionString))
         {
             //..
         }
     }
}

另一种方法是将连接字符串存储在RequestContext and use a custom IDbConnectionFactory