文章目录
需要会的东西
认证机制
我试着描述一下
常规的token认证
前端每次向后端发送请求,在请求头里面写入access token,后端进行认证,非必须登录的操作都可以正常进行,而必须登录的资源都会返回login required
缺点:因为access token权限高,会频繁使用,相对不安全。设置过期时间太长了,被盗取后容易出问题,但是设置过期时间太短了,用户就需要频繁登录,来获取到access token
access token + refresh token
access token的操作跟上述一致,但是明确了,access token的过期时间:短,但是没有很短,如果很短就需要一直刷新,refresh token就会频繁发送,这是不可以的。
时效短带来的不好的用户体验怎么来解决呢?这时候就需要refresh token来刷新access token了
实际操作
生成+验证
具体操作跟上一篇博客基本一致
这里直接上代码了
def create_token(user, refresh_token=False):
headers = {
"alg": "HS256",
"typ": "JWT",
}
exp = int(time.time() + 600) if refresh_token is False else int(time.time() + 3600 * 24 * 14)
payload = {
"name": user.username,
"user_id": user.user_id,
"exp": exp,
"iss": 'byszqq'
}
key = current_app.config.get('JWT_SECRET_KEY')
if refresh_token is True:
u = UserModel.query.filter(UserModel.user_id == user.user_id).first()
refresh_key = rand_str(6)
u.refresh_key = refresh_key
payload['refresh_key'] = refresh_key
db.session.commit()
token = jwt.encode(payload=payload, key=key, algorithm='HS256', headers=headers)
return token
def validate_token(token, refresh_token=False):
payload = None
msg = None
key = current_app.config.get('JWT_SECRET_KEY')
try:
payload = jwt.decode(jwt=token, key=key, algorithms=['HS256'], issuer='byszqq')
if refresh_token is True:
user = UserModel.query.filter(UserModel.user_id == payload.get('user_id')).first()
if user.refresh_key != payload.get('refresh_key'):
msg = 'refresh key 错误'
else:
if payload.get('refresh_key') is not None:
msg = '禁止用refresh token'
except exceptions.ExpiredSignatureError:
msg = 'token已失效'
except jwt.DecodeError:
msg = 'token认证失败'
except jwt.InvalidTokenError:
msg = '非法的token'
return payload, msg
在hook中的操作
在接受请求,进入视图函数前,对请求头中的Authorization中的access token进行认证,如果通过认证,给g对象绑定user对象,如果是时效,g.refresh = True。
在经过视图函数后,进行两个判断,如果是access token过期了,在Authorization中放入refresh字符串,告诉前端,需要访问刷新token的接口。如果g对象有user对象,在Authorization中放入新的access token,并且如果是刚登录的用户,再添加refresh token在请求头中。
注意一个东西,在before_request中的g.user = user
@app.before_request
def before_request():
token, msg = validate_token(request.headers.get('Authorization'))
if msg is None:
try:
user = UserModel.query.filter(UserModel.user_id == token.get('user_id')).first()
g.user = user
except Exception as e:
print(e)
elif msg == 'token已失效':
g.refresh = True
@app.after_request
def after_request(resp):
if hasattr(g, 'refresh') and g.refresh is True:
resp.headers['Authorization'] = 'refresh'
if hasattr(g, 'user'):
resp.headers['Authorization'] = create_token(g.user)
if hasattr(g, 'login') and g.login is True:
resp.headers['refresh-token'] = create_token(g.user, refresh_token=True)
return resp
login
其他都不太重要,重要的是我们看到g.user = cur_user
class LoginAPI(MethodView):
def post(self):
username = request.form.get('username')
password = request.form.get('password')
try:
cur_user = UserModel.query.filter(UserModel.username == username).first()
except SQLAlchemyError as e:
Log.error(e)
return jsonify({'code': 500, 'message': 'database error'})
else:
if cur_user is None:
return jsonify({'code': 404, 'message': "there's no such user"})
if check_password_hash(cur_user.password, password):
g.user = cur_user
g.login = True
return jsonify({'code': 200, 'message': 'success', 'data': obj_to_dict(g.user)})
else:
return jsonify({'code': 403, 'message': 'your password is wrong'})
login require
简单来说,就是验证g对象中有没有user这个属性,而根据上面的login操作,还有hook里面的操作,会发现,只有当1. 登录过后,有g.user 2. access token通过验证后,有g.user
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if hasattr(g, 'user'):
return func(*args, **kwargs)
return jsonify({'code': 403, 'message': "login required"})
return wrapper
refresh
这是一个接口,当前端受到的Authorization中是"refresh"是调用
将refresh_token传到后端,进行验证,通过了又是g.user = user
,返回去看一看hook的after_request的描述,如果g对象有user对象,在Authorization中放入新的access token,所以就会发现,在经过了refresh这个接口后,如果refresh token正确,就会有g.user = user
,然后就会有resp.headers['Authorization'] = create_token(g.user)
,这样就做到了根据refresh token 刷新access token的效果了
class RefreshAPI(MethodView):
def post(self):
refresh_token = request.form.get('refresh_token')
token, msg = validate_token(refresh_token, refresh_token=True)
if msg is None:
try:
user = UserModel.query.filter(UserModel.user_id == token.get('user_id')).first()
except SQLAlchemyError as e:
Log.error(e)
else:
g.user = user
return jsonify({'code': 200, 'message': 'success'})
else:
return jsonify({'code': 403, 'message': 'please login again'})
结语
算是敲了一年的代码了,有啥问题请多多指教