5

我正在尝试为内部监控实现一个非常简单的网页。它应该显示一些数据,这些数据通过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)
  • 如果我使用带有自签名证书的 HTTPS,此设置是否安全?
  • Flask-Login文档强调要实际登录用户,我必须明确调用login_user. 我不这样做,但我可以登录。这怎么可能?

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

4

1 回答 1

3

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

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

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

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

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

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

在您的情况下,您使用的是 HTTP 基本身份验证。浏览器将Authorization随每个请求发送标头,并且由于 Flask-Login 永远不会在session. 我认为这没有任何问题,但是如果您想避免不断验证用户身份的工作(特别是在添加密码散列后,这是 CPU 密集型的),您可能需要考虑调用该login_user函数来做一些事情更高效。

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

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

所以帮自己一个忙,不要在代码中写下你的密码。至少,在启动时从环境变量中读取它。

于 2015-09-06T07:30:06.307 回答