Witaj ponownie, @name! Darwin tutaj z ostatnią lekcją przed deployment - autentykacja użytkowników z Flask-Login! 🔐👤
Analogia Safari: Autentykacja to jak przepustki dla turystów - tylko osoby z ważnymi biletami mogą wejść do parku, obserwować zwierzęta, i korzystać z ekskluzywnych tras! Bez autentykacji każdy mógłby wejść i edytować Twoją bazę danych! 🎫🦁
Problem: Bez autentykacji każdy może:
Rozwiązanie: System logowania/rejestracji z Flask-Login!
Flask-Login to rozszerzenie zarządzające sesjami użytkowników:
✅ Login/logout użytkowników ✅ Zapamiętywanie sesji (cookies) ✅ Ochrona routes (
@login_required)
✅ Dostęp do current_user
✅ "Remember me" functionality1pip install Flask-LoginFlask-Login wymaga klasy
User z określonymi metodami. Najprościej użyć UserMixin:1# models.py
2from flask_login import UserMixin
3from werkzeug.security import generate_password_hash, check_password_hash
4
5class User(UserMixin):
6 def __init__(self, id, username, email, password_hash):
7 self.id = id
8 self.username = username
9 self.email = email
10 self.password_hash = password_hash
11
12 def set_password(self, password):
13 """Hash hasła"""
14 self.password_hash = generate_password_hash(password)
15
16 def check_password(self, password):
17 """Sprawdź hasło"""
18 return check_password_hash(self.password_hash, password)
19
20 def __repr__(self):
21 return f'<User {self.username}>'
dostarcza:UserMixin
is_authenticated - czy zalogowanyis_active - czy aktywnyis_anonymous - czy anonimowyget_id() - zwraca ID jako string1# app.py
2from flask import Flask
3from flask_login import LoginManager
4
5app = Flask(__name__)
6app.config['SECRET_KEY'] = 'your-secret-key-123'
7
8# Inicjalizuj LoginManager
9login_manager = LoginManager()
10login_manager.init_app(app)
11login_manager.login_view = 'login' # Przekierowanie dla niezalogowanych
12login_manager.login_message = 'Zaloguj się, aby uzyskać dostęp.'
13
14# Baza użytkowników (w produkcji: SQLite/PostgreSQL)
15users_db = {}
16next_user_id = 1
17
18# User loader - wymagany przez Flask-Login
19@login_manager.user_loader
20def load_user(user_id):
21 """Załaduj użytkownika po ID"""
22 return users_db.get(int(user_id))
- Flask-Login wywołuje tę funkcję, aby załadować użytkownika z sesji.@login_manager.user_loader
1from flask_wtf import FlaskForm
2from wtforms import StringField, PasswordField, SubmitField
3from wtforms.validators import DataRequired, Email, EqualTo, Length
4
5class RegistrationForm(FlaskForm):
6 username = StringField('Username',
7 validators=[DataRequired(), Length(min=4, max=20)])
8
9 email = StringField('Email',
10 validators=[DataRequired(), Email()])
11
12 password = PasswordField('Hasło',
13 validators=[DataRequired(), Length(min=8)])
14
15 confirm_password = PasswordField('Potwierdź hasło',
16 validators=[DataRequired(),
17 EqualTo('password', message='Hasła muszą być identyczne')])
18
19 submit = SubmitField('Zarejestruj się')1from flask import render_template, redirect, url_for, flash
2from flask_login import login_user
3from models import User
4from forms import RegistrationForm
5
6@app.route('/register', methods=['GET', 'POST'])
7def register():
8 form = RegistrationForm()
9
10 if form.validate_on_submit():
11 global next_user_id
12
13 # Sprawdź, czy username/email już istnieje
14 for user in users_db.values():
15 if user.username == form.username.data:
16 flash('Username już zajęty!', 'error')
17 return render_template('register.html', form=form)
18
19 if user.email == form.email.data:
20 flash('Email już zarejestrowany!', 'error')
21 return render_template('register.html', form=form)
22
23 # Utwórz nowego użytkownika
24 user = User(
25 id=next_user_id,
26 username=form.username.data,
27 email=form.email.data,
28 password_hash=None
29 )
30 user.set_password(form.password.data) # Hash hasła
31
32 users_db[next_user_id] = user
33 next_user_id += 1
34
35 flash(f'Konto {user.username} utworzone! Możesz się zalogować.', 'success')
36 return redirect(url_for('login'))
37
38 return render_template('register.html', form=form)1class LoginForm(FlaskForm):
2 username = StringField('Username', validators=[DataRequired()])
3 password = PasswordField('Hasło', validators=[DataRequired()])
4 remember = BooleanField('Zapamiętaj mnie')
5 submit = SubmitField('Zaloguj')1from flask_login import login_user, current_user, logout_user
2
3@app.route('/login', methods=['GET', 'POST'])
4def login():
5 # Jeśli już zalogowany, przekieruj
6 if current_user.is_authenticated:
7 return redirect(url_for('home'))
8
9 form = LoginForm()
10
11 if form.validate_on_submit():
12 # Znajdź użytkownika
13 user = None
14 for u in users_db.values():
15 if u.username == form.username.data:
16 user = u
17 break
18
19 # Sprawdź hasło
20 if user and user.check_password(form.password.data):
21 login_user(user, remember=form.remember.data)
22 flash(f'Witaj z powrotem, {user.username}!', 'success')
23
24 # Przekieruj do next_page lub home
25 next_page = request.args.get('next')
26 return redirect(next_page) if next_page else redirect(url_for('home'))
27 else:
28 flash('Nieprawidłowy username lub hasło!', 'error')
29
30 return render_template('login.html', form=form)
- zaloguj użytkownika i stwórz sesję.login_user(user, remember=True)
1@app.route('/logout')
2def logout():
3 logout_user()
4 flash('Zostałeś wylogowany.', 'info')
5 return redirect(url_for('home'))
- wyloguj użytkownika i usuń sesję.logout_user()
Dekorator
@login_required chroni routes przed niezalogowanymi użytkownikami:1from flask_login import login_required, current_user
2
3@app.route('/species/add', methods=['GET', 'POST'])
4@login_required # Tylko zalogowani
5def add_species():
6 form = AddSpeciesForm()
7
8 if form.validate_on_submit():
9 # ... dodaj gatunek ...
10 flash(f'Gatunek dodany przez {current_user.username}!', 'success')
11 return redirect(url_for('list_species'))
12
13 return render_template('add_species.html', form=form)
14
15@app.route('/profile')
16@login_required
17def profile():
18 return render_template('profile.html', user=current_user)Jeśli niezalogowany użytkownik wejdzie na
/species/add:/login/species/addcurrent_user to proxy do zalogowanego użytkownika:1from flask_login import current_user
2
3@app.route('/')
4def home():
5 if current_user.is_authenticated:
6 return render_template('home.html',
7 username=current_user.username,
8 species_count=len(species_db))
9 else:
10 return render_template('home.html',
11 username=None,
12 species_count=len(species_db))1{% if current_user.is_authenticated %}
2 <p>Zalogowany jako: <strong>{{ current_user.username }}</strong></p>
3 <a href="{{ url_for('logout') }}">Wyloguj</a>
4{% else %}
5 <a href="{{ url_for('login') }}">Zaloguj</a> |
6 <a href="{{ url_for('register') }}">Zarejestruj się</a>
7{% endif %}NIGDY nie przechowuj haseł w plain text! Używaj haszowania:
1from werkzeug.security import generate_password_hash, check_password_hash
2
3# Rejestracja - hashuj hasło
4password_hash = generate_password_hash('TajneHaslo123')
5# Output: 'pbkdf2:sha256:260000$...'
6
7# Logowanie - sprawdź hasło
8is_correct = check_password_hash(password_hash, 'TajneHaslo123')
9# Output: True
używa PBKDF2 z solą i wieloma iteracjami - bezpieczne!generate_password_hash()
1from flask_login import UserMixin
2from werkzeug.security import generate_password_hash, check_password_hash
3
4class User(UserMixin):
5 def __init__(self, id, username, email, password_hash):
6 self.id = id
7 self.username = username
8 self.email = email
9 self.password_hash = password_hash
10
11 def set_password(self, password):
12 self.password_hash = generate_password_hash(password)
13
14 def check_password(self, password):
15 return check_password_hash(self.password_hash, password)1from flask_wtf import FlaskForm
2from wtforms import StringField, PasswordField, BooleanField, SubmitField
3from wtforms.validators import DataRequired, Email, EqualTo, Length
4
5class RegistrationForm(FlaskForm):
6 username = StringField('Username', validators=[DataRequired(), Length(min=4, max=20)])
7 email = StringField('Email', validators=[DataRequired(), Email()])
8 password = PasswordField('Hasło', validators=[DataRequired(), Length(min=8)])
9 confirm_password = PasswordField('Potwierdź hasło',
10 validators=[DataRequired(), EqualTo('password')])
11 submit = SubmitField('Zarejestruj')
12
13class LoginForm(FlaskForm):
14 username = StringField('Username', validators=[DataRequired()])
15 password = PasswordField('Hasło', validators=[DataRequired()])
16 remember = BooleanField('Zapamiętaj mnie')
17 submit = SubmitField('Zaloguj')1from flask import Flask, render_template, redirect, url_for, flash, request
2from flask_login import LoginManager, login_user, logout_user, login_required, current_user
3from models import User
4from forms import RegistrationForm, LoginForm, AddSpeciesForm
5
6app = Flask(__name__)
7app.config['SECRET_KEY'] = 'your-secret-key-123'
8
9# Flask-Login
10login_manager = LoginManager()
11login_manager.init_app(app)
12login_manager.login_view = 'login'
13
14# Bazy danych (in-memory)
15users_db = {}
16next_user_id = 1
17
18species_db = {}
19next_species_id = 1
20
21@login_manager.user_loader
22def load_user(user_id):
23 return users_db.get(int(user_id))
24
25@app.route('/')
26def home():
27 return render_template('home.html',
28 species_count=len(species_db))
29
30@app.route('/register', methods=['GET', 'POST'])
31def register():
32 if current_user.is_authenticated:
33 return redirect(url_for('home'))
34
35 form = RegistrationForm()
36
37 if form.validate_on_submit():
38 global next_user_id
39
40 # Sprawdź duplikaty
41 for user in users_db.values():
42 if user.username == form.username.data:
43 flash('Username już zajęty!', 'error')
44 return render_template('register.html', form=form)
45
46 # Utwórz użytkownika
47 user = User(next_user_id, form.username.data, form.email.data, None)
48 user.set_password(form.password.data)
49 users_db[next_user_id] = user
50 next_user_id += 1
51
52 flash(f'Konto utworzone! Zaloguj się.', 'success')
53 return redirect(url_for('login'))
54
55 return render_template('register.html', form=form)
56
57@app.route('/login', methods=['GET', 'POST'])
58def login():
59 if current_user.is_authenticated:
60 return redirect(url_for('home'))
61
62 form = LoginForm()
63
64 if form.validate_on_submit():
65 user = None
66 for u in users_db.values():
67 if u.username == form.username.data:
68 user = u
69 break
70
71 if user and user.check_password(form.password.data):
72 login_user(user, remember=form.remember.data)
73 flash(f'Witaj, {user.username}!', 'success')
74
75 next_page = request.args.get('next')
76 return redirect(next_page) if next_page else redirect(url_for('home'))
77 else:
78 flash('Nieprawidłowy username lub hasło!', 'error')
79
80 return render_template('login.html', form=form)
81
82@app.route('/logout')
83@login_required
84def logout():
85 logout_user()
86 flash('Wylogowano.', 'info')
87 return redirect(url_for('home'))
88
89@app.route('/species/add', methods=['GET', 'POST'])
90@login_required
91def add_species():
92 form = AddSpeciesForm()
93
94 if form.validate_on_submit():
95 global next_species_id
96
97 new_species = {
98 'id': next_species_id,
99 'name': form.name.data,
100 'added_by': current_user.username
101 }
102
103 species_db[next_species_id] = new_species
104 next_species_id += 1
105
106 flash(f'Gatunek dodany przez {current_user.username}!', 'success')
107 return redirect(url_for('list_species'))
108
109 return render_template('add_species.html', form=form)
110
111if __name__ == '__main__':
112 app.run(debug=True)1<nav>
2 <a href="{{ url_for('home') }}">Home</a>
3 <a href="{{ url_for('list_species') }}">Gatunki</a>
4
5 {% if current_user.is_authenticated %}
6 <a href="{{ url_for('add_species') }}">Dodaj gatunek</a>
7 <a href="{{ url_for('profile') }}">Profil ({{ current_user.username }})</a>
8 <a href="{{ url_for('logout') }}">Wyloguj</a>
9 {% else %}
10 <a href="{{ url_for('login') }}">Zaloguj</a>
11 <a href="{{ url_for('register') }}">Zarejestruj</a>
12 {% endif %}
13</nav>W tej lekcji nauczyłeś/aś się:
@login_required - ochrona routescurrent_user - dostęp do zalogowanego użytkownikaAnalogia Safari finalna: Flask-Login to jak system przepustek safari - tylko turyści z ważnymi biletami (
current_user.is_authenticated) mogą wejść do strefy chronionych tras (@login_required), a każdy turysta ma swój unikalny identyfikator (user.id) i hasło (haszowane)! 🎫🔐🦁Następna lekcja: Darwin pokaże Ci deployment - publikowanie aplikacji Flask w internecie (Gunicorn, Render, Railway)! 🚀🌐