Bygg ett sÀkert inloggningssystem i Flask. Guiden tÀcker projektuppsÀttning, lösenordskryptering, sessionshantering och avancerad sÀkerhet.
Flask-autentisering: En omfattande guide för att bygga sÀkra inloggningssystem
I dagens digitala vĂ€rld krĂ€ver nĂ€stan alla meningsfulla webbapplikationer ett sĂ€tt att hantera och identifiera sina anvĂ€ndare. Oavsett om du bygger ett socialt nĂ€tverk, en e-handelsplattform eller ett företagsintranĂ€t, Ă€r ett sĂ€kert och pĂ„litligt autentiseringssystem inte bara en funktion â det Ă€r ett grundlĂ€ggande krav. Det Ă€r den digitala grindvakten som skyddar anvĂ€ndardata, personaliserar upplevelser och möjliggör förtroende.
Flask, det populÀra Python-mikroramverket, erbjuder flexibiliteten att bygga kraftfulla webbapplikationer, men det lÀmnar medvetet autentiseringsimplementeringen till utvecklaren. Detta minimalistiska tillvÀgagÄngssÀtt Àr en styrka, vilket gör att du kan vÀlja de bÀsta verktygen för jobbet utan att vara lÄst till en specifik metodik. Det innebÀr dock ocksÄ att du Àr ansvarig för att bygga systemet korrekt och sÀkert.
Denna omfattande guide Àr utformad för en internationell publik av utvecklare. Vi kommer att guida dig genom varje steg för att bygga ett komplett, produktionsklart inloggningssystem för anvÀndare i Flask. Vi börjar med det absoluta grundlÀggande och bygger gradvis upp till en robust lösning, som tÀcker viktiga sÀkerhetsrutiner lÀngs vÀgen. I slutet av denna handledning kommer du att ha kunskapen och koden för att implementera sÀker anvÀndarregistrering, inloggning och sessionshantering i dina egna Flask-projekt.
FörutsÀttningar: Konfigurera din utvecklingsmiljö
Innan vi skriver vÄr första rad autentiseringskod behöver vi upprÀtta en ren och organiserad utvecklingsmiljö. Detta Àr en universell bÀsta praxis inom mjukvaruutveckling, som sÀkerstÀller att ditt projekts beroenden inte krockar med andra projekt pÄ ditt system.
1. Python och virtuella miljöer
Se till att du har Python 3.6 eller senare installerat pĂ„ ditt system. Vi kommer att anvĂ€nda en virtuell miljö för att isolera vĂ„rt projekts paket. Ăppna din terminal eller kommandotolk och kör följande kommandon:
# Skapa en projektkatalog
mkdir flask-auth-project
cd flask-auth-project
# Skapa en virtuell miljö (mappen 'venv')
python3 -m venv venv
# Aktivera den virtuella miljön
# PĂ„ macOS/Linux:
source venv/bin/activate
# PĂ„ Windows:
venv\Scripts\activate
Du vet att miljön Àr aktiv nÀr du ser `(venv)` som prefix pÄ din kommandotolk.
2. Installera nödvÀndiga Flask-tillÀgg
VÄrt autentiseringssystem kommer att byggas pÄ en stapel av utmÀrkta, vÀl underhÄllna Flask-tillÀgg. Var och en tjÀnar ett specifikt syfte:
- Flask: KĂ€rnwebbramverket.
- Flask-SQLAlchemy: En Object-Relational Mapper (ORM) för att interagera med vÄr databas pÄ ett Pythoniskt sÀtt.
- Flask-Migrate: Hanterar databasschemamigreringar.
- Flask-WTF: Förenklar arbetet med webbformulÀr, ger validering och CSRF-skydd.
- Flask-Login: Hanterar anvÀndarsessionen, hanterar inloggning, utloggning och att komma ihÄg anvÀndare.
- Flask-Bcrypt: Ger starka lösenordskrypteringsfunktioner.
- python-dotenv: Hanterar miljövariabler för konfiguration.
Installera dem alla med ett enda kommando:
pip install Flask Flask-SQLAlchemy Flask-Migrate Flask-WTF Flask-Login Flask-Bcrypt python-dotenv
Del 1: Grunden - Projektstruktur och databasmodell
Ett vÀlorganiserat projekt Àr lÀttare att underhÄlla, skala och förstÄ. Vi kommer att anvÀnda ett vanligt Flask-applikationsfabriksmönster.
Designa en skalbar projektstruktur
Skapa följande katalog- och filstruktur inuti din `flask-auth-project`-katalog:
/flask-auth-project
|-- /app
| |-- /static
| |-- /templates
| | |-- base.html
| | |-- index.html
| | |-- login.html
| | |-- register.html
| | |-- dashboard.html
| |-- __init__.py
| |-- models.py
| |-- forms.py
| |-- routes.py
|-- .env
|-- config.py
|-- run.py
- /app: Huvudpaketet som innehÄller vÄr applikationslogik.
- /templates: Kommer att innehÄlla vÄra HTML-filer.
- __init__.py: Initierar vÄr Flask-applikation (applikationsfabriken).
- models.py: Definierar vÄra databastabeller (t.ex. User-modellen).
- forms.py: Definierar vÄra registrerings- och inloggningsformulÀr med Flask-WTF.
- routes.py: InnehÄller vÄra vyfunktioner (logiken för olika URL:er).
- config.py: Lagrar applikationens konfigurationsinstÀllningar.
- run.py: Huvudskriptet för att starta webbservern.
- .env: En fil för att lagra miljövariabler som hemliga nycklar (denna fil bör INTE checkas in i versionshanteringen).
Konfigurera din Flask-applikation
LÄt oss fylla vÄra konfigurationsfiler.
.env fil:
Skapa denna fil i roten av ditt projekt. Det Àr hÀr vi lagrar kÀnslig information.
SECRET_KEY='en-mycket-stark-och-lÄng-slumpmÀssig-hemlig-nyckel'
DATABASE_URL='sqlite:///site.db'
VIKTIGT: ErsÀtt vÀrdet för `SECRET_KEY` med din egen lÄnga, slumpmÀssiga och oförutsÀgbara strÀng. Denna nyckel Àr avgörande för att sÀkra anvÀndarsessioner.
config.py fil:
Denna fil lÀser konfigurationen frÄn vÄr `.env`-fil.
import os
from dotenv import load_dotenv
basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(basedir, '.env'))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
Skapa anvÀndarmodellen med Flask-SQLAlchemy
User-modellen Àr hjÀrtat i vÄrt autentiseringssystem. Den definierar strukturen för `users`-tabellen i vÄr databas.
app/models.py:
from flask_login import UserMixin
from . import db, login_manager
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
def __repr__(self):
return f''
LÄt oss bryta ner detta:
- `UserMixin`: Detta Àr en klass frÄn `Flask-Login` som innehÄller generiska implementeringar för metoder som `is_authenticated`, `is_active` etc., som vÄr User-modell behöver.
- `@login_manager.user_loader`: Denna funktion Àr ett krav för `Flask-Login`. Den anvÀnds för att ladda om anvÀndarobjektet frÄn anvÀndar-ID:t som lagras i sessionen. Flask-Login kommer att anropa denna funktion vid varje begÀran för en inloggad anvÀndare.
- `password_hash`: Observera att vi INTE lagrar lösenordet direkt. Vi lagrar en `password_hash`. Detta Àr en av de mest kritiska sÀkerhetsprinciperna inom autentisering. Att lagra lösenord i klartext Àr en enorm sÀkerhetsbrist. Om din databas nÄgonsin komprometteras kommer angripare att ha alla anvÀndares lösenord. Genom att lagra en hash gör vi det berÀkningsmÀssigt ogenomförbart för dem att hÀmta de ursprungliga lösenorden.
Initiera applikationen
Nu binder vi ihop allt i vÄr applikationsfabrik.
app/__init__.py:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from config import Config
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()
login_manager.login_view = 'main.login' # Omdirigeringssida för anvÀndare som inte Àr inloggade
login_manager.login_message_category = 'info' # Bootstrap-klass för meddelanden
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
from .routes import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
run.py:
from app import create_app, db
from app.models import User
app = create_app()
@app.shell_context_processor
def make_shell_context():
return {'db': db, 'User': User}
if __name__ == '__main__':
app.run(debug=True)
Innan vi kan köra appen mÄste vi skapa databasen. FrÄn din aktiverade virtuella miljö i terminalen, kör dessa kommandon:
flask shell
>>> from app import db
>>> db.create_all()
>>> exit()
Detta kommer att skapa en `site.db`-fil i din rotkatalog, som innehÄller `user`-tabellen vi definierade.
Del 2: Bygga den centrala autentiseringslogiken
Med grunden pÄ plats kan vi nu bygga de anvÀndarvÀnda delarna: registrerings- och inloggningsformulÀr samt de routes som bearbetar dem.
AnvÀndarregistrering: Registrera nya anvÀndare sÀkert
Först definierar vi formulÀret med Flask-WTF.
app/forms.py:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from .models import User
class RegistrationForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken. Please choose a different one.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('That email is already registered. Please choose a different one.')
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
LÀgg mÀrke till de anpassade valideringarna `validate_username` och `validate_email`. Flask-WTF anropar automatiskt alla metoder med mönstret `validate_
Sedan skapar vi routern för att hantera registreringen.
app/routes.py:
from flask import Blueprint, render_template, url_for, flash, redirect, request
from .forms import RegistrationForm, LoginForm
from .models import User
from . import db, bcrypt
from flask_login import login_user, current_user, logout_user, login_required
main = Blueprint('main', __name__)
@main.route('/')
@main.route('/index')
def index():
return render_template('index.html')
@main.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password_hash=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in', 'success')
return redirect(url_for('main.login'))
return render_template('register.html', title='Register', form=form)
Lösenordskryptering med Flask-Bcrypt
Den viktigaste raden i koden ovan Àr:
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
Bcrypt Àr en modern, adaptiv krypteringsalgoritm. Den tar anvÀndarens lösenord och utför en komplex, berÀkningsmÀssigt kostsam envÀgsomvandling pÄ det. Den inkluderar ocksÄ ett slumpmÀssigt "salt" för varje lösenord för att förhindra attacker med regnbÄgstabeller. Detta innebÀr att Àven om tvÄ anvÀndare har samma lösenord, kommer deras lagrade hashvÀrden att vara helt olika. Den resulterande hashen Àr vad vi lagrar i databasen. Det Àr nÀstan omöjligt att reversera denna process för att fÄ det ursprungliga lösenordet.
AnvÀndarinloggning: Autentisera befintliga anvÀndare
Nu lÀgger vi till inloggningsroutern till vÄr `app/routes.py`-fil.
app/routes.py (lÀgg till denna route):
@main.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password_hash, form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('main.index'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return render_template('login.html', title='Login', form=form)
De viktigaste stegen hÀr Àr:
- Hitta anvÀndaren: Vi gör en databasfrÄga efter en anvÀndare med den angivna e-postadressen.
- Verifiera lösenordet: Detta Àr den avgörande kontrollen: `bcrypt.check_password_hash(user.password_hash, form.password.data)`. Denna funktion tar den lagrade hashen frÄn vÄr databas och det klartextlösenord som anvÀndaren just angav. Den krypterar det angivna lösenordet igen med samma salt (som Àr lagrat som en del av sjÀlva hashen) och jÀmför resultaten. Den returnerar `True` endast om de matchar. Detta gör att vi kan verifiera ett lösenord utan att nÄgonsin behöva dekryptera den lagrade hashen.
- Hantera sessionen: Om lösenordet Àr korrekt anropar vi `login_user(user, remember=form.remember.data)`. Denna funktion frÄn `Flask-Login` registrerar anvÀndaren som inloggad och lagrar deras ID i anvÀndarsessionen (en sÀker, serversides-cookie). Argumentet `remember` hanterar funktionen "Kom ihÄg mig".
AnvÀndarutloggning: Avsluta en session sÀkert
Utloggning Àr enkelt. Vi behöver bara en route som anropar `Flask-Login`s `logout_user`-funktion.
app/routes.py (lÀgg till denna route):
@main.route('/logout')
def logout():
logout_user()
return redirect(url_for('main.index'))
Denna funktion rensar anvÀndar-ID:t frÄn sessionen, vilket effektivt loggar ut dem.
Del 3: Skydda routes och hantera anvÀndarsessioner
Nu nÀr anvÀndare kan logga in och ut mÄste vi utnyttja deras autentiserade status.
Skydda innehÄll med `@login_required`
MÄnga sidor, som en anvÀndares instrumentpanel eller kontoinstÀllningar, bör endast vara tillgÀngliga för inloggade anvÀndare. `Flask-Login` gör detta otroligt enkelt med dekoratören `@login_required`.
LÄt oss skapa en skyddad instrumentpanelsroute.
app/routes.py (lÀgg till denna route):
@main.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', title='Dashboard')
Det var allt! Om en anvÀndare som inte Àr inloggad försöker besöka `/dashboard`, kommer `Flask-Login` automatiskt att avbryta begÀran och omdirigera dem till inloggningssidan (vilket vi konfigurerade i `app/__init__.py` med `login_manager.login_view = 'main.login'`). Efter att de framgÄngsrikt har loggat in, kommer den intelligent att omdirigera dem tillbaka till instrumentpanelsidan de ursprungligen försökte komma Ät.
à tkomst till den aktuella anvÀndarens information
Inom dina routes och mallar tillhandahÄller `Flask-Login` ett magiskt proxobjekt som kallas `current_user`. Detta objekt representerar den anvÀndare som för nÀrvarande Àr inloggad för den aktiva begÀran. Om ingen anvÀndare Àr inloggad Àr det ett anonymt anvÀndarobjekt dÀr `current_user.is_authenticated` kommer att vara `False`.
Du kan anvÀnda detta i din Python-kod:
# I en route
if current_user.is_authenticated:
print(f'Hello, {current_user.username}!')
Och du kan ocksÄ anvÀnda det direkt i dina Jinja2-mallar:
<!-- I en mall som base.html -->
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
<a href="{{ url_for('main.logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('main.login') }}">Login</a>
<a href="{{ url_for('main.register') }}">Register</a>
{% endif %}
Detta gör det möjligt för dig att dynamiskt Àndra navigeringsfÀltet eller andra delar av din UI baserat pÄ anvÀndarens inloggningsstatus.
HTML-mallar
För fullstÀndighetens skull, hÀr Àr nÄgra grundlÀggande mallar som du kan placera i `app/templates`-katalogen. De anvÀnder enkel HTML men kan enkelt integreras med ett ramverk som Bootstrap eller Tailwind CSS.
base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }} - Flask Auth App</title>
</head>
<body>
<nav>
<a href="{{ url_for('main.index') }}">Home</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
<a href="{{ url_for('main.logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('main.login') }}">Login</a>
<a href="{{ url_for('main.register') }}">Register</a>
{% endif %}
</nav>
<hr>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
register.html / login.html (exempel med registreringsformulÀr):
{% extends "base.html" %}
{% block content %}
<div>
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset>
<legend>Join Today</legend>
<div>
{{ form.username.label }}
{{ form.username() }}
</div>
<div>
{{ form.email.label }}
{{ form.email() }}
</div>
<div>
{{ form.password.label }}
{{ form.password() }}
</div>
<div>
{{ form.confirm_password.label }}
{{ form.confirm_password() }}
</div>
</fieldset>
<div>
{{ form.submit() }}
</div>
</form>
</div>
{% endblock content %}
Del 4: Avancerade Àmnen och sÀkerhetsrutiner
Systemet vi har byggt Àr stabilt, men en produktionsgradig applikation krÀver mer. HÀr Àr viktiga nÀsta steg och sÀkerhetsövervÀganden.
1. à terstÀllning av lösenord
AnvÀndare kommer oundvikligen att glömma sina lösenord. Ett sÀkert flöde för ÄterstÀllning av lösenord Àr avgörande. Den standardiserade, sÀkra processen Àr:
- AnvÀndaren anger sin e-postadress pÄ en sida för "Glömt lösenord".
- Applikationen genererar en sÀker, engÄngsanvÀnd, tidsbegrÀnsad token. `itsdangerous`-biblioteket (installerat med Flask) Àr perfekt för detta.
- Applikationen skickar ett e-postmeddelande till anvÀndaren som innehÄller en lÀnk med denna token.
- NÀr anvÀndaren klickar pÄ lÀnken validerar applikationen token (kontrollerar dess giltighet och utgÄngsdatum).
- Om den Àr giltig presenteras anvÀndaren med ett formulÀr för att ange och bekrÀfta ett nytt lösenord.
Skicka aldrig ett anvÀndares gamla lösenord eller ett nytt lösenord i klartext via e-post.
2. E-postbekrÀftelse vid registrering
För att förhindra att anvÀndare registrerar sig med falska e-postadresser och för att sÀkerstÀlla att du kan kontakta dem, bör du implementera ett steg för e-postbekrÀftelse. Processen liknar mycket en lösenordsÄterstÀllning: generera en token, skicka en bekrÀftelselÀnk via e-post och ha en route som validerar token och markerar anvÀndarens konto som `bekrÀftat` i databasen.
3. Rate limiting för att förhindra brute-force-attacker
En brute-force-attack Àr nÀr en angripare upprepade gÄnger försöker olika lösenord pÄ ett inloggningsformulÀr. För att mildra detta bör du implementera rate limiting. Detta begrÀnsar antalet inloggningsförsök som en enskild IP-adress kan göra inom en viss tidsram (t.ex. 5 misslyckade försök per minut). `Flask-Limiter`-tillÀgget Àr ett utmÀrkt verktyg för detta.
4. AnvÀnd miljövariabler för alla hemligheter
Vi har redan gjort detta för vÄr `SECRET_KEY` och `DATABASE_URL`, vilket Àr utmÀrkt. Det Àr en kritisk praxis för sÀkerhet och portabilitet. Checka aldrig in din `.env`-fil eller nÄgon fil med hÄrdkodade inloggningsuppgifter (som API-nycklar eller databaslösenord) i ett publikt versionshanteringssystem som GitHub. AnvÀnd alltid en `.gitignore`-fil för att exkludera dem.
5. Cross-Site Request Forgery (CSRF)-skydd
Goda nyheter! Genom att anvÀnda `Flask-WTF` och inkludera `{{ form.hidden_tag() }}` i vÄra formulÀr har vi redan aktiverat CSRF-skydd. Denna dolda tagg genererar en unik token för varje formulÀrinlÀmning, vilket sÀkerstÀller att begÀran kommer frÄn din faktiska webbplats och inte frÄn en skadlig extern kÀlla som försöker lura dina anvÀndare.
Slutsats: Dina nÀsta steg inom Flask-autentisering
Grattis! Du har framgÄngsrikt byggt ett komplett och sÀkert anvÀndarautentiseringssystem i Flask. Vi har tÀckt hela livscykeln: konfigurerat ett skalbart projekt, skapat en databasmodell, sÀkert hanterat anvÀndarregistrering med lösenordskryptering, autentiserat anvÀndare, hanterat sessioner med Flask-Login och skyddat routes.
Du har nu en robust grund som du tryggt kan integrera i alla Flask-projekt. Kom ihĂ„g att sĂ€kerhet Ă€r en pĂ„gĂ„ende process, inte en engĂ„ngsinstallation. De principer vi har diskuterat â sĂ€rskilt kryptering av lösenord och skydd av hemliga nycklar â Ă€r icke-förhandlingsbara för alla applikationer som hanterar anvĂ€ndardata.
HÀrifrÄn kan du utforska Ànnu mer avancerade autentiseringsteman för att ytterligare förbÀttra din applikation:
- Rollbaserad Ätkomstkontroll (RBAC): LÀgg till ett `role`-fÀlt till din User-modell för att ge olika behörigheter till vanliga anvÀndare och administratörer.
- OAuth-integration: LÄt anvÀndare logga in med hjÀlp av tredjepartstjÀnster som Google, GitHub eller Facebook.
- TvÄfaktorsautentisering (2FA): LÀgg till ett extra sÀkerhetslager genom att krÀva en kod frÄn en autentiseringsapp eller SMS.
Genom att bemÀstra grunderna för autentisering har du tagit ett betydande steg framÄt i din resa som professionell webbutvecklare. Lycka till med kodningen!