Flaskauthentication and authorization

overview

authentication and authorization is WebapplicationDevelopmentin important 组成部分. authentication is verificationuser身份 过程, 而authorization则 is 确定user可以访问哪些resource 过程. 本章节将介绍such as何 in Flaskapplicationinimplementationuserauthentication and authorizationfunctions, includingpassword哈希, sessionmanagement, rolepermissionetc. in 容.

1. password哈希 and securitystore

store明文password is 非常不security 做法. 我们应该usingpassword哈希algorithms将password转换 for 不可逆 哈希值, 然 after 只store哈希值. Flask-Bcrypt is a scale, 它providing了bcrypt哈希algorithms encapsulation, 方便我们 in Flaskapplicationinusing.

1.1 installationFlask-Bcrypt

pip install flask-bcrypt

1.2 configurationFlask-Bcrypt

from flask import Flask
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
bcrypt = Bcrypt(app)

1.3 password哈希 and verification

我们可以usingFlask-Bcryptproviding generate_password_hashmethod来生成password哈希, usingcheck_password_hashmethod来verificationpassword:

# 生成password哈希
hashed_password = bcrypt.generate_password_hash('password').decode('utf-8')

# verificationpassword
if bcrypt.check_password_hash(hashed_password, 'password'):
    print("password正确")
else:
    print("passworderror")

1.4 in usermodelinusingpassword哈希

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    
    def set_password(self, password):
        self.password = bcrypt.generate_password_hash(password).decode('utf-8')
        
    def check_password(self, password):
        return bcrypt.check_password_hash(self.password, password)

2. sessionmanagement and loginstatus

sessionmanagement is Webapplicationin用于跟踪userloginstatus mechanism. Flaskproviding了 in 置 sessionsupport, 我们可以usingsessionobject来store and 访问sessiondata.

2.1 configurationsessionkey

for 了security起见, 我们需要configuration一个随机 sessionkey:

app.config['SECRET_KEY'] = 'your-secret-key'  # 应该using一个随机生成 string

2.2 loginfunctionsimplementation

from flask import render_template, url_for, flash, redirect, request, session
from app import app, db, bcrypt
from app.forms import LoginForm
from app.models import User

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.check_password(form.password.data):
            # storeuserID to sessionin
            session['user_id'] = user.id
            flash('login成功!', 'success')
            return redirect(url_for('home'))
        else:
            flash('login失败, 请check邮箱 and password', 'danger')
    return render_template('login.html', title='login', form=form)

2.3 登出functionsimplementation

@app.route("/logout")
def logout():
    #  from sessionin移除userID
    session.pop('user_id', None)
    flash('已成功登出', 'success')
    return redirect(url_for('home'))

2.4 loginstatuscheck

我们可以creation一个装饰器来checkuser is 否已login:

from functools import wraps
from flask import session, flash, redirect, url_for

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('请先login', 'warning')
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

# using装饰器保护routing
@app.route("/dashboard")
@login_required
def dashboard():
    return render_template('dashboard.html')

2.5 获取当 before user

我们可以creation一个function来获取当 before login user:

def get_current_user():
    if 'user_id' in session:
        return User.query.get(session['user_id'])
    return None

#  in routinginusing
@app.route("/profile")
@login_required
def profile():
    user = get_current_user()
    return render_template('profile.html', user=user)

3. Flask-Loginscale

Flask-Login is a 专门用于processinguserauthentication Flaskscale, 它providing了更完整 sessionmanagementfunctions, including记住我, login保护, 当 before user获取etc..

3.1 installationFlask-Login

pip install flask-login

3.2 configurationFlask-Login

from flask import Flask
from flask_login import Loginmanagementr

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'

# configurationLoginmanagementr
login_manager = Loginmanagementr(app)
login_manager.login_view = 'login'  # 设置login视graph
login_manager.login_message_category = 'info'  # 设置loginmessageclass别

3.3 updateusermodel

我们需要updateusermodel, 使其implementationFlask-Login要求 method:

from flask_login import UserMixin
from app import db, bcrypt

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    
    def set_password(self, password):
        self.password = bcrypt.generate_password_hash(password).decode('utf-8')
        
    def check_password(self, password):
        return bcrypt.check_password_hash(self.password, password)

3.4 加载userfunction

我们需要creation一个function来根据userID加载user:

from app.models import User

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

3.5 usingFlask-Login

现 in 我们可以usingFlask-Loginproviding functions:

loginfunctions

