Flask: 在domain.com和username.domain.com之间共享会话
我在 domain.com 上运行了一个 Flask 应用。
我还有另一个 Flask 应用在另一台服务器上,运行在 username.domain.com。
通常情况下,用户是通过 domain.com 登录的。
但是,对于付费用户,他们应该在 username.domain.com 登录。
我想知道,使用 Flask 的话,怎么才能确保 domain.com 和 username.domain.com 之间的会话是共享的,同时又能确保他们只能访问对应的 username.domain.com?
我对安全性这块比较担心。
2 个回答
你可以使用Flask自带的会话功能,这种会话是基于浏览器的cookie。为了让用户能够在'.domain.com'的多个子域名上登录,你只需要指定
app.config['SESSION_COOKIE_DOMAIN'] = '.domain.com'
这样,用户的浏览器就会有一个会话cookie,这样他就可以在'domain.com'下的每个Flask实例上登录。这个方法只有在每个Flask实例使用相同的app.secret_key时才有效。
想了解更多信息,可以查看 在两个应用程序之间共享Flask登录会话
编辑:后来,在阅读了你的完整问题后,我发现原来的回答并不是你想要的。
我把原来的回答留在了这个答案的底部,方便搜索的朋友们,但下面是修订后的版本。
Cookies会自动发送到同一域名下的子域名(在大多数现代浏览器中,域名必须包含一个点(表示顶级域名)才能实现这种行为)。身份验证需要在处理之前完成,并且你的会话需要从一个集中源进行管理。我们来一步步看看。
为了确认,我将假设(根据你告诉我的)你的设置如下:
- 服务器 1:
- 用于 domain.com 的 Flask 应用
- 服务器 2:
- 用于 username.domain.com 的用户资料 Flask 应用
首先需要解决的问题是将会话存储在两个服务器都能访问的位置。因为默认情况下,会话存储在磁盘上(而且两个服务器显然不共享同一个硬盘),我们需要对现有设置和新的用户资料 Flask 应用进行一些修改。
第一步是选择存储会话的位置,常见的选择是使用 MySQL、Postgres 等数据库管理系统(DBMS)来存储,但人们也常常选择将其放在更临时的地方,比如 Memcached 或 Redis。
在这两种截然不同的系统之间选择的简短版本如下:
数据库
- 数据库随时可用
- 你可能已经实现了一个数据库
- 开发者通常对他们选择的数据库有预先的了解
内存(Redis/Memcached 等)
- 速度明显更快
- 系统通常提供基本的数据自我管理
- 不会对现有数据库造成额外负担
你可以在 flask 中找到一些数据库会话的例子 这里 和 这里。
虽然 Redis 的设置可能会根据每个用户的经验水平而有所不同,但我推荐这个选项。你可以在 这里 查看一个示例。
其余的内容我认为在原始答案中已经涵盖,其中一部分演示了用户名与数据库记录的匹配(较大的代码块)。
单个 Flask 应用的旧解决方案
首先,你需要设置 Flask 来处理子域名,这只需在配置文件中指定一个新的变量名。例如,如果你的域名是 example.com,你可以在 Flask 配置中添加以下内容。
SERVER_NAME = "example.com"
你可以在 这里 阅读更多关于这个选项的信息。
这里需要快速提到的是,如果你只是使用 localhost,这将非常困难(甚至不可能)进行测试。如上所述,浏览器通常不会向没有点(顶级域名)的域名的子域发送 cookies。许多操作系统默认情况下也不允许 localhost 设置子域。你可以通过定义自己的 DNS 条目来实现这一点(在 *UNIX 上是 /etc/hosts
,在 Windows 上是 %system32%/etc/hosts
)。
一旦你的配置准备好了,你需要为子域通配符定义一个 Blueprint
。
这很简单:
from flask import Blueprint
from flask.ext.login import current_user
# Create our Blueprint
deep_blue = Blueprint("subdomain_routes", __name__, subdomain="<username>")
# Define our route
@deep_blue.route('/')
def user_index(username):
if not current_user.is_authenticated():
# The user needs to log in
return "Please log in"
elif username != current_user.username:
# This is not the correct user.
return "Unauthorized"
# It's the right user!
return "Welcome back!"
这里的关键是确保你的用户对象的 __repr__
包含一个用户名键。例如……
class User(db.Model):
username = db.Column(db.String)
def __repr__(self):
return "<User {self.id}, username={self.username}>".format(self=self)
不过需要注意的是,当用户名包含特殊字符(空格、@、? 等)时,会出现问题,这些字符在 URL 中无法使用。为此,你需要对用户名施加限制,或者在验证时先正确转义名称,然后再进行解码。
如果你有任何问题或请求,请随时问我。这个回答是在我喝咖啡的间隙写的,所以有点匆忙。