Flask设计带token认证的API

Flask设计带token认证的API

  • RESTful风格的API

REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了。
REST的一个特性是无状态的,没有session cookies,如果访问需要验证的接口,客户端请求必需每次都发送用户名和密码。通常在实际app应用中,并不会每次都将用户名和密码发送。所以我们使用token作为认证用户的身份信息。

  • 为什么使用Token验证:

在Web领域基于Token的身份验证随处可见。在大多数使用Web API的互联网公司中,tokens 是多用户下处理认证的最佳方式。
以下几点特性会让你在程序中使用基于Token的身份验证

  1. 无状态、可扩展
  2. 支持移动设备
  3. 跨程序调用
  4. 安全

创建用户数据库

使用Flask-SQLAlchemy (ORM)的模块去管理用户数据库。

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key = True)
    username = db.Column(db.String(32), index = True)
    password_hash = db.Column(db.String(128))

因为安全的原因,明文密码不可以直接存储,必需经过hash后方可存入数据库。如果数据库被脱了,也是比较难破解的。密码永远不要明文存在数据库中。

Password Hashing

在User类里面使用PassLib库对密码进行hash,添加加密跟验证加密后的密码方法。

from passlib.apps import custom_app_context as pwd_context

class User(db.Model):
    # ...

    def hash_password(self, password):
        self.password_hash = pwd_context.encrypt(password)

    def verify_password(self, password):
        return pwd_context.verify(password, self.password_hash)

hash算法是单向的,意味着它只能hash密码,但是无法还原密码。

用户注册

客户端发送表单信息,带用户名跟密码,验证用户名跟密码是否合法,是否已被注册。

@app.route('/users/sign_up', methods = ['POST'])
def new_user():
    username = request.json.get('username')
    password = request.json.get('password')
    if username is None or password is None:
        abort(400) # missing arguments
    if User.query.filter_by(username = username).first() is not None:
        abort(400) # existing user
    user = User(username = username)
    user.hash_password(password)
    db.session.add(user)
    db.session.commit()
    return jsonify({ 'username': user.username }), 201, {'Location': url_for('get_user', id = user.id, _external = True)}

用户登录

用户登录,验证用户名以及密码。

@user_login.route('/login', methods=['GET', 'POST'])
def sign():
    error = None
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        if verify_password(username, password):
            # 号码以及密码验证通过
            pass
        else:
            # 手机号或者密码错误
            return jsonify({'status_code':'401','error_message':'Unauthorized'})
        token = g.user.generate_auth_token(6000)
        status_code = "201"
        user_data = {
            'status_code': status_code,
            'token': token,
            'duration': 6000,
            "user": {
                "id": g.user.id,
                "phone": g.user.phone,
                "nickname": g.user.nickname,
                "avar": '/static/images/user_img/test_user_1.png',
                "message": '这个人很懒什么都没留下',
                "orderList": []
            }
        }
        json_user_data = jsonify(user_data)
        return json_user_data
    else:
        return jsonify({'status_code': '400', 'error_message': 'INVALID REQUEST'})

在这里我们使用flask的上下文请求对象g。处理请求时用作临时存储的对象,每次请求都会重设这个变量。

@auth.verify_password
def verify_password(username_or_token, password):
    # first try to authenticate by token
    user = User.verify_auth_token(username_or_token)
    if not user:
        # try to authenticate with username/password
        user = User.query.filter_by(phone=username_or_token).first()
        if not user or not user.verify_password(password):
            return False
    g.user = user
    return True

基于Token的认证

因为需要每次请求都要发送用户名和密码,客户端需要把验证信息存储起来进行发送,这样十分不方便,就算在HTTPS下的传输,也是有风险存在的。

比前面的密码验证方法更好的是使用Token认证请求。

扫描二维码关注公众号,回复: 1860520 查看本文章

原理是第一次客户端与服务器交换过认证信息后得到一个认证token,后面的请求就使用这个token进行请求。

Token通常会给一个过期的时间,当超过这个时间后,就会变成无效,需要产生一个新的token。这样就算token泄漏了,危害也只是在有效的时间内。
生成token和验证token的方法可以附加到User model上实现:

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

class User(db.Model):
    # ...

    def generate_auth_token(self, expiration = 600):
        s = Serializer(app.config['SECRET_KEY'], expires_in = expiration)
        return s.dumps({ 'id': self.id })

    @staticmethod
    def verify_auth_token(token):
        s = Serializer(app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None # valid token, but expired
        except BadSignature:
            return None # invalid token
        user = User.query.get(data['id'])
        return user

在generate_auth_token()函数中,token其实就是一个加密过的字典,里面包含了用户的id和默认为10分钟(600秒)的过期时间。

verify_auth_token()的实现是一个静态方法,因为token只是一次解码检索里面的用户id。获取用户id后就可以在数据库中取得用户资料了。

试试使用一个新的接入点,让客户端请求一个token:

@auth.login_required
def get_auth_token():
    token = g.user.generate_auth_token()
    return jsonify({ 'token': token.decode('ascii') })

给客户端返回一个json,客户端选择在header里面给服务端发送token,服务端获取header的token,使用verify_password方法验证。

token = request.headers['accesstoken']
user = User.verify_auth_token(token)
if not user:
    return jsonify({'status_code': '401', 'error_message': 'Unauthorized'})

一个简单的例子的github

如果想了解flask项目的结构的话参考这篇

猜你喜欢

转载自blog.csdn.net/t6_17/article/details/80888399