from flask import render_template, url_for, flash, redirect
from flask_login import login_user
from app import app, db
from app.forms import LoginForm
from app.models import User

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.check_password(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('home'))
        else:
            flash('login失败, 请check邮箱 and password', 'danger')
    return render_template('login.html', title='login', form=form)

登出functions

from flask_login import logout_user

@app.route("/logout")
def logout():
    logout_user()
    flash('已成功登出', 'success')
    return redirect(url_for('home'))

login保护装饰器

from flask_login import login_required

@app.route("/dashboard")
@login_required
def dashboard():
    return render_template('dashboard.html')

获取当 before user

from flask_login import current_user

@app.route("/profile")
@login_required
def profile():
    return render_template('profile.html', user=current_user)

4. role and permissionmanagement

in 许 many applicationin, 我们需要 for 不同 user分配不同 role, 并根据role控制 for resource 访问. 例such as, management员可以访问所 has resource, 而普通user只能访问部分resource.

4.1 rolemodeldesign

我们可以creation一个Rolemodel来表示userrole, 然 after using many for many relationships将user and role关联起来:

from flask_login import UserMixin
from app import db

#  many  for  many 关联表
roles_users = db.Table('roles_users',
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('role.id'))
)

class Role(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20), unique=True, nullable=False)
    description = db.Column(db.String(100))

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))

4.2 role分配

我们可以 in userregister时 for user分配默认role, or 者 in management界面in手动分配role:

# register时分配默认role
@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data)
        user.set_password(form.password.data)
        
        # 分配默认role
        default_role = Role.query.filter_by(name='User').first()
        if default_role:
            user.roles.append(default_role)
        
        db.session.add(user)
        db.session.submitting()
        flash('register成功!', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', title='register', form=form)

4.3 permissioncheck装饰器

我们可以creation一个装饰器来checkuser is 否具 has specificrole:

from functools import wraps
from flask import flash, redirect, url_for
from flask_login import current_user

def role_required(role_name):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            # checkuser is 否login且具 has 指定role
            if not current_user.is_authenticated:
                flash('请先login', 'warning')
                return redirect(url_for('login'))
            
            has_role = any(role.name == role_name for role in current_user.roles)
            if not has_role:
                flash('您没 has permission访问此页面', 'danger')
                return redirect(url_for('home'))
            
            return f(*args, **kwargs)
        return decorated_function
    return decorator

# using装饰器保护routing
@app.route("/admin/dashboard")
@role_required('Admin')
def admin_dashboard():
    return render_template('admin/dashboard.html')

4.4 in 模板incheckrole

我们可以 in 模板incheck当 before user is 否具 has specificrole:

<!--  in 模板incheckrole -->
{% if current_user.is_authenticated %}
    <p>欢迎, {{ current_user.username }}!</p>
    
    {% if 'Admin' in current_user.roles|map(attribute='name') %}        <a href="{{ url_for('admin_dashboard') }}">management员面板</a>
    {% endif %}
    
    <a href="{{ url_for('logout') }}">登出</a>
{% else %}
    <a href="{{ url_for('login') }}">login</a>
    <a href="{{ url_for('register') }}">register</a>
{% endif %}

5. Flask-Securityscale

Flask-Security is a functions完整 authentication and authorizationscale, 它集成了Flask-Login, Flask-Bcrypt, Flask-WTFetc.scale, providing了register, login, passwordreset, rolemanagementetc.完整functions.

5.1 installationFlask-Security

pip install flask-security

5.2 configurationFlask-Security

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt'
app.config['SECURITY_PASSWORD_SALT'] = 'your-salt'
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_SEND_REGISTER_EMAIL'] = False

db = SQLAlchemy(app)

# 定义model
roles_users = db.Table('roles_users',
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('role.id'))
)

class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))

# 设置userdatastore
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)

# creation默认role and user
with app.app_context():
    db.create_all()
    # creationrole
    user_datastore.find_or_create_role(name='Admin', description='Administrator')
    user_datastore.find_or_create_role(name='User', description='Regular User')
    # creationmanagement员user
    if not User.query.filter_by(email='admin@example.com').first():
        user_datastore.create_user(
            email='admin@example.com',
            password='password',
            roles=['Admin']
        )
    db.session.submitting()

5.3 usingFlask-Security

Flask-Securityproviding了一系列默认routing and 视graph, including:

  • /login - login页面
  • /logout - 登出页面
  • /register - register页面
  • /change-password - modifypassword页面
  • /reset-password - resetpassword页面

我们可以自定义这些视graph and 模板, 也可以usingFlask-Securityproviding 装饰器来保护routing:

from flask_security import login_required, roles_required

