Pour implémenter quelque chose comme un écran d'administration Django à l'aide de Flask, il est pratique d'utiliser une bibliothèque appelée Flask-Admin. Cependant, si vous utilisez simplement Flask-Admin tel quel, vous pouvez entrer dans l'écran administrateur sans saisir de mot de passe (sans vous connecter), ce qui est très vulnérable en termes de sécurité. Dans cet article, nous utiliserons Flask-Admin et Flask-Login pour ** implémenter un écran d'administrateur de base de données avec une fonction de connexion **.
Puisque la quantité d'informations est un peu petite, c'est un article que j'ai essayé d'augmenter les informations de l'explication en japonais. Ceux qui parlent anglais ou qui ne sont pas doués pour les mots redondants peuvent voir le site de référence à partir du lien ci-dessus.
Model-View-Controller Vous devez en savoir un peu plus sur le modèle. Parce que dans cet article, j'utiliserai des mots comme modèle et contrôleur. Je pense que l'article ci-dessous sera utile. À propos du modèle MVC
Ubuntu20.04LTS
MySQL 8.0.21
Python3.8.5
Flask==1.1.2
Flask-Admin==1.5.6
Flask-Login==0.5.0
Flask-SQLAlchemy==2.4.4
mysqlclient==2.0.1
Soudain, je vais vous montrer tout le code source final pour ceux qui n'ont pas le temps. Une explication détaillée est ci-dessous.
from flask import Flask, abort, jsonify, render_template, request, redirect, url_for
from wtforms import form, fields, validators
import flask_admin as admin
import flask_login as login
from flask_admin.contrib import sqla
from flask_admin import helpers, expose
from flask_admin.contrib.sqla import ModelView
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://{user}:{password}@{host}/{db_name}".format(**{
'user': os.environ['RDS_USER'],
'password': os.environ['RDS_PASS'],
'host': os.environ['RDS_HOST'],
'db_name': os.environ['RDS_DB_NAME']
})
app.config['SECRET_KEY'] = os.environ['FLASK_SECRET_KEY']
db = SQLAlchemy(app)
class AdminUser(db.Model):
id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(50), unique=True)
password = db.Column(db.String(250))
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
return self.id
def __unicode__(self):
return self.username
class LoginForm(form.Form):
login = fields.StringField(validators=[validators.required()])
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
user = self.get_user()
if user is None:
raise validators.ValidationError('Le nom d'utilisateur ou le mot de passe est différent.')
if not check_password_hash(user.password, self.password.data):
raise validators.ValidationError('Le nom d'utilisateur ou le mot de passe est différent.')
def get_user(self):
return db.session.query(AdminUser).filter_by(login=self.login.data).first()
class RegistrationForm(form.Form):
login = fields.StringField(validators=[validators.required()])
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
if db.session.query(AdminUser).filter_by(login=self.login.data).count() > 0:
raise validators.ValidationError('Le même nom d'utilisateur existe.')
def init_login():
login_manager = login.LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return db.session.query(AdminUser).get(user_id)
class MyModelView(sqla.ModelView):
def is_accessible(self):
return login.current_user.is_authenticated
class MyAdminIndexView(admin.AdminIndexView):
@expose('/')
def index(self):
if not login.current_user.is_authenticated:
return redirect(url_for('.login_view'))
return super(MyAdminIndexView, self).index()
@expose('/login/', methods=('GET', 'POST'))
def login_view(self):
form = LoginForm(request.form)
if helpers.validate_form_on_submit(form):
user = form.get_user()
login.login_user(user)
if login.current_user.is_authenticated:
return redirect(url_for('.index'))
link = '<p>Pour ne pas créer de compte<a href="' + url_for('.register_view') + '">cliquez ici</a></p>'
self._template_args['form'] = form
self._template_args['link'] = link
return super(MyAdminIndexView, self).index()
@expose('/register/', methods=('GET', 'POST'))
def register_view(self):
form = RegistrationForm(request.form)
if helpers.validate_form_on_submit(form):
user = AdminUser()
form.populate_obj(user)
user.password = generate_password_hash(form.password.data)
db.session.add(user)
db.session.commit()
login.login_user(user)
return redirect(url_for('.index'))
link = '<p>Si vous avez déjà un compte<a href="' + url_for('.login_view') + '">Cliquez ici pour vous identifier</a></p>'
self._template_args['form'] = form
self._template_args['link'] = link
return super(MyAdminIndexView, self).index()
@expose('/logout/')
def logout_view(self):
login.logout_user()
return redirect(url_for('.index'))
init_login()
admin = admin.Admin(app, 'Écran administrateur', index_view=MyAdminIndexView(), base_template='my_master.html')
admin.add_view(MyModelView(AdminUser, db.session))
@app.route("/", methods=['GET'])
def index():
return "Hello, World!"
if __name__ == "__main__":
app.run()
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://{user}:{password}@{host}/{db_name}".format(**{
'user': os.environ['RDS_USER'],
'password': os.environ['RDS_PASS'],
'host': os.environ['RDS_HOST'],
'db_name': os.environ['RDS_DB_NAME']
})
app.config['SECRET_KEY'] = os.environ['FLASK_SECRET_KEY']
db = SQLAlchemy(app)
Comme il existe de nombreux articles en japonais sur Internet, l'explication ici est omise.
class AdminUser(db.Model):
id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(50), unique=True)
password = db.Column(db.String(250))
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
return self.id
def __unicode__(self):
return self.username
login est votre nom d'utilisateur. Il définit le nom d'utilisateur et le mot de passe pour se connecter à l'écran administrateur. La raison pour laquelle chaque méthode a un décorateur de propriété est d'obtenir des informations telles que si vous vous êtes déjà connecté ou non lorsque vous écrivez le processus de connexion ultérieurement. Si vous voulez en savoir plus sur le décorateur immobilier ↓ Propriétés
class LoginForm(form.Form):
login = fields.StringField(validators=[validators.required()])
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
user = self.get_user()
if user is None:
raise validators.ValidationError('Le nom d'utilisateur ou le mot de passe est différent.')
if not check_password_hash(user.password, self.password.data):
raise validators.ValidationError('Le nom d'utilisateur ou le mot de passe est différent.')
def get_user(self):
return db.session.query(AdminUser).filter_by(login=self.login.data).first()
class RegistrationForm(form.Form):
login = fields.StringField(validators=[validators.required()])
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
if db.session.query(AdminUser).filter_by(login=self.login.data).count() > 0:
raise validators.ValidationError('Le même nom d'utilisateur existe.')
C'est un contrôleur qui décrit le traitement lorsque des entrées sont reçues du formulaire de la vue (écran de connexion ou écran d'enregistrement de compte administrateur). Ce à quoi vous devez faire attention ici est la classe LoginForm.
check_password_hash(user.password, self.password.data)
est. Ceci est pratique qui compare le mot de passe réel qui est haché et enregistré et la valeur hachée entrée à partir de l'écran de connexion, et renvoie True s'ils correspondent.
Bien que cela ne soit pas recommandé, si le mot de passe est stocké en texte brut dans la base de données, l'expression conditionnelle doit être utilisée.
if user.password != self.password.data:
Je pense que vous devriez le changer en.
def init_login():
login_manager = login.LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return db.session.query(AdminUser).get(user_id)
class MyModelView(sqla.ModelView):
def is_accessible(self):
return login.current_user.is_authenticated
class MyAdminIndexView(admin.AdminIndexView):
@expose('/')
def index(self):
if not login.current_user.is_authenticated:
return redirect(url_for('.login_view'))
return super(MyAdminIndexView, self).index()
@expose('/login/', methods=('GET', 'POST'))
def login_view(self):
form = LoginForm(request.form)
if helpers.validate_form_on_submit(form):
user = form.get_user()
login.login_user(user)
if login.current_user.is_authenticated:
return redirect(url_for('.index'))
link = '<p>Pour ne pas créer de compte<a href="' + url_for('.register_view') + '">cliquez ici</a></p>'
self._template_args['form'] = form
self._template_args['link'] = link
return super(MyAdminIndexView, self).index()
@expose('/register/', methods=('GET', 'POST'))
def register_view(self):
form = RegistrationForm(request.form)
if helpers.validate_form_on_submit(form):
user = AdminUser()
form.populate_obj(user)
user.password = generate_password_hash(form.password.data)
db.session.add(user)
db.session.commit()
login.login_user(user)
return redirect(url_for('.index'))
link = '<p>Si vous avez déjà un compte<a href="' + url_for('.login_view') + '">Cliquez ici pour vous identifier</a></p>'
self._template_args['form'] = form
self._template_args['link'] = link
return super(MyAdminIndexView, self).index()
@expose('/logout/')
def logout_view(self):
login.logout_user()
return redirect(url_for('.index'))
init_login()
admin = admin.Admin(app, 'Écran administrateur', index_view=MyAdminIndexView(), base_template='my_master.html')
admin.add_view(MyModelView(AdminUser, db.session))
C'est un peu comme le faire normalement avec Flask.
Ce à quoi vous devez faire attention ici est la classe MyModelView. MyModelView hérite de sqla.ModelView et remplace la méthode is_accessible. (J'ai besoin de) La méthode is_accessible renvoie si l'utilisateur est déjà connecté. En remplaçant simplement la méthode is_accessible, vous pourrez définir des règles de contrôle d'accès dans une classe de vue ultérieure (ici, la classe MyAdminIndexView).
init_login()
admin = admin.Admin(app, 'Écran administrateur', index_view=MyAdminIndexView(), base_template='my_master.html')
admin.add_view(MyModelView(AdminUser, db.session))
Définit quelle classe de vue est réellement utilisée dans quel modèle.
Cela n'a aucun sens sans HTML. Créez un répertoire de modèles dans le répertoire racine du projet et créez des fichiers et des répertoires avec la structure suivante.
templates/
admin/
index.html
my_master.html
index.html
my_master.html
{% extends 'admin/base.html' %}
{% block access_control %}
{% if current_user.is_authenticated %}
<div class="btn-group pull-right">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-user"></i> {{ current_user.login }} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('admin.logout_view') }}">Se déconnecter</a></li>
</ul>
</div>
{% endif %}
{% endblock %}
Sur l'écran après la connexion, si vous appuyez sur l'ID utilisateur, un bouton déroulant apparaîtra.
templates/index.html
<html>
<body>
<div>
<a href="{{ url_for('admin.index') }}">Go to admin!</a>
</div>
</body>
</html>
N'importe quelle page d'index est bien. Je vais l'écrire de manière appropriée.
templates/admin/index.html
{% extends 'admin/master.html' %}
{% block body %}
{{ super() }}
<div class="row-fluid">
<div>
{% if current_user.is_authenticated %}
<h1>Écran d'administration de Civichat</h1>
<p class="lead">
Agréé
</p>
<p>
Vous pouvez gérer les données à partir de cet écran. Si vous souhaitez vous déconnecter/admin/Veuillez accéder à la déconnexion.
</p>
{% else %}
<form method="POST" action="">
{{ form.hidden_tag() if form.hidden_tag }}
{% for f in form if f.type != 'CSRFTokenField' %}
<div>
{{ f.label }}
{{ f }}
{% if f.errors %}
<ul>
{% for e in f.errors %}
<li>{{ e }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
<button class="btn" type="submit">Terminé</button>
</form>
{{ link | safe }}
{% endif %}
</div>
<a class="btn btn-primary" href="/"><i class="icon-arrow-left icon-white"></i>Revenir</a>
</div>
{% endblock body %}
C'est comme une page d'index sur l'écran d'administration après la connexion.
En premier lieu, je pense qu'il est nécessaire de restreindre l'accès par adresse IP avant d'atteindre le formulaire de connexion. Je pense que l'article ci-dessous sera utile.
Si vous avez des erreurs, veuillez nous en informer dans les commentaires.
Recommended Posts