概述
使用Flask实现一个简单的用户鉴权模块,后端数据库使用Sqlite,在业务上主要包括用户管理、权限管理。用户角色主要分为管理员(admin) 普通用户(user) 访客(guest)(为求方便,不考虑注册功能实现,文末有源码下载链接)
业务分解
管理员(admin)
- 权限:访问所有接口
- 功能:
- 添加user,guest用户
- 删除user,guset用户
- 查询所有用户
普通用户(user)
- 权限:访问user权限接口
- 功能:
- 添加guest用户
- 删除guest用户
- 查询user,guest用户
访客(guest)
- 权限:访问访客权限接口
- 功能:无
业务流程图
业务实现
数据库设计
user表:(id,name,password , roles),用户表
role表:(id,name,permissions,users,角色表
permission表:(id,name,roles),权限表
user_role表:(id,user_id,role_id),用户角色关联表
role_permission表:(id,role_id,perssion_id)用户权限关联表
数据库模型如下:
数据表关系解析:
- User 和 Role 之间的关系:
- 一个 User 可以有多个 Role,一个 Role 也可以分配给多个 User。这种多对多关系通过 UserRole 表实现,这里为了方便,一个用户只对应一种角色
- User 表中没有直接存储 Role 信息,而是通过 UserRole 表关联。
- Role 和 Permission 之间的关系:
- 一个 Role 可以拥有多个 Permission,一个 Permission 也可以赋予多个 Role。这种多对多关系通过 roles_permissions 表实现。这里为了方便,一个role只对应一种permission
- Role 表中没有直接存储 Permission 信息,而是通过 roles_permissions 表关联。
代码实现
代码结构
创建基础配置(config.py)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
DATABASE_PATH = os.path.join(BASE_DIR, 'data', 'users.db')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DATABASE_PATH}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['JWT_SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY') or '123456789'
db = SQLAlchemy(app)
初始化一个Flask对象,配置数据库路径,和jwt认证配置
构建数据库文件(model文件夹)
model/role.py
from config import db
#'role_permission.role_id' could not find table 'roles' with which to generate a foreign key to target column 'id'
class Role(db.Model):
__tablename__ = 'role'
id = db.Column(db.Integer, primary_key=True,autoincrement=True)
name = db.Column(db.String(64), unique=True,nullable=False)
#permissions = db.relationship('permission', secondary='role_permission', back_populates='roles')
class Permission(db.Model):
__tablename__ = 'permission'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
class RolePermission(db.Model):
__tablename__ = 'role_permission'
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id',ondelete='CASCADE'),nullable=False)
permission_id = db.Column(db.Integer, db.ForeignKey('permission.id', ondelete='CASCADE'))
Role.permissions = db.relationship('Permission', secondary='role_permission', backref='roles')
构建Role,Permisson,RolePermission等数据模型类,构建数据表之间的关系
model/user.py
from config import db
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(32), unique=True, nullable=False)
password = db.Column(db.String(64))
#roles = db.relationship('Role', secondary='user_role',back_populates='users')
class UserRole(db.Model):
__tablename__ = 'user_role'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id',ondelete='CASCADE'), nullable=False)
role_id = db.Column(db.Integer, db.ForeignKey('role.id',ondelete='CASCADE'), nullable=False)
User.roles = db.relationship('Role', secondary='user_role', backref='users')
构建User,UserRole数据模型
构建中间件(middleware文件夹)
middleware/auth.py
# middleware/auth.py
from functools import wraps
from flask import jsonify
from flask_jwt_extended import get_jwt_identity,jwt_required
from model.user import User
def role_required(role_name):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
current_user = get_jwt_identity()
user = User.query.filter_by(name=current_user).first()
if not user or not any(role.name == role_name for role in user.roles):
return jsonify({"msg": "权限不足"}), 403
return func(*args, **kwargs)
return wrapper
return decorator
def permission_required(permission_name):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
current_user = get_jwt_identity()
user = User.query.filter_by(name=current_user).first()
if not user or not any(permission.name == permission_name for role in user.roles for permission in role.permissions):
return jsonify({"msg": "权限不足"}), 403
return func(*args, **kwargs)
return wrapper
return decorator
# Admin权限
def admin_permission_required(func):
@wraps(func)
@jwt_required()
def wrapper(*args, **kwargs):
current_user = get_jwt_identity()
user = User.query.filter_by(name=current_user).first()
if not user or not any(permission.name == "Admin" for role in user.roles for permission in role.permissions):
return jsonify({"msg": "权限不足"}), 403
return func(*args, **kwargs)
return wrapper
# User权限
def user_permission_required(func):
def wrapper(*args, **kwargs):
current_user = get_jwt_identity()
user = User.query.filter_by(name=current_user).first()
if not user or not any((permission.name == 'User' or permission.name == 'Admin') for role in user.roles for permission in role.permissions):
return jsonify({"msg": "权限不足"}), 403
return func(*args, **kwargs)
return wrapper
role_required验证角色,permission_required验证用户权限,admin_permission_required验证是否是管理员权限,user_permission_required验证是否是普通用户权限
构建路由(routers文件夹)
routers/auth.py
# views/auth.py
from flask import Blueprint, jsonify, request
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
from config import db
from model.user import User
from model.role import Role, Permission
from middleware.auth import role_required,permission_required
auth_bp = Blueprint('auth', __name__)
# 登录,获取access_token
@auth_bp.route('/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
user = User.query.filter_by(name=username).first()
if not user or user.password != password:
return jsonify({"msg": "Bad username or password"}), 401
access_token = create_access_token(identity=username)
return jsonify(access_token=access_token)
@auth_bp.route('/admin', methods=['GET'])
@jwt_required()
@role_required('Admin')
def admin_route():
return jsonify({"msg": "Welcome Admin!"})
@auth_bp.route('/user', methods=['GET'])
@jwt_required()
@role_required('User')
def user_route():
return jsonify({"msg": "Welcome User!"})
@auth_bp.route('/manage_users', methods=['GET'])
@jwt_required()
@permission_required('Admin')
def manage_users_route():
return jsonify({"msg": "Welcome User Manager!"})
login用户登录,产出access_token,用于后续操作的验证,在后续请求中,header中添加Authorization
:Bearer <access_token>
,后面几个路由用来测试。
routers/action.py
from flask import Blueprint, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from sqlalchemy.orm import aliased
from config import db
from model.user import User
from model.role import Role
from middleware.auth import admin_permission_required, user_permission_required
action_bp = Blueprint('action', __name__)
#添加用户
# admin权限可以添加user,guest
# user权限可以添加guest
# guest无添加权限
@action_bp.route('/add', methods=['POST'])
@jwt_required()
def add_user():
try:
cur_name = get_jwt_identity()
cur_user = User.query.filter_by(name=cur_name).first()
if not cur_user:
return jsonify({'code': 204, 'msg': '请先登录'})
cur_role = cur_user.roles[0].name
data = request.get_json()
username = data.get('username')
password = data.get('password')
role = data.get('role')
if cur_role == 'Admin':
if role in ('User','Guest'):
tmp_role = Role.query.filter_by(name=role).first()
user = User(name=username, password=password,roles=[tmp_role])
db.session.add(user)
db.session.commit()
return jsonify({'code': 201, 'msg': '添加成功','data':{}})
else:
return jsonify({'code': 204, 'msg': '用户角色超出权限','data':{}})
elif cur_role == 'User':
if role in ('Guest'):
tmp_role = Role.query.filter_by(name=role).first()
user = User(name=username, password=password,roles=[tmp_role])
db.session.add(user)
db.session.commit()
return jsonify({'code': 201, 'msg': '添加成功','data':{}})
else:
return jsonify({'code': 204, 'msg': '用户角色超出权限','data':{}})
else:
return jsonify({'code': 204, 'msg': '用户角色超出权限','data':{}})
except Exception as e:
return jsonify({'code': 204, 'msg': str(e)})
# 删除用户
# admin权限可以删除user,guest
# user权限可以删除guest
# guest无删除权限
@action_bp.route('/delete', methods=['POST'])
@jwt_required()
def delete_user():
try:
data = request.get_json()
username = data.get('username')
user = User.query.filter_by(name=username).first()
if user is None:
return jsonify({'code': 204, 'msg': '用户不存在'})
else:
user_role = user.roles[0].name
cur_name = get_jwt_identity()
if cur_name == username:
return jsonify({'code': 204, 'msg': '不能删除自己','data':{}})
cur_user = User.query.filter_by(name=cur_name).first()
if not cur_user:
return jsonify({'code': 204, 'msg': '请先登录','data':{}})
cur_role = cur_user.roles[0].name
if cur_role == 'Admin' and user_role in ('User','Guest'):
db.session.delete(user)
db.session.commit()
return jsonify({'code': 201, 'msg': '删除成功','data':{}})
elif cur_role == 'User' and user_role == 'Guest':
db.session.delete(user)
db.session.commit()
return jsonify({'code': 201, 'msg': '删除成功','data':{}})
else:
return jsonify({'code': 204, 'msg': '用户角色超出权限','data':{}})
except Exception as e:
return jsonify({'code': 204, 'msg': str(e)})
# 查询用户
# admin权限可以查询admin,user,guest
# user权限可以查询user,guest
@action_bp.route('/query', methods=['GET'])
@jwt_required()
def query_users():
try:
cur_name = get_jwt_identity()
cur_user = User.query.filter_by(name=cur_name).first()
if not cur_user:
return jsonify({'code': 204, 'msg': '请先登录'})
cur_role = cur_user.roles[0].name
if cur_role == 'Admin':
users = User.query.all()
elif cur_role == 'User':
users = User.query.join(User.roles).filter(Role.name != 'Admin').all()
#users = User.query.filter(User.roles[0].name != 'Admin').all()
else:
return jsonify({'code': 204, 'msg': '用户角色超出权限'})
res = []
for u in users:
tmp = {
'id':u.id,
'name':u.name,
'role':u.roles[0].name
}
res.append(tmp)
return jsonify({'code': 201, 'data': res,'msg': '查询成功'})
except Exception as e:
return jsonify({'code': 204, 'msg': str(e),'data':{}})
# admin模式
# 只有admin权限可以进入
@action_bp.route('/adminmode', methods=['POST'])
@admin_permission_required
def admin_mode():
return jsonify({'code': 201, 'msg': '只有admin权限可以访问'})
# user模式
# admin,user权限可以进入
@action_bp.route('/usermode', methods=['POST'])
@user_permission_required
def user_mode():
return jsonify({'code': 201, 'msg': '只有【admin,user】权限可以访问'})
# guest模式
# admin,user,guest权限可以进入
@action_bp.route('/guestmode', methods=['POST'])
def guest_mode():
return jsonify({'code': 201, 'msg': '所有用户都可以访问'})
用户操作,添加,删除,查询,已经不同角色的权限测试
数据库生成,服务端构建(app.py,main.py)
app.py
from flask_jwt_extended import JWTManager
from config import app,db
from model.user import User
from model.role import Role,Permission
from routers.auth import auth_bp
from routers.action import action_bp
#
def create_tables():
with app.app_context():
db.create_all()
# 添加角色和权限
if not Role.query.filter_by(name='Admin').first():
admin_role = Role(name='Admin')
admin_permission = Permission(name='Admin')
user_role = Role(name='User')
user_permission = Permission(name='User')
guest_role = Role(name='Guest')
guest_permission = Permission(name='Guest')
admin_role.permissions.extend([admin_permission])
user_role.permissions.extend([user_permission])
guest_role.permissions.extend([guest_permission])
#创建一个管理员
user = User(name='admin', password='admin', roles=[admin_role])
db.session.add(admin_role)
db.session.add(user_role)
db.session.add(guest_role)
db.session.add(admin_permission)
db.session.add(user_permission)
db.session.add(guest_permission)
db.session.add(user)
db.session.commit()
def init_app():
create_tables()
JWTManager(app)
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(action_bp, url_prefix='/action')
@app.route('/hello')
def hello():
return 'hello world'
app.run(host='0.0.0.0', port=5000)
- 创建数据库,并添加一个admin用户,本项目为只能创建了一个admin用户,如果需要多admin用户,请自行扩展
- Flask对象添加路由和JWT配置初始化
main.py
from app import init_app
if __name__ == '__main__':
init_app()
至此,项目完成了。我的系统是windows10,Python版本是3.11.7,项目工具版本如下
Flask == 3.0.3
Flask-JWT-Extended == 4.6.0
Flask-SQLAlchemy == 3.1.1
后记
一个简单的用户权限管理模块完成啦,由于本人主要从事后端开发,考虑到时间问题,所以前端没有实现,测试的话,你可以使用postman,或者自己实现前端,添加swagger等,如果测试中出现bug,请见谅。
源码下载