[PYTHON] Implementierung des DB-Administratorbildschirms durch Flask-Admin und Flask-Login

Einführung

Um so etwas wie einen Django-Administratorbildschirm mit Flask zu implementieren, ist es bequem, eine Bibliothek namens Flask-Admin zu verwenden. Wenn Sie jedoch nur Flask-Admin verwenden, können Sie den Administratorbildschirm aufrufen, ohne ein Kennwort einzugeben (ohne sich anzumelden), was aus Sicherheitsgründen sehr anfällig ist. In diesem Artikel verwenden wir Flask-Admin und Flask-Login, um ** einen DB-Administratorbildschirm mit einer Anmeldefunktion zu implementieren **.

Dieser Beitrag

Referenzquelle

Da die Informationsmenge etwas gering ist, ist dies ein Artikel, in dem ich versucht habe, die Informationen der Erklärung auf Japanisch zu erweitern. Diejenigen, die Englisch sprechen können oder nicht gut in redundanten Formulierungen sind, können die Referenzseite über den obigen Link sehen.

Über MVC

Model-View-Controller Sie müssen ein wenig über das Modell wissen. Denn in diesem Artikel werde ich Wörter wie Modell und Controller verwenden. Ich denke, der folgende Artikel wird hilfreich sein. Informationen zum MVC-Modell

Autorenumgebung

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

Ganzer Quellcode

Plötzlich zeige ich Ihnen den gesamten endgültigen Quellcode für diejenigen, die nicht die Zeit haben. Eine ausführliche Erklärung finden Sie unten.

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('Der Benutzername oder das Passwort ist unterschiedlich.')

    if not check_password_hash(user.password, self.password.data):
      raise validators.ValidationError('Der Benutzername oder das Passwort ist unterschiedlich.')

  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('Der gleiche Benutzername existiert.')


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>Um kein Konto zu erstellen<a href="' + url_for('.register_view') + '">Klicke hier</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>Wenn Sie bereits ein Konto haben<a href="' + url_for('.login_view') + '">Klicken Sie hier, um sich einzuloggen</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, 'Administratorbildschirm', 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()

Stellen Sie eine Verbindung zur Datenbank her

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)

Da es im Internet viele japanische Artikel gibt, wird die Erklärung hier weggelassen.

Erstellen Sie ein Modell für ein Administratorkonto

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 ist Ihr Benutzername. Es definiert den Benutzernamen und das Kennwort für die Anmeldung am Administratorbildschirm. Der Grund, warum jede Methode über einen Eigenschaftsdekorator verfügt, besteht darin, Informationen abzurufen, z. B. ob Sie sich bereits angemeldet haben, wenn Sie den Anmeldevorgang später schreiben. Wenn Sie mehr über Property Decorator erfahren möchten ↓ Eigenschaften

Controller erstellen


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('Der Benutzername oder das Passwort ist unterschiedlich.')

    if not check_password_hash(user.password, self.password.data):
      raise validators.ValidationError('Der Benutzername oder das Passwort ist unterschiedlich.')

  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('Der gleiche Benutzername existiert.')

Es ist ein Controller, der die Verarbeitung beschreibt, wenn Eingaben aus der Form der Ansicht empfangen werden (Anmeldebildschirm oder Registrierungsbildschirm für Administratorkonten). Was Sie hier beachten sollten, ist die LoginForm-Klasse.

check_password_hash(user.password, self.password.data)

ist. Dies ist eine praktische Methode, die das gehashte und gespeicherte echte Kennwort und den vom Anmeldebildschirm eingegebenen Hash-Wert vergleicht und True zurückgibt, wenn sie übereinstimmen.

Obwohl dies nicht empfohlen wird, sollte der bedingte Ausdruck verwendet werden, wenn das Kennwort in der Datenbank im Klartext gespeichert ist.

if user.password != self.password.data:

Ich denke, Sie sollten es ändern.

Ansicht erstellen


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>Um kein Konto zu erstellen<a href="' + url_for('.register_view') + '">Klicke hier</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>Wenn Sie bereits ein Konto haben<a href="' + url_for('.login_view') + '">Klicken Sie hier, um sich einzuloggen</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, 'Administratorbildschirm', index_view=MyAdminIndexView(), base_template='my_master.html')
admin.add_view(MyModelView(AdminUser, db.session))

Es ist ein bisschen so, als würde man es normalerweise mit Flask machen.

