我有一个基于Pylons的Web应用程序,该应用程序通过Sqlalchemy(v0.5)连接到Postgres数据库。为了安全起见,我没有使用普通的Postgres用户(例如“webapp”),而是遵循简单的Web应用程序的典型模式(在几乎所有教程中都可以看到),而是要求用户输入自己的Postgres用户名和密码。 ,并且正在使用它来建立连接。这意味着我们可以充分利用Postgres安全性。
更复杂的是,有两个单独的数据库要连接。尽管它们当前位于同一Postgres集群中,但它们需要能够在以后转移到单独的主机上。
我们正在使用sqlalchemy的declarative程序包,尽管我看不到这有什么关系。
sqlalchemy的大多数示例都显示了一些简单的方法,例如在应用程序启动时使用通用的数据库用户名和密码在Web应用程序中一次设置一次元数据。通常使用Metadata.bind = create_engine()完成此操作,有时甚至在数据库模型文件中的模块级也是如此。
我的问题是,我们如何才能将建立连接的时间推迟到用户登录之前,然后(当然)针对每个后续请求重用这些连接,或者使用相同的凭据重新建立连接。
我们正在努力-我们认为-但我不仅不确定它的安全性,而且我认为它在这种情况下看起来非常沉重。
在BaseController的__call__
方法内,我们从Web session 中检索用户ID和密码,对每个数据库调用sqlalchemy create_engine()一次,然后重复调用例程,该例程重复调用Session.bind_mapper(),对可能引用到每个表的例程每个连接,即使任何给定的请求通常仅引用一个或两个表。看起来像这样:
# in lib/base.py on the BaseController class
def __call__(self, environ, start_response):
# note: web session contains {'username': XXX, 'password': YYY}
url1 = 'postgres://%(username)s:%(password)s@server1/finance' % session
url2 = 'postgres://%(username)s:%(password)s@server2/staff' % session
finance = create_engine(url1)
staff = create_engine(url2)
db_configure(staff, finance) # see below
... etc
# in another file
Session = scoped_session(sessionmaker())
def db_configure(staff, finance):
s = Session()
from db.finance import Employee, Customer, Invoice
for c in [
Employee,
Customer,
Invoice,
]:
s.bind_mapper(c, finance)
from db.staff import Project, Hour
for c in [
Project,
Hour,
]:
s.bind_mapper(c, staff)
s.close() # prevents leaking connections between sessions?
因此,对每个请求都会进行create_engine()调用...我可以看到这是必需的,并且连接池可能会缓存它们并明智地执行操作。
但是,在每个请求上为每个表调用一次Session.bind_mapper()吗?似乎必须有更好的方法。
显然,由于所有这些都要求强大的安全性,因此我们不希望低安全性用户在以后的请求中无意中使用为高安全性用户建立的连接。
最佳答案
将全局对象(映射器,元数据)绑定(bind)到用户特定的连接不是一种好方法。以及使用作用域 session 。我建议为每个请求创建新 session 并将其配置为使用用户特定的连接。下面的示例假定您对每个数据库使用单独的元数据对象:
binds = {}
finance_engine = create_engine(url1)
binds.update(dict.fromkeys(finance_metadata.sorted_tables, finance_engine))
# The following line is required when mappings to joint tables are used (e.g.
# in joint table inheritance) due to bug (or misfeature) in SQLAlchemy 0.5.4.
# This issue might be fixed in newer versions.
binds.update(dict.fromkeys([Employee, Customer, Invoice], finance_engine))
staff_engine = create_engine(url2)
binds.update(dict.fromkeys(staff_metadata.sorted_tables, staff_engine))
# See comment above.
binds.update(dict.fromkeys([Project, Hour], staff_engine))
session = sessionmaker(binds=binds)()