zoukankan      html  css  js  c++  java
  • 测试平台系列(7) 改造注册接口

    引入jwt改造注册接口

    回顾

    还记得我们之前编写了一个register接口吗?可那只是个空壳,直接返回了注册成功的响应体。

    这不禁让我想起以前在某快递网站上,点击催单按钮,对方直接弹出催单成功的alert提示。但我打开了他的html代码,发现只要点击这个按钮就会弹出提示,没有任何后端的交互,这里我就不点名这个韵达快递了哈。只能说真的很秀~

    知识准备

    • cookie和session

      传统的web软件,需要存储用户的登录信息。cookie和session是比较常见的模式,当然也是比较古老的模式。其中cookie主要依赖于客户端,可以简单的说是浏览器。可以说用户的登录信息,存储在浏览器的localstorage中。而session,顾名思义,它是将用户存储于服务器中。更好的解释请自行百度。。

    • token

      token从广义说的话,是令牌的意思。我这里是json web token(JWT)的简写。简单的说,就是一种通过存储用户信息(以base64编码存储)然后需要的时候将之解析的方法。这样就达到你这个用户所有信息都被存储为一个字符串而且也看不到具体内容的目的。感兴趣的同学可以去看看~~

    Python中的JWT

    Python自带了jwt,现在咱们来熟悉一下相关的方法。

    先给出我在pity/middleware/Jwt.py中的实现

    import hashlib
    from datetime import timedelta, datetime

    import jwt
    from jwt.exceptions import ExpiredSignatureError

    EXPIRED_HOUR = 3


    class UserToken(object):
        key = 'pityToken'
        salt = 'pity'

        @staticmethod
        def get_token(data):
            new_data = dict({"exp": datetime.utcnow() + timedelta(hours=EXPIRED_HOUR)}, **data)
            return jwt.encode(new_data, key=UserToken.key).decode()

        @staticmethod
        def parse_token(token):
            try:
                return jwt.decode(token, key=UserToken.key)
            except ExpiredSignatureError:
                raise Exception("token已过期, 请重新登录")

        @staticmethod
        def add_salt(password):
            m = hashlib.md5()
            m.update(password + UserToken.salt)
            return m.hexdigest()

    UserToken类有3个方法,第一个方法呢,就是把用户信息压缩成一串字符串,并附带3小时的过期时间。

    第二个就是解析token为之前的用户信息。

    第二个方法add_salt是为了能够用md5码去存储用户密码,一旦有邪恶势力拿到数据库密码的话,不至于会暴露用户的密码,add_salt就是加盐的意思,如果你要反破解md5码,嘿嘿,我这里给你加了一层你想象不到的东西,所以相对来说还是比较安全的呢。

    编写核心方法

    仔细想一下,我们的用户如果需要注册的话,第一步是先判断这个账号是否已经注册过了。好,那我们来编写第一个方法: register_user

    我们新建dao/auth/UserDao.py

    from sqlalchemy import or_

    from app.middleware.Jwt import UserToken
    from app.models import db
    from app.models.user import User
    from app.utils.logger import Log


    class UserDao(object):
        log = Log("UserDao")

        @staticmethod
        def register_user(username, name, password, email):
            """

            :param username: 用户名
            :param name: 姓名
            :param password: 密码
            :param email: 邮箱
            :return:
            """

            try:
                users = User.query.filter(or_(User.username == username, User.email == email)).all()
                if users is not None:
                    raise Exception("用户名或邮箱已存在")
                # 注册的时候给密码加盐
                pwd = UserToken.add_salt(password)
                user = User(username, name, pwd, email)
                db.session.add(user)
                db.session.commit()
            except Exception as e:
                UserDao.log.error(f"用户注册失败: {str(e)}")
                return str(e)
            return None

    注意,User.query.filter这行代码的意思是,找出所有username或email已经存在的用户,如果有,则抛出异常,没有则直接通过orm插入这行数据。

    在orm的世界里,我们实例化的user对象就是数据表中的一行数据,这样理解会否更清晰一点呢?

    改造注册接口

    可以看到这个方法很空洞,我们需要做什么呢。

    首先我们是不是得接收传入的用户名/密码/邮箱等注册信息,对参数进行一些关键校验,然后调用刚才的核心方法。如果不出错的话,咱们的登录方法就写好了~

    校验参数

    Flask通过 request.get_json() 可以直接获取到传入的json数据,并返回一个dict对象。

    PS: 这么写比较丑陋,暂时先忍忍,后续我们再进行改造。

    引入核心方法

    只是导入了UserDao模块并调用了register方法,如果err不为空,就返回code=110, 意思是报警了(报错了)

    说明一下,code=101是参数错误,110是异常错误, 0是正常返回。

    注意,这不是什么官方的规定,是我一时兴起哈哈,以后都会按照这个约定来。

    完整代码:

    简单地测试一下

    启动服务

    可以看到,出现了循环引用的问题,原来我们在app/init.py里面引入了controller,在controller里面又引入了model,在model里面又引入了app/init.py里面的pity对象,所以出现了循环引用。

    解决的方法很简单:我们把注册蓝图这种脏活累活都放到run.py,他是最外层,没有人引入run.py的东西。

    改动后的app/init.py:

    from flask import Flask

    from config import Config

    pity = Flask(__name__)


    pity.config.from_object(Config)

    run.py:

    from datetime import datetime

    from app import pity
    from app.utils.logger import Log
    from app.controllers.auth.user import auth

    # 注册蓝图
    pity.register_blueprint(auth)


    @pity.route('/')
    def hello_world():
        log = Log("hello world")
        log.info("有人访问了你的网站了")
        now = datetime.now().strftime("%Y-%M-%d %H:%M:%S")
        print(now)
        return now


    if __name__ == "__main__":
        pity.run("0.0.0.0", threaded=True, port="7777")

    打开postman测试一下:

    不对劲的事情又发生了,好气呀!但是咱们不慌,仔细看一下这个报错,说的是405http方法不允许,找到我们写的接口:

    原来是这里出了问题,我们稍稍改动一下,将请求方式改成post即可。

    重启服务try again

    奇怪了,明明没有注册过这个用户,却提示了这个,首先我们检查一下数据库:

    发现数据库也没问题,那么我们使出大招,断点疗法:

    此处点一下会出现一个红色小圆形,再次请求:

    原来如此,我们的判断除了问题,返回的是个空数组,当然不是None了,所以咱们需要修改下校验方式:

    重启后继续尝试:

    这个我看出来了,这一定是md5那块出了问题,仔细找下原因吧。

    (太特么自信了,导致前面的图懒得改,只能边写边修改了,以后就不会这样直播写了,写好了直接上代码即可。)

    看报错的意思是,unicode必须hash成bytes,多大点事: 直接利用api:

    重启后继续尝试:

    真不容易,咱们检查下数据库:

    有了,注意密码也不是明文哦
    有了,注意密码也不是明文哦

    检查下重复注册问题(再次请求一次)

    时间不早了,今天的内容就到这了,打烊了打烊了,疯狂写bug!!!

    后端代码地址: https://github.com/wuranxu/pity

    前端代码地址: https://github.com/wuranxu/pityWeb

    觉得有用的话可以帮忙点个Star哦QAQ

  • 相关阅读:
    使用StreamHttpResponse和FileResponse下载文件的注意事项及文件私有化
    Django中@login_required用法简介
    Django之template
    单链表反转的原理和python代码实现
    两个队列实现栈,两个栈实现队列
    Linux--5 mariadb和redis的安装
    Linux--4
    Linux--3
    Linux--2 Linux之文档与目录结构、shell基本命令
    Linux--1 初识
  • 原文地址:https://www.cnblogs.com/we8fans/p/14502954.html
Copyright © 2011-2022 走看看