我正在尝试为内部监控实现一个非常简单的网页。它应该显示一些数据,通过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)
Flask-Login
文档强调要实际登录用户,我必须明确调用login_user
。我不这样做,但我可以登录。这怎么可能?UPD:在可预见的未来,我将成为唯一的用户,因此大多数情况下我担心是否可以拦截和解密流量,或通过Websocket连接发送数据而不是认证
答案 0 :(得分:3)
如果我将HTTPS与自签名证书一起使用,这个设置是否安全?
您的数据库中存有以纯文本格式存储的用户密码(我知道,您还没有数据库,但我认为您最终会有一个数据库?)。如果您的数据库被黑客入侵,那么您的用户会讨厌您,特别是那些使用相同密码进行在线银行业务的用户。您应该在数据库中存储散列密码,以防止黑客入侵。查看Flask-Bcrypt或Werkzeug中的密码散列函数。
使用HTTPS很好,但由于您还在使用WebSocket,因此需要评估通过套接字连接的数据是否也需要加密。
自签名证书并不是一个好主意,因为浏览器无法验证其真实性,因此他们(正确地)建议您的用户远离您的网站。
Flask-Login文档强调要实际登录用户,我必须显式调用login_user。我不这样做,但我可以登录。这怎么可能?
记录用户的想法是,您不必对发送的每个请求重新对其进行身份验证。 login_user
只记录用户登录session
的情况。在后续请求中,Flask-Login将在会话中找到用户,因此不需要再调用您的回调来进行身份验证。
在您的情况下,您正在使用HTTP基本身份验证。浏览器将为每个请求发送Authorization
标头,并且由于Flask-Login从未在session
中找到任何内容,因此它始终会调用您的回调,该回调每次都会对用户进行身份验证。我没有看到任何问题,但是如果你想避免不断地对用户进行身份验证的工作(特别是在你添加密码哈希后,这是CPU密集型的),你可能要考虑调用login_user
函数使事情变得更有效率。
更新:因此您声称计划在代码中保留以纯文本格式编写的用户列表。这真是个糟糕的主意。您希望努力使数据在客户端和服务器之间保持安全,因此您还应该在存储密码方面采取良好的安全措施。
我看到,在您是唯一用户的小网站的密码中看到的最大风险是您错误地公开了代码。例如,如果您希望将代码置于版本控制之下,那么除了在服务器上运行的副本(可以被黑客攻击的另一个位置)之外,您还将拥有密码的副本。如果你也备份你的脚本,它也会在那里。
所以请帮个忙,不要在代码中写密码。至少,在启动时从环境变量中读取它。