基于Python-Flask的权限管理4:基于Token的登录
一、前言
登录功能是每个系统的基础,本篇实现了基于token的用户登录和请求权限控制。
二、数据库model
models文件夹下新建user.py,创建ORM实体类
# !/usr/bin/python3 # -*- coding: utf-8 -*- """ @Author : Huguodong @Version : ------------------------------------ @File : user.py @Description : 用户表 @CreateTime : 2020/3/7 14:45 ------------------------------------ @ModifyTime : """ import hashlib from sqlalchemy import func from db import db from models.BaseModel import BaseModel class User(BaseModel): """ 用户表 """ __tablename__ = "t_user" id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment="用户ID") nickname = db.Column(db.String(30), comment="用户昵称") user_name = db.Column(db.String(30), comment="登录账号") user_type = db.Column(db.Boolean, default=1, comment="用户类型(1系统用户") email = db.Column(db.String(50), comment="用户邮箱") phone = db.Column(db.String(20), comment="手机号") phonenumber = db.Column(db.String(11), comment="手机号码") sex = db.Column(db.INTEGER, default=1, comment="用户性别(1男 2女 3未知)") avatar = db.Column(db.String(100), comment="头像路径") password = db.Column(db.String(50), comment="密码") salt = db.Column(db.String(20), comment="盐加密") status = db.Column(db.INTEGER, default=1, comment="帐号状态(1正常 2禁用") dept_id = db.Column(db.INTEGER, comment="部门id") del_flag = db.Column(db.INTEGER, default=1, comment="删除标志(1代表存在 2代表删除)") login_ip = db.Column(db.String(50), comment="最后登陆IP") login_date = db.Column(db.TIMESTAMP, comment="最后登陆时间", nullable=False, onupdate=func.now()) def check_password(self, passwd): ''' 检查密码 :param passwd: :return: 0/1 ''' # 创建md5对象 m = hashlib.md5() b = passwd.encode(encoding='utf-8') m.update(b) str_md5 = m.hexdigest() if self.password == str_md5: return 1 else: return 0
二、创建蓝图
1.permission文件夹下新建蓝图文件user.py
from permission import * user = Blueprint('user', __name__)
2.app.py注册蓝图
app.register_blueprint(user.user, url_prefix='/api/user')
3.写一个测试方法
@user.route('/test', methods=["GET"]) def test(): return SUCCESS()
4.浏览器输入http://127.0.0.1:5000/api/user/test
二、实现token方法
用token校验身份,是前后端交互的常用方式。
它有以下特性:
- 会失效
- 加密
- 可以根据它拿到用户的信息
关于token的方法写在utils下的common.py下
1.生成token:生成方式( 内部配置的私钥+有效期+用户的id +用户名+用户角色列表)
def create_token(user_id, user_name, role_list): ''' 生成token :return: token ''' # 第一个参数是内部的私钥,这里写在共用的配置信息里了,如果只是测试可以写死 # 第二个参数是有效期(秒) s = Serializer(config.SECRET_KEY, expires_in=config.EXPIRES_IN) # 接收用户id转换与编码 token = None try: token = s.dumps({"id": user_id, "name": user_name, "role": role_list}).decode("ascii") except Exception as e: app.logger.error("获取token失败:{}".format(e)) return token
2.校验token:校验接收到的token,如果成功返回用户信息,否则返回None
def verify_token(token): ''' 校验token :param token: :return: 用户信息 or None ''' # 参数为私有秘钥,跟上面方法的秘钥保持一致 s = Serializer(config.SECRET_KEY) try: # 转换为字典 data = s.loads(token) return data except Exception as e: return None
3.有很多接口是必须登录才能操作的,最好的方式就是在写一个装饰器,添加在需要的api上
def login_required(*role): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): try: # 在请求头上拿到token token = request.headers["Authorization"] except Exception as e: # 没接收的到token,给前端抛出错误 return jsonify(code=Code.NO_PARAMETER.value, msg='缺少参数token') s = Serializer(config.SECRET_KEY) try: user = s.loads(token) if role: # 获取token中的权限列表如果在参数列表中则表示有权限,否则就表示没有权限 user_role = user['role'] result = [x for x in user_role if x in list(role)] if not result: return jsonify(code=Code.ERR_PERMISSOM.value, msg="权限不够") except Exception as e: return jsonify(code=Code.LOGIN_TIMEOUT.value, msg="登录已过期") return func(*args, **kw) return wrapper return decorator
这样只需在方法上添加装饰器就能限制用户访问
#限制只有admin可以操作 @user.route('/xxx', methods=["POST"]) @login_required(‘admin’) def xxx() return xx
二、用户登录
点击前端页面的登录按钮,会请求登录接口
persmisson文件夹下的user.py新建登录方法
@user.route('/login', methods=["POST"]) def login(): ''' 用户登录 :return:token ''' res_dir = request.get_json() if res_dir is None: return NO_PARAMETER() # 获取前端传过来的参数 username = res_dir.get("username") password = res_dir.get("password") # 校验参数 if not all([username, password]): return jsonify(code=Code.NOT_NULL.value, msg="用户名和密码不能为空") try: user = User.query.filter_by(user_name=username).first() except Exception as e: app.logger.error("login error:{}".format(e)) return jsonify(code=Code.REQUEST_ERROR.value, msg="获取信息失败") if user is None or not user.check_password(password) or user.del_flag == 2 or user.status==2 : return jsonify(code=Code.ERR_PWD.value, msg="用户名或密码错误") # 获取用户信息,传入生成token的方法,并接收返回的token # 获取用户角色 user_role = Role.query.join(User_Role, Role.id == User_Role.role_id).join(User, User_Role.user_id == user.id).filter( User.id == user.id).all() role_list = [i.role_key for i in user_role] token = create_token(user.id, user.user_name, role_list) data = {'token': token, 'userId': user.id, 'userName': user.user_name, 'nickname': user.nickname} # 记录登录ip将token存入rerdis try: user.login_ip = request.remote_addr user.update() Redis.write(f"token_{user.user_name}", token) except Exception as e: return jsonify(code=Code.UPDATE_DB_ERROR.value, msg="登录失败") if token: # 把token返回给前端 return jsonify(code=Code.SUCCESS.value, msg="登录成功", data=data) else: return jsonify(code=Code.REQUEST_ERROR.value, msg="请求失败", data=token)
二、用户注销
persmisson文件夹下的user.py新建注销方法
@user.route('/logout', methods=["POST"]) @login_required() def logout(): ''' 注销方法:redis删除token :return: ''' try: token = request.headers["Authorization"] user = verify_token(token) if user: key = f"token_{user.get('name')}" redis_token = Redis.read(key) if redis_token: Redis.delete(key) return SUCCESS() else: return AUTH_ERR() except Exception as e: app.logger.error(f"注销失败") return REQUEST_ERROR()
三、检查Token
登录成功后,系统会请求一个check_token方法,主要是检查用户token是否合法
persmisson文件夹下的user.py新建check_token方法
@user.route('/check_token', methods=["POST"]) def check_token(): # 在请求头上拿到token token = request.headers["Authorization"] user = verify_token(token) if user: key = f"token_{user.get('name')}" redis_token = Redis.read(key) if redis_token == token: return SUCCESS(data=user.get('id')) else: return OTHER_LOGIN() else: return AUTH_ERR()
本文来自博客园,作者:HuTiger,转载请注明原文链接:https://www.cnblogs.com/huguodong/p/12589287.html,欢迎加入qq群927460240学习讨论