clean code

i create stuff

使用Flask实现用户认证API

| Comments

我们在开发网站时会采用sessioncookie的方式来处理登录权限问题, 而在移动应用中要验证用户身份采用登录时给用户生成一个token(令牌)的方式. 每次用户发出需要身份认证的请求时, 就需要验证一次token是否有效, 无效的情况包括token无法被解析等. 另一个问题是如果token被泄露, 用户的安全将受到威胁, 所以应当对这个token设置一个过期时间, 超过这个时间后应当重新登录, 这样可以将用户信息泄露的风险降低.

生成和使用token

有个很棒的Python第三方库叫itsdangerous, 包含许多常见安全问题的解决方案, 比如文件名等等.

生成token

TimedJSONWebSignatureSerializer能将包含用户id的字典, 如{'user_id': 1}设置一个具有过期时间的数字证书(Signature), 需要注意的是, 设置的secret key一定要足够安全, 在flask应用中, 我们采用flask配置中的secret key

1
2
3
4
5
6
7
8
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from itsdangerous import SignatureExpired, BadSignature
from config import config


def gen_token(user, expiration=1440*31*60):  # 单位为秒, 设定31天过期
    s = Serializer(config.SECRET_KEY, expires_in=expiration)
    return s.dumps({'id': user.id})  # user为model中封装过的对象

验证token合法性以及是否过期

装饰器(decorator)Python一个很有用的语法糖, 可以有效地减少重复代码. 在每个需要验证token的场景都用装饰器包裹一层, 就能验证无效token和过期token了~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from functools import wraps


def token_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        token = request.form['token']
        s = Serializer(config.SECRET_KEY)
        try:
            data = s.loads(token)
        except SignatureExpired:
            return jsonify({'status': 'fail', 'data': {'msg': 'expired token'}})
        except BadSignature:
            return jsonify({'status': 'fail', 'data': {'msg': 'useless token'}})
        kwargs['user_id'] = data['id']
        return func(*args, **kwargs)
    return wrapper

例如实现一个关注用户的操作, 在视图函数中这样调用装饰器token_required

1
2
3
4
5
6
7
8
9
10
11
12
13
@user.route('/follow', methods=['POST'])
@token_required
def follow_user(user_id):
    user_to_follow_id = request.form['user_id']
    user_rel = UserRel(user_id, user_to_follow_id)
    model = Model()
    if model.get_user_rel(user_id, user_to_follow_id):
        return to_json('already follow')
    user_to_follow = model.session.query(UserInfo).filter_by(user_id=user_to_follow_id).first()
    data = user_to_follow.data()
    model.session.add(user_rel)
    model.session.commit()
    return to_json(data, success=True)  # 将flask中的jsonify封装了一层

可是这样每个视图函数中需要一个user_id的参数, 这样还是存在重复, 不知是不是我装饰器用得不对:(

参考

[1]使用Flask设计带认证token的RESTful API接口
[2]itsdangerous文档

Comments