今日学习笔记
基于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 x
四、用户登录
点击前端页面的登录按钮,会请求登录接口

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()
浙公网安备 33010602011771号