Was Sie hier beachten sollten, ist die MyModelView-Klasse. MyModelView erbt von sqla.ModelView und überschreibt die Methode is_accessible. (Ich muss einfach) Die Methode is_accessible gibt zurück, ob der Benutzer bereits angemeldet ist. Durch einfaches Überschreiben der Methode is_accessible können Sie Zugriffssteuerungsregeln in einer späteren Ansichtsklasse (hier die MyAdminIndexView-Klasse) definieren.

init_login()
admin = admin.Admin(app, 'Administratorbildschirm', index_view=MyAdminIndexView(), base_template='my_master.html')
admin.add_view(MyModelView(AdminUser, db.session))

Definiert, welche Ansichtsklasse in welchem Modell tatsächlich verwendet wird.

Schreiben Sie HTML

Ohne HTML ist es bedeutungslos. Erstellen Sie ein Vorlagenverzeichnis im Projektstammverzeichnis und erstellen Sie Dateien und Verzeichnisse mit der folgenden Struktur.

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') }}">Ausloggen</a></li>
  </ul>
</div>
{% endif %}
{% endblock %}

Wenn Sie nach der Anmeldung die Benutzer-ID drücken, wird auf dem Bildschirm eine Dropdown-Schaltfläche angezeigt.

templates/index.html

<html>
  <body>
    <div>
      <a href="{{ url_for('admin.index') }}">Go to admin!</a>
    </div>
  </body>
</html>

Jede Indexseite ist in Ordnung. Ich werde es angemessen schreiben.

templates/admin/index.html

{% extends 'admin/master.html' %}
{% block body %}
{{ super() }}
<div class="row-fluid">

    <div>
        {% if current_user.is_authenticated %}
        <h1>Civichat Admin-Bildschirm</h1>
        <p class="lead">
Zertifiziert
        </p>
        <p>
Sie können die Daten von diesem Bildschirm aus verwalten. Wenn Sie sich abmelden möchten/admin/Bitte greifen Sie auf die Abmeldung zu.
        </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">Erledigt</button>
        </form>
        {{ link | safe }}
        {% endif %}
    </div>

    <a class="btn btn-primary" href="/"><i class="icon-arrow-left icon-white"></i>Rückkehr</a>
</div>
{% endblock body %}

Es ist wie eine Indexseite auf dem Admin-Bildschirm nach dem Anmelden.

Ich möchte IP zusätzlich zur Passwortauthentifizierung einschränken

Erstens denke ich, dass der Zugriff nach IP-Adresse eingeschränkt werden muss, bevor das Anmeldeformular erreicht wird. Ich denke, der folgende Artikel wird hilfreich sein.

IP-Einschränkung mit Flask

Schließlich

Wenn Sie Fehler haben, teilen Sie uns dies bitte in den Kommentaren mit.

Recommended Posts

Implementierung des DB-Administratorbildschirms durch Flask-Admin und Flask-Login
Erklärung und Implementierung von SocialFoceModel
Einführung und Implementierung von JoCoR-Loss (CVPR2020)
Erklärung und Implementierung des ESIM-Algorithmus
Einführung und Implementierung der Aktivierungsfunktion
Zusammenfassung der grundlegenden Implementierung von PyTorch
Erklärung und Implementierung von einfachem Perzeptron
[Python] Implementierung der Nelder-Mead-Methode und Speichern von GIF-Bildern durch matplotlib
Ableitung der multivariaten t-Verteilung und Implementierung der Zufallszahlengenerierung durch Python
Implementieren Sie ein Modell mit Status und Verhalten (3) - Beispiel für die Implementierung durch den Dekorateur
Implementierung und Experiment der konvexen Clustering-Methode
Implementierung von SVM durch probabilistische Gradientenabstiegsmethode
Erklärung und Implementierung des Decomposable Attention-Algorithmus
Zusammenfassung der Verbindungsmethode nach DB von SQL Alchemy
[Python] Vergleich der Theorie und Implementierung der Hauptkomponentenanalyse durch Python (PCA, Kernel PCA, 2DPCA)
Vergleichen Sie die Implementierungsbeispiele für scikit-learn und pyclustering k-means
Niedrigrangige Approximation von Bildern durch HOSVD und HOOI
Berechnung der technischen Indikatoren durch TA-Lib und Pandas
TRIE-Baumimplementierung mit Python und LOUDS
Paralleles Lernen von Deep Learning durch Keras und Kubernetes
Tiefes Lernen durch Implementierung (Segmentierung) ~ Implementierung von SegNet ~
Erläuterung der Bearbeitungsentfernung und Implementierung in Python