我们在开发网站时会采用session
和cookie
的方式来处理登录权限问题, 而在移动应用中要验证用户身份采用登录时给用户生成一个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文档