将 Flask-SocketIO 与 Flask-Login 和 HTTP Basic Auth 结合使用

Pas*_*ist 5 python authentication flask flask-login flask-socketio

我正在尝试为内部监控实现一个非常简单的网页。它应该显示一些数据,这些数据通过 socketio 实时更新。服务器在后台运行一个线程,该线程获取数据并将其中继到客户端。

我想用登录表单保护页面。为了简单起见,我选择了 HTTP Basic Auth,主要是因为我不想设计登录表单。

我做了以下工作:

  • 在 下@login_manager.request_handler,我检查request.authorization. 如果它有效,我将返回一个经过身份验证的User对象。
  • 在 下@login_manager.unauthorized_handler,我触发了身份验证对话框。
  • '/'页面受保护@login_required
  • 我还拦截了该socketio.on('connect')事件并在current_user那里检查。如果未通过身份验证,我将断开连接。

这是整个工作示例:

## Standard imports, disregard them
import functools
import gevent

## Otherwise I'm getting KeyError on shutdown
import gevent.monkey
gevent.monkey.patch_all()

from flask import Flask, request, Response
from flask.ext.login import LoginManager, UserMixin, login_required, current_user
from flask.ext.socketio import SocketIO

## To see the logging.debug call in socketio.on('connect')
import logging
logging.getLogger().setLevel(logging.DEBUG)

## App configuration
app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'a long and random string'

login_manager = LoginManager()
login_manager.init_app(app)
socketio = SocketIO(app)

## This thing sends updates to the client
class BackgroundThread(gevent.Greenlet):
    def run(self):
        while True:
            socketio.emit(
                'my event',
                {'my field': 'my data'},
                namespace='/my-namespace'
            )
            gevent.sleep(2)

## Not bothering with a database
class User(UserMixin):
    users = {
        u'1': (u'myname', u'mypass')
    }

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def get_id(self):
        return u'1'

    @classmethod
    def get_by_username(cls, requested_username):
        for username, password in cls.users.itervalues():
            if username == requested_username:
                return User(username, password)
        return None

## From https://flask-socketio.readthedocs.org/en/latest/
def authenticated_only(f):
    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        if not current_user.is_authenticated():
            request.namespace.disconnect()
        else:
            return f(*args, **kwargs)
    return wrapped

## The password is checked here
@login_manager.request_loader
def load_request(request):
    auth = request.authorization

    if auth is not None:
        username, password = auth['username'], auth['password']
        user = User.get_by_username(username)
        if user is not None and user.password == password:
            return user
    return None

## From http://flask.pocoo.org/snippets/8/
@login_manager.unauthorized_handler
def http_basic_auth():
    return Response(
    'Could not verify your access level for that URL.\n'
    'You have to login with proper credentials', 401,
    {'WWW-Authenticate': 'Basic realm="Login Required"'})


@app.route('/')
@login_required
def index():
    return "My page"  # in real code this is actually a render_template call


@socketio.on('connect', namespace='/my-namespace')
@authenticated_only
def test_connect():
    logging.debug('Client connected: {.username}.'.format(current_user))


if __name__ == '__main__':
    thread = BackgroundThread()
    thread.start()

    socketio.run(app)
Run Code Online (Sandbox Code Playgroud)
  • 如果我使用带有自签名证书的 HTTPS,此设置是否安全?
  • Flask-Login文件强调,实际登录用户,我必须显式调用login_user。我不这样做,但我可以登录。这怎么可能?

UPD:在可预见的未来,我将成为唯一的用户,所以我最关心的是是否可以拦截和解密流量,或者在未经身份验证的情况下通过 Websocket 连接发送数据。

Mig*_*uel 3

如果我使用带有自签名证书的 HTTPS,此设置是否安全?

您将用户密码以纯文本形式存储在数据库中(我知道,您还没有数据库,但我假设您最终会有一个数据库?)。如果您的数据库遭到黑客攻击,那么您的用户会讨厌您,特别是那些在网上银行使用相同密码的用户。您应该将散列密码存储在数据库中,以保护它们免受黑客攻击。查看 Flask-Bcrypt 或 Werkzeug 中的密码哈希函数。

使用 HTTPS 很好,但由于您还使用 WebSocket,因此您需要评估通过套接字连接传输的数据是否也需要加密。

自签名证书不是一个好主意,因为浏览器无法验证其真实性,因此它们会(正确地)建议您的用户远离您的网站。

Flask-Login 文档强调,要实际登录用户,我必须显式调用 login_user。我不这样做,但我可以登录。这怎么可能?

让用户登录的想法是,您不必对他们发送的每个请求重新进行身份验证。只login_user记录用户登录的情况session。在后续请求中,Flask-Login 将在会话中找到用户,因此不需要调用您的回调来再次进行身份验证。

在您的情况下,您使用的是 HTTP 基本身份验证。浏览器将Authorization在每个请求中发送标头,并且由于 Flask-Login 永远不会在 中找到任何内容session,因此它总是调用您的回调,该回调每次都会对用户进行身份验证。我认为这没有任何问题,但如果您想避免不断验证用户身份的工作(特别是在添加密码散列之后,这是 CPU 密集型的),您可能需要考虑调用该函数来使事情变得login_user有点更高效。

更新:所以您声称您计划在代码中保留以纯文本形式编写的用户列表。这是一个非常非常糟糕的主意。您希望努力确保客户端和服务器之间数据传输的安全,因此您还应该在存储密码方面采取良好的安全实践。

我认为,在您是唯一用户的小型网站的代码中使用密码的最大风险是您错误地暴露了代码。例如,如果您想将代码置于版本控制之下,除了在服务器上运行的副本(又一个可能被黑客攻击的地方)之外,您还将在那里拥有密码的副本。如果您还对脚本进行备份,它也会在那里。

因此,请帮自己一个忙,不要将密码写在代码中。至少,在启动时从环境变量中读取它。