8. 为Lamb编写用户登录接口(带token)

前言

上篇我们给Lamb引入了flask_sqlalchemy,接着我们就编写我们第一个接口---登录。

知识准备

  • cookie和session

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

  • token

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

Python中的JWT

Python好像自带了jwt,我不是很确定。大家可以测试一下import jwt是否报错。

先给出我在Lamb/server/middleware/jwt.py中的实现

from datetime import timedelta, datetime

import jwt
from jwt.exceptions import ExpiredSignatureError


class UserToken(object):
    key = 'LambToken'
    expired_time = 3

    @staticmethod
    def get_token(data):
        new_data = dict({"exp": datetime.utcnow() + timedelta(hours=UserToken.expired_time)}, **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已过期, 请重新登录")

首先介绍下吧,我封装了一个UserToken类:

  • key

    这个字段是一个保密的东西,我这里给大家暴露无遗。这个应该是在你数据的前后加上这个key再解码成base64,所以可以看到decode的时候也需要这个key。

  • expired_time

    这是token的过期时间,我这里设置的是3个小时。

  • get_token

    这里的data参数是我要存储的数据,其实就是user表的内容。

  • parse_token

    其实比较简单,但是需要注意的是,我这里只捕获了一种异常,可能还有token解析失败的异常,这里只是初版。为了适配我们的登录接口。

流程

我们这里设定的逻辑,可能会有缺陷。大概如下:

  1. 用户登录,服务器校验用户名密码是否正确;

  2. 如果不正确则返回用户名或密码错误的提示消息,如果正确,则将用户表里用户的数据通过token存储并返回给客户端,客户端随后只需要操作token,服务端解析token就知道是哪个用户操作的了。


开始

  • 首先我们要编写views层的内容Lamb/server/app/views/auth/User.py

    from flask import Blueprint
    from flask import jsonify
    from flask import request
    
    from ..auth.UserSchema import UserSchema
    from ...controllers.auth.user import UserUtil
    from ...utils.decorator import schema, token_expired
    
    user = Blueprint("user", __name__, url_prefix="/user")
    
    
    @user.route("/login", methods=("POST",))
    #@schema(UserSchema.login_schema) 此处涉及到请求参数校验跳过
    def login():
        data = request.get_json()
        user_info, token = UserUtil.login(data.get("username"), data.get("password"))
        if user_info is None:
            return jsonify(dict(code=500, msg="用户名或密码错误"))
        return jsonify(dict(code=200, msg="登录成功", token=token, **user_info))
    
    

    代码十分简单,定义了一个user蓝图,在其router下定义了一个/login接口,允许的请求方式为POST(这里是可以多选的)。随后获取请求json,我这里data.get('username')之所以没做判断,是因为上面的schema装饰器帮忙做了校验=。=,今天有点晚了,就先不细讲了。可以看到我调用了controllers/auth/user下的UserUtil类,完成登录操作。如果取出来的user_info为None,就返回用户名或密码错误。否则就返回token以及用户信息。

  • 完善login方法

    from .. import User
    from .. import Team
    from ...middleware.jwt import UserToken
    
    
    class UserUtil(object):
    
        @staticmethod
        def login(user, pwd):
            user_info, token = None, None
            man = User.query.filter_by(username=user, password=pwd).first()
            if man is not None:
                # 生成token
                user_info = UserUtil.get_dict(man)
                team_info = Team.query.filter_by(id=man.team_id).first               if team_info is None:
                    # 没有team归属 列为默认权限
                    user_info["permission"] = "user"
                return user_info, UserToken.get_token(user_info)
            return user_info, token
    
        @staticmethod
        def get_dict(user_obj):
            return {c.name: getattr(user_obj, c.name) for c in user_obj.__table__.columns if     
    c.name != 'password'}
    
    

    login为登录方法,可以看到这里先默认user_info和token为None,接着通过User.query.filter_by去查询username=user且password=pwd的第一条数据。这个方法如果没有查询到,会返回None。

接着判断man是不是None,如果不是None说明用户存在,接着去查询他在团队的权限,如果查询到他不属于任何团队,则给他一个默认权限。我这里的初衷是,权限以团队为单位。比如管理员团队,分配什么权限,当然我这里权限表还没设计好。

get_dict是将User类转为dict对象。

对自己的登录接口做接口测试

首先看自己的数据库的数据。

image.png

可以看到我的账号是wuranxu,密码是123。

启动服务以后,我们利用postman调用一下自己的接口。

正确的用户名密码

image.png

然后是错误的用户名密码

image.png

然后是字段不传

image.png

接着是字段类型不正确

image.png

总结

其实上述例子还是有挺多问题的,后续值得我们去优化。

  • http返回json code有很多种,我这里虽然作了规定。比如102、101、200、500等。但是这些很显然应该保存为常量,或者说以特定的错误形式返回出去。比如101是参数丢失,102是参数类型不正确。

  • 给用户的默认权限是user

    这里应该也是一套权限列表,user也应该以常量的形式存储起来。

  • 其实虽然每次登录token都会刷新,但是之前的那个token只要没有过期就仍然可用。

  • 日志和异常处理

    对于错误的记录/处理几乎没有,这个很容易造成服务器崩溃。

这些都是值得优化的地方,我个人经验也比较有限。所以还是需要努力学习啊!*^*更新一次好累啊,晚安!!!

上一篇: 7. 给Lamb配置flask-sqlalchemy
下一篇: 暂无
github地址: https://github.com/wuranxu/Lamb

你可能感兴趣的:(8. 为Lamb编写用户登录接口(带token))