Python使用Flask进行用户认证与授权
一、Flask简介
Flask 是一个轻量级的 Python Web 应用框架,由 Armin Ronacher 开发。它基于 Werkzeug WSGI 工具包和 Jinja2 模板引擎。Flask 被设计为易于扩展和定制,非常适合快速构建小型到中型规模的 Web 应用。与一些大型的框架(如 Django)相比,Flask 核心功能简洁,开发者可以根据项目需求灵活选择各种扩展。
1.1 Flask 安装
在开始使用 Flask 之前,需要先安装它。如果已经安装了 Python 和 pip,可以通过以下命令进行安装:
pip install flask
这将从 Python Package Index(PyPI)下载并安装最新版本的 Flask。
1.2 一个简单的Flask应用
以下是一个最基本的 Flask 应用示例,用于展示 Flask 的简单使用:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run()
在上述代码中,首先导入 Flask
类。然后创建一个 Flask
应用实例 app
。@app.route('/')
是一个装饰器,它将根路径('/'
)映射到 hello_world
函数。当访问应用的根路径时,hello_world
函数会被调用并返回 'Hello, World!'
。app.run()
用于启动 Flask 应用,默认在 127.0.0.1:5000
运行。
二、用户认证基础概念
在深入探讨使用 Flask 进行用户认证与授权之前,我们需要先理解一些基本概念。
2.1 认证(Authentication)
认证是验证用户身份的过程。在 Web 应用中,这通常意味着用户提供凭据(如用户名和密码),应用验证这些凭据是否与存储在数据库或其他存储中的信息匹配。如果匹配,则用户被认证,应用知道该用户是谁。
2.2 授权(Authorization)
授权是确定已认证用户是否有权执行特定操作的过程。例如,只有管理员用户可能有权删除其他用户的账户,而普通用户没有此权限。授权通常基于角色(如管理员、普通用户)或特定的权限集来进行判断。
2.3 常用认证方式
- 基本认证(Basic Authentication):基本认证是一种简单的认证方式,用户在每次请求时将用户名和密码以 Base64 编码的形式包含在请求头中。虽然简单,但安全性较低,因为用户名和密码在每次请求时都被发送,且 Base64 编码很容易被解码。
- 表单认证(Form - Based Authentication):这是一种常见的认证方式,用户通过 HTML 表单输入用户名和密码,提交后服务器验证凭据。如果验证成功,服务器通常会在用户的浏览器中设置一个会话(session),后续请求可以通过这个会话来识别用户。
- Token - 基于认证(Token - Based Authentication):在这种方式下,用户认证成功后,服务器会生成一个令牌(token),并返回给用户。用户在后续请求中,将这个 token 包含在请求头或其他位置发送给服务器。服务器验证 token 的有效性来确定用户身份。这种方式在现代 Web 应用尤其是前后端分离的架构中非常流行,因为 token 可以在不同的服务之间共享,且不需要在服务器端存储过多的会话信息。
三、Flask 中的用户认证实现
3.1 使用 Flask - HTTPBasicAuth 实现基本认证
Flask - HTTPBasicAuth
是一个用于在 Flask 应用中实现基本认证的扩展。
3.1.1 安装 Flask - HTTPBasicAuth
可以使用 pip 进行安装:
pip install flask - httpbasicauth
3.1.2 代码示例
from flask import Flask
from flask_httpbasicauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
# 模拟用户数据库
users = {
"admin": "password123",
"user": "userpass"
}
@auth.verify_password
def verify_password(username, password):
if username in users and users[username] == password:
return True
return False
@app.route('/protected')
@auth.login_required
def protected():
return 'This is a protected resource'
if __name__ == '__main__':
app.run()
在上述代码中,首先导入 Flask
和 HTTPBasicAuth
。创建 Flask
应用实例 app
和 HTTPBasicAuth
实例 auth
。定义一个模拟的用户数据库 users
。@auth.verify_password
装饰的函数 verify_password
用于验证用户名和密码。@app.route('/protected')
装饰的函数 protected
是一个受保护的路由,@auth.login_required
确保只有认证通过的用户才能访问该路由。
3.2 使用 Flask - Login 实现表单认证
Flask - Login
是一个用于在 Flask 应用中处理用户登录状态的扩展,非常适合实现表单认证。
3.2.1 安装 Flask - Login
通过 pip 安装:
pip install flask - login
3.2.2 数据库模型准备
假设使用 SQLite 数据库和 Flask - SQLAlchemy
来管理用户数据。首先安装 Flask - SQLAlchemy
:
pip install flask - sqlalchemy
以下是定义用户模型的代码:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
db = SQLAlchemy(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
db.create_all()
在上述代码中,创建 Flask
应用实例 app
,配置 SQLAlchemy
使用 SQLite 数据库。定义 User
类,它继承自 UserMixin
和 db.Model
。UserMixin
为用户模型提供了一些默认的方法,如 is_authenticated
、is_active
、is_anonymous
和 get_id
,这些方法在 Flask - Login
中很有用。
3.2.3 认证相关代码
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
app.secret_key = 'your_secret_key'
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username, password=password).first()
if user:
login_user(user)
return redirect(url_for('protected'))
else:
return 'Invalid credentials'
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
@app.route('/protected')
@login_required
def protected():
return 'This is a protected page'
if __name__ == '__main__':
db.create_all()
app.run()
在上述代码中,LoginManager
实例 login_manager
被初始化并关联到应用。@login_manager.user_loader
装饰的函数 load_user
用于从数据库中加载用户对象。/login
路由处理用户登录,验证用户名和密码,如果正确则使用 login_user
登录用户并重定向到受保护页面,否则返回错误信息。/logout
路由使用 logout_user
注销用户并重定向到登录页面。/protected
路由是一个受保护的页面,只有登录用户可以访问。
四、Flask 中的用户授权实现
4.1 基于角色的授权
基于角色的授权是一种常见的授权方式,不同角色(如管理员、普通用户)具有不同的权限。
4.1.1 数据库模型扩展
在前面的用户模型基础上,添加角色字段:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
db = SQLAlchemy(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(50), nullable=False, default='user')
db.create_all()
4.1.2 授权代码示例
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
app.secret_key = 'your_secret_key'
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(50), nullable=False, default='user')
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username, password=password).first()
if user:
login_user(user)
return redirect(url_for('protected'))
else:
return 'Invalid credentials'
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
@app.route('/admin - only')
@login_required
def admin_only():
user = current_user
if user.role!= 'admin':
return 'Access denied'
return 'This is an admin - only page'
@app.route('/protected')
@login_required
def protected():
return 'This is a protected page'
if __name__ == '__main__':
db.create_all()
app.run()
在上述代码中,/admin - only
路由是一个仅管理员可访问的页面。在访问该路由时,首先获取当前用户 current_user
,检查其角色是否为 admin
,如果不是则返回 Access denied
,否则显示页面内容。
4.2 基于权限的授权
基于权限的授权比基于角色的授权更细粒度,每个操作都可以对应一个权限。
4.2.1 数据库模型设计
需要创建权限表和用户权限关联表:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
db = SQLAlchemy(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
class Permission(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True, nullable=False)
class UserPermission(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
permission_id = db.Column(db.Integer, db.ForeignKey('permission.id'))
db.create_all()
4.2.2 授权代码示例
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
app.secret_key = 'your_secret_key'
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
class Permission(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True, nullable=False)
class UserPermission(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
permission_id = db.Column(db.Integer, db.ForeignKey('permission.id'))
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username, password=password).first()
if user:
login_user(user)
return redirect(url_for('protected'))
else:
return 'Invalid credentials'
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
@app.route('/create - post', methods=['GET', 'POST'])
@login_required
def create_post():
has_permission = Permission.query.join(UserPermission).filter(
UserPermission.user_id == current_user.id,
Permission.name == 'create_post'
).first()
if not has_permission:
return 'Access denied'
# 处理创建文章的逻辑
return 'Create post page'
@app.route('/protected')
@login_required
def protected():
return 'This is a protected page'
if __name__ == '__main__':
db.create_all()
app.run()
在上述代码中,/create - post
路由检查当前用户是否具有 create_post
权限。通过查询 Permission
、UserPermission
表的关联来判断用户是否有此权限,如果没有则返回 Access denied
。
五、安全考虑
5.1 密码存储安全
在处理用户认证时,密码存储的安全性至关重要。绝不能以明文形式存储密码,应该使用密码哈希函数。在 Python 中,可以使用 bcrypt
库。
5.1.1 安装 bcrypt
pip install bcrypt
5.1.2 密码哈希存储示例
import bcrypt
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
db = SQLAlchemy(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(120), nullable=False)
def set_password(self, password):
self.password_hash = bcrypt.hashpw(password.encode('utf - 8'), bcrypt.gensalt())
def check_password(self, password):
return bcrypt.checkpw(password.encode('utf - 8'), self.password_hash)
db.create_all()
在上述代码中,User
类增加了 set_password
和 check_password
方法。set_password
方法使用 bcrypt.hashpw
对密码进行哈希处理并存储哈希值,check_password
方法使用 bcrypt.checkpw
验证密码。
5.2 防止 CSRF 攻击
跨站请求伪造(CSRF)是一种常见的 Web 攻击,攻击者通过伪装成用户的合法请求来执行恶意操作。Flask - WTF 扩展可以帮助防止 CSRF 攻击。
5.2.1 安装 Flask - WTF
pip install flask - wtf
5.2.2 使用 CSRF 保护示例
from flask import Flask, render_template, request, redirect, url_for
from flask_wtf.csrf import CSRFProtect
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///users.db'
app.secret_key = 'your_secret_key'
csrf = CSRFProtect(app)
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username, password=password).first()
if user:
login_user(user)
return redirect(url_for('protected'))
else:
return 'Invalid credentials'
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
@app.route('/protected')
@login_required
def protected():
return 'This is a protected page'
if __name__ == '__main__':
db.create_all()
app.run()
在上述代码中,CSRFProtect
实例 csrf
被初始化并关联到应用。在模板中,需要包含 CSRF 令牌,Flask - WTF 会在请求处理时验证令牌,防止 CSRF 攻击。
六、结合前端实现认证与授权
在实际应用中,通常需要结合前端来实现用户认证与授权的交互。以下以使用 HTML 和 JavaScript 与 Flask 后端交互为例。
6.1 前端登录表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<title>Login</title>
</head>
<body>
<form action="{{ url_for('login') }}" method="post">
<label for="username">Username:</label><br>
<input type="text" id="username" name="username"><br>
<label for="password">Password:</label><br>
<input type="password" id="password" name="password"><br><br>
<input type="submit" value="Login">
</form>
</body>
</html>
上述 HTML 代码创建了一个简单的登录表单,表单提交到 Flask 后端的 login
路由。
6.2 使用 AJAX 进行认证
如果是前后端分离的应用,可以使用 AJAX 来处理认证。以下是一个简单的示例,使用 fetch
API:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<title>Login</title>
</head>
<body>
<label for="username">Username:</label><br>
<input type="text" id="username"><br>
<label for="password">Password:</label><br>
<input type="password" id="password"><br><br>
<button onclick="login()">Login</button>
<script>
function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
fetch('/login', {
method: 'POST',
headers: {
'Content - Type': 'application/json'
},
body: JSON.stringify({
username: username,
password: password
})
})
.then(response => response.text())
.then(data => {
if (data === 'Invalid credentials') {
alert('Invalid credentials');
} else {
alert('Login successful');
}
});
}
</script>
</body>
</html>
在上述代码中,当点击登录按钮时,login
函数使用 fetch
发送一个 POST 请求到 /login
路由,携带用户名和密码。根据后端返回的响应进行相应的提示。
6.3 前端授权控制
在前端,可以根据用户的认证状态和授权信息来控制页面元素的显示。例如,只有管理员用户才能看到删除按钮:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<title>Admin Page</title>
</head>
<body>
{% if current_user.role == 'admin' %}
<button onclick="deleteUser()">Delete User</button>
{% endif %}
<script>
function deleteUser() {
// 处理删除用户的 AJAX 请求逻辑
}
</script>
</body>
</html>
在上述代码中,使用 Flask 的 Jinja2 模板语法,根据 current_user.role
判断是否为管理员,如果是则显示删除按钮。
通过以上内容,详细介绍了如何在 Flask 应用中实现用户认证与授权,涵盖了基本概念、多种认证与授权方式、安全考虑以及与前端的结合等方面,希望能帮助开发者构建安全可靠的 Web 应用。