Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
python:flask:flask_login [2022/10/28 18:30] marclebrun [Initialisation] |
python:flask:flask_login [2022/10/30 04:50] (Version actuelle) marclebrun [Login] |
||
---|---|---|---|
Ligne 13: | Ligne 13: | ||
**UserMixin** : | **UserMixin** : | ||
- | <code python> | + | <code python app/models/user.py> |
from flask_login import UserMixin | from flask_login import UserMixin | ||
+ | from werkzeug.security import generate_password_hash, check_password_hash | ||
class User(UserMixin, db.Model): | class User(UserMixin, db.Model): | ||
Ligne 31: | Ligne 32: | ||
* ''is_anonymous'' | * ''is_anonymous'' | ||
* ''get_id()'' | * ''get_id()'' | ||
+ | |||
+ | Implémenter la propriété **password** qui sera en **écriture seule**, | ||
+ | en effet on peut seulement écrire un nouveau password mais jamais le | ||
+ | relire. L'écriture du password assigne le **hash** de la valeur donnée | ||
+ | au champ **password_hash** : | ||
+ | |||
+ | <code python app/models/user.py> | ||
+ | @property | ||
+ | def password(self): | ||
+ | raise AttributeError('password is not a readable attribute') | ||
+ | | ||
+ | @password.setter | ||
+ | def password(self, password): | ||
+ | self.password_hash = generate_password_hash(password) | ||
+ | </code> | ||
+ | |||
+ | Implémenter la méthode **verify_password** : | ||
+ | |||
+ | <code python app/models/user.py> | ||
+ | def verify_password(self, password): | ||
+ | return check_password_hash(self.password_hash, password) | ||
+ | </code> | ||
===== Initialisation ===== | ===== Initialisation ===== | ||
Ligne 36: | Ligne 59: | ||
Initialiser le **LoginManager** lors de la création de l'application : | Initialiser le **LoginManager** lors de la création de l'application : | ||
- | <code python> | + | <code python app/__init__.py> |
+ | from flask import Flask | ||
from flask_login import LoginManager | from flask_login import LoginManager | ||
Ligne 57: | Ligne 81: | ||
aura besoin de charger un utilisateur sur base de son identifiant. | aura besoin de charger un utilisateur sur base de son identifiant. | ||
- | <code python> | + | <code python app/__init__.py> |
+ | # ... | ||
+ | |||
+ | from app.models.user import User | ||
+ | |||
+ | # ... | ||
@login_manager.user_loader | @login_manager.user_loader | ||
def load_user(user_id): | def load_user(user_id): | ||
- | # ... | ||
| | ||
# Lecture de l'utilisateur depuis la base de données | # Lecture de l'utilisateur depuis la base de données | ||
Ligne 67: | Ligne 96: | ||
| | ||
return User.query.get(int(user_id)) | return User.query.get(int(user_id)) | ||
- | </code> | ||
- | |||
- | ===== Login ===== | ||
- | |||
- | <code python> | ||
- | </code> | ||
- | |||
- | ===== Protection d'une route ===== | ||
- | |||
- | Avec le décorateur **@login_required** | ||
- | |||
- | :!: Il est important de mettre les deux décorateurs dans cet ordre. | ||
- | |||
- | <code python> | ||
- | from flask_login import login_required | ||
- | |||
- | @app.route('/secret') | ||
- | @login_required | ||
- | def secret(): | ||
- | return 'Seuls les utilisateurs authentifiés verront ceci.' | ||
</code> | </code> | ||
===== Formulaire de login ===== | ===== Formulaire de login ===== | ||
- | <code python> | + | <code python app/forms/auth.py> |
from flask_wtf import FlaskForm | from flask_wtf import FlaskForm | ||
from wtforms import StringField, PasswordField, BooleanField, SubmitField | from wtforms import StringField, PasswordField, BooleanField, SubmitField | ||
Ligne 104: | Ligne 113: | ||
submit = SubmitField('Log In') | submit = SubmitField('Log In') | ||
</code> | </code> | ||
+ | |||
+ | ===== Login ===== | ||
+ | |||
+ | <code python app/controllers/auth.py> | ||
+ | from flask import Blueprint | ||
+ | from flask import request | ||
+ | from flask import render_template, redirect, url_for, flash | ||
+ | from flask_login import login_user | ||
+ | from app.forms.auth import LoginForm | ||
+ | from app.models.user import User | ||
+ | |||
+ | auth = Blueprint('auth', __name__) | ||
+ | |||
+ | @auth.route('/login', methods=['GET', 'POST']) | ||
+ | def login(): | ||
+ | form = LoginForm() | ||
+ | | ||
+ | # Si le formulaire a été posté (submit) et que son contenu | ||
+ | # a passé toutes les validations | ||
+ | if form.validate_on_submit(): | ||
+ | | ||
+ | # Si l'utilisateur a cliqué sur "Login" | ||
+ | if form.submit.data: | ||
+ | | ||
+ | # Rechercher l'utilisateur selon l'email entré | ||
+ | user = User.query.filter_by(email = form.email.data).first() | ||
+ | | ||
+ | # Si l'utilisateur est trouvé, et que son mot de passe correspond | ||
+ | if user is not None and user.verify_password(form.password.data): | ||
+ | | ||
+ | # Prendre en compte la connexion de l'utilisateur. | ||
+ | # Si 'remember_me' vaut True, un cookie sera ajouté afin de | ||
+ | # conserver la connexion active. | ||
+ | login_user(user, form.remember_me.data) | ||
+ | flash(u"Welcome, <b>%s</b> !" % user.name, "success") | ||
+ | | ||
+ | # Retourner une redirection | ||
+ | # - soit vers la page 'suivante' si le login provient d'une | ||
+ | # tentative d'accès à une page protégée | ||
+ | # - soit vers la page d'accueil | ||
+ | next = request.args.get('next') | ||
+ | if next is None or not next.startswith('/'): | ||
+ | next = url_for('main.home') | ||
+ | return redirect(next) | ||
+ | | ||
+ | # Si l'utilisateur n'est pas trouvé, on ajoute un message flash | ||
+ | # avant de retourner au formulaire | ||
+ | flash(u"Invalid username or password.", "error") | ||
+ | | ||
+ | # Rendu du formulaire dans tous les cas non traités ci-dessus | ||
+ | return render_template('auth/login.html', form = form) | ||
+ | </code> | ||
+ | |||
+ | ===== Logout ===== | ||
+ | |||
+ | <code python> | ||
+ | from flask_login import logout_user, login_required | ||
+ | |||
+ | @auth.route('/logout') | ||
+ | @login_required | ||
+ | def logout(): | ||
+ | logout_user() | ||
+ | flash(u"You have been logged out.", "success") | ||
+ | return redirect(url_for('main.home')) | ||
+ | </code> | ||
+ | |||
===== Objet current_user ===== | ===== Objet current_user ===== | ||
Ligne 109: | Ligne 184: | ||
La variable ''current_user'' est définie par **Flask-Login**, | La variable ''current_user'' est définie par **Flask-Login**, | ||
et elle disponible dans les templates et dans les vues. | et elle disponible dans les templates et dans les vues. | ||
+ | |||
+ | Lorsqu'aucun utilisateur n'est connecté, ''current_user'' contient | ||
+ | un objet dont la propriété ''is_authenticated'' vaut **False** | ||
+ | |||
+ | Lorsqu'un utilisateur est connecté, ''current_user'' contient un | ||
+ | objet de la classe **User**. | ||
+ | |||
+ | <code python> | ||
+ | from flask_login import current_user | ||
+ | |||
+ | # ... | ||
+ | |||
+ | if current_user.is_authenticated: | ||
+ | print("Hello %s !" % current_user.name) | ||
+ | </code> | ||
On peut afficher le lien **Log In** ou **Log Out** en fonction | On peut afficher le lien **Log In** ou **Log Out** en fonction | ||
- | de l'état de l'utilisateur (connecté ou non) : | + | de l'état de l'utilisateur (connecté ou non): |
<code html> | <code html> | ||
Ligne 119: | Ligne 209: | ||
<a href="{{ url_for('auth.login') }}">Log In</a> | <a href="{{ url_for('auth.login') }}">Log In</a> | ||
{% endif %} | {% endif %} | ||
+ | </code> | ||
+ | |||
+ | ===== Protection d'une route ===== | ||
+ | |||
+ | Avec le décorateur **@login_required** | ||
+ | |||
+ | :!: Il est important de mettre les deux décorateurs dans cet ordre. | ||
+ | |||
+ | <code python> | ||
+ | from flask_login import login_required | ||
+ | |||
+ | @app.route('/secret') | ||
+ | @login_required | ||
+ | def secret(): | ||
+ | return 'Seuls les utilisateurs authentifiés verront ceci.' | ||
</code> | </code> | ||