# usinglogin保护装饰器
@app.route("/profile")
@login_required
def profile():
    return render_template('profile.html')

# usingrole保护装饰器
@app.route("/admin")
@roles_required('Admin')
def admin():
    return render_template('admin.html')

6. best practices

  • 始终usingpassword哈希storepassword, 不要store明文password
  • using强随机keyserving asSECRET_KEY
  • for 不同 environmentusing不同 configuration (Developmentenvironment, testenvironment, produceenvironment)
  • usingHTTPSprotocol保护userdata传输
  • implementationpasswordresetfunctions
  • for login失败次数设置限制, 防止暴力破解
  • usingrolepermissionsystem控制resource访问
  • 定期updatepassword哈希algorithms and parameter

summarized

本章节介绍了Flaskin authentication and authorizationfunctions, including:

  • usingFlask-Bcryptforpassword哈希 and verification
  • usingFlask in 置sessionmanagementuserloginstatus
  • usingFlask-Loginscaleimplementation更完整 authenticationfunctions
  • implementationrole and permissionmanagementsystem
  • usingFlask-Securityscaleimplementation完整 authenticationauthorizationfunctions

authentication and authorization is Webapplicationsecurity important 组成部分, Master这些knowledge将helping你Development出security reliable Flaskapplication.

codeexample

以 under is a 完整 Flaskauthentication and authorizationexample:

from flask import Flask, render_template, url_for, flash, redirect, request
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import Loginmanagementr, UserMixin, login_user, logout_user, login_required, current_user

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = Loginmanagementr(app)
login_manager.login_view = 'login'
login_manager.login_message_category = 'info'

#  many  for  many 关联表
roles_users = db.Table('roles_users',
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('role.id'))
)

class Role(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20), unique=True, nullable=False)
    description = db.Column(db.String(100))

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
    
    def set_password(self, password):
        self.password = bcrypt.generate_password_hash(password).decode('utf-8')
        
    def check_password(self, password):
        return bcrypt.check_password_hash(self.password, password)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# creation表单class
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo

class RegistrationForm(FlaskForm):
    username = StringField('user名', validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    confirm_password = PasswordField('确认password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('register')

class LoginForm(FlaskForm):
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    remember = BooleanField('记住我')
    submit = SubmitField('login')

# routing
@app.route("/")
def home():
    return render_template('home.html')

@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data)
        user.set_password(form.password.data)
        
        # 分配默认role
        default_role = Role.query.filter_by(name='User').first()
        if default_role:
            user.roles.append(default_role)
        
        db.session.add(user)
        db.session.submitting()
        flash('register成功!', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', title='register', form=form)

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.check_password(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('home'))
        else:
            flash('login失败, 请check邮箱 and password', 'danger')
    return render_template('login.html', title='login', form=form)

@app.route("/logout")
def logout():
    logout_user()
    return redirect(url_for('home'))

@app.route("/profile")
@login_required
def profile():
    return render_template('profile.html', title='个人资料')

# rolecheck装饰器
from functools import wraps

def role_required(role_name):
    def decorator(f):
        @wraps(f)
        @login_required
        def decorated_function(*args, **kwargs):
            has_role = any(role.name == role_name for role in current_user.roles)
            if not has_role:
                flash('您没 has permission访问此页面', 'danger')
                return redirect(url_for('home'))
            return f(*args, **kwargs)
        return decorated_function
    return decorator

@app.route("/admin")
@role_required('Admin')
def admin():
    return render_template('admin.html', title='management员面板')

# creation默认data
with app.app_context():
    db.create_all()
    # creationrole
    if not Role.query.filter_by(name='Admin').first():
        admin_role = Role(name='Admin', description='management员')
        db.session.add(admin_role)
    if not Role.query.filter_by(name='User').first():
        user_role = Role(name='User', description='普通user')
        db.session.add(user_role)
    # creationmanagement员user
    if not User.query.filter_by(email='admin@example.com').first():
        admin_user = User(username='admin', email='admin@example.com')
        admin_user.set_password('admin123')
        admin_role = Role.query.filter_by(name='Admin').first()
        if admin_role:
            admin_user.roles.append(admin_role)
        db.session.add(admin_user)
    db.session.submitting()

if __name__ == '__main__':
    app.run(debug=True)

练习题

  1. usingFlask-Bcryptimplementationpassword哈希 and verification
  2. usingFlask-Loginimplementationuserlogin, 登出 and login保护
  3. design并implementation一个rolepermissionsystem
  4. creation不同role user, 并testpermission控制
  5. implementationpasswordresetfunctions