前言
原本使用的是session,认证,服务器会把session-id存在浏览器的cookie里面,然后在服务端会保存session-id,也看到很多flask关于session的操作,自定义保存到redis等等,但是在负载均衡的时候,就会发现,当服务器多起来之后,访问的时候其他的服务器并没有保存另一个服务器产生的session-id。当然保存到同一个数据库可以解决,但是我试了一下使用token来认证。
大概思路就是借鉴了这个视频的,可能有很多不符合规范,还请大家多多指出
apifox介绍token的使用
思路
一般token都是放在请求头的Authorization,但是我还是个萌新,项目是使用服务器渲染的,没有做前后端分离,好像没法设置请求头,所以我就观察了一下请求头,发现cookie是每次都会发过来的,所以我就将token存在cookie里面,每次发过来,进行验证,如果能够每次通过验证,就能够保持登录态。
用到的东西
- flask的钩子函数
- pyjwt模块
具体实现
token的生成验证
1. 生成token
这个传入的user参数是ORM对象,就是数据库的单个用户的类,具体是这样的:user = UserModel.query.filter(UserModel.id ==user_id).first()
设置好headers和payload之后,直接使用jwt.encode就可以了,就能得到一个加密的字符串了
def create_token(user):
headers = {
"alg": "HS256",
"typ": "JWT",
}
payload = {
"name": user.username,
"role": user.role,
"user_id": user.id,
"exp": int(time.time() + 3600),
"iss": 'cgy'
}
token = jwt.encode(payload=payload, key=current_app.config.get('SECRET_KEY'), algorithm='HS256',
headers=headers)
return token
2. 验证token
底下可以发现jwt.decode将token解密,可以得到上面写的payload,并且验证issuer。如果验证失败,返回msg,即变量msg不为None,之后也可以根据payload和msg是否为None,进行判断验证是否成功。
def validate_token(token):
payload = None
msg = None
try:
payload = jwt.decode(jwt=token, key=current_app.config.get('SECRET_KEY'), algorithms=['HS256'], issuer='cgy')
except exceptions.ExpiredSignatureError:
msg = 'token已失效'
except jwt.DecodeError:
msg = 'token认证失败'
except jwt.InvalidTokenError:
msg = '非法的token'
return payload, msg
用户认证流程
在每次接收请求的时候,提取出cookie中的token,然后在每次请求操作结束的时候,生成token,放到cookie中
接收请求时
从cookie中取出token,然后进行认证,如果
@app.before_request
def before_request():
payload, msg = validate_token(request.cookies.get('token'))
if payload is not None and msg is None:
user = UserModel.query.filter(UserModel.id == token.get('user_id')).first()
g.user = user
结束请求时
这个resp是一个响应对象,可以在视图函数中给他绑定一些属性,在这里能够识别到
- 首先是查看resp这个对象有没有logout这个属性,并且这个属性的值为True,如果是的话,就删除cookie
- 如果不是退出登录的话,就查看g对象或者resp对象,有没有user这个属性,如果有的话就生成token并且存到cookie中
- g对象:可以看到上面的接收请求的时候,会认证一次,如果能够通过的话,将user存到g对象中,所以这里查看g对象中是否有user这个属性是在检查,这是不是一个登录过的用户的发送请求
@app.after_request
def after_request(resp):
if hasattr(resp, 'logout') and resp.logout is True:
resp.delete_cookie('token')
else:
if hasattr(g, 'user'):
token = create_token(g.user)
resp.set_cookie('token', token, max_age=3600)
return resp
登录赋予登录态
我们会发现一个问题,在接受请求的时候,是先获取到了cookie再给g对象绑定user,这里结束请求的时候再通过在检查g对象中有没有user。我们好像没有在哪里设置g对象有user,从而设置cookie,那么这个所谓的第一次,就在login视图函数里面,可以看到,当使用post请求时,有g.user = user
,这个第一次,给g对象绑定user,从而结束请求时开始将token加入到cookie中
@bp.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
if hasattr(g, 'user'):
return redirect('/')
return render_template('login.html')
elif request.method == 'POST':
form = LoginForm(request.form)
if form.validate():
user = UserModel.query.filter(UserModel.username == form.username.data).first()
g.user = user
flash('登录成功')
return redirect(url_for('food.index'))
else:
flash('登录失败')
return render_template('login.html')
总结
笼统来看就是,
登录的时候,给g对象绑定user,然后在结束那一次请求的时候,会把token加入到cookie中。
在之后接收请求时,会获取cookie中的token并且认证一下,知道这是登录后的用户,再给g对象绑定user
在返回响应前,查看g对象是否有user属性,如果有的话,再更新cookie(因为有时效)