强制登录实现思路分析
1.强制登录需求
在开发中, 部分接口是要求用户登录才能访问的, 所以有强制登录的需求. 当用户登录的情况下, 可以正常访问特定的接口, 但当用户没有登录的情况下, 访问特定的接口, 则要求用户进行登录.
2.强制登录思路
1.定义装饰器脚本, 脚本内定义login_required装饰器
2.传入装饰器中的函数就是请求要访问的视图, 所以在视图执行前, 进行判断, 如果用户登录则放行, 执行视图; 如果用户未登录则返回401状态码与'Invalid token'信息
3.如何判断用户是否登录呢? 之前的请求钩子已经将用户的id放进了g对象中, 只需要判断g对象中是否存在user_id即可.
强制登录装饰器的实现
# 4.代码实现
from flask import g
from functools import wraps
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if g.user_id is not None:
return func(*args, **kwargs)
else:
return {'message': 'Invalid token'}, 401
return wrapper
# 可以使用测试蓝图来测试装饰器
更新Token
1.Token失效如何处理
# Token的失效
我们给Token设置了过期时间, 那过期时间多久合适呢? 同学们可能想到一种情况"15天免登录", 是不是我们给Token设置15天的有效期就行了呢?
当然不是, 给Token设置15天的有效期会使得Token暴露时间过长, 降低安全性. 所以我们约定只给Token两个小时的有效时间.但问题又来了. 两个小时后Token失效了咋办呢? 从新登录? 如果你是用户, 可能在这种情况下会感觉体验非常的差.
所以需要解决的事情就是: 刷新token(无感知)
2.无感知更新Token思路
# 更新Token思路
1.在给用户返回token时, 我们给用户设置了两小时有效期, 同时我们可以给用户返回一个额外的refresh_token, 这个token设置的有效期根据业务需求而定.
2.用户携带正常的token请求接口, 当token失效后, 变返回401状态码, 告诉前端用户token失效了, 前端携带者refresh_token请求后端的put接口进行Token的刷新, 生成新的token返回给用户
3.用户接收到后, 再次请求接口使用新生成的token, 这一些列的操作需要前端和后端配合完成, 做到用户无感知的状态下已经变更了原有的token值.
更新Token接口实现(代码)
# 接口实现
# (1).改写_generate_token方法
def _generate_token(self, user_id, refresh=True):
"""
生成token
:param user_id:
:return:
"""
# 获取盐
secret = current_app.config.get('JWT_SECRET')
# 定义过期时间
expiry = datetime.utcnow() + timedelta(hours=2)
# 生成Token
token = 'Bearer ' + generate_jwt({'user_id': user_id}, expiry, secret)
if refresh:
expiry = datetime.utcnow() + timedelta(days=15)
# is_refresh作为更新token的信号
refresh_token = 'Bearer' + generate_jwt({'user_id': user_id, 'is_refresh': True}, expiry, secret)
else:
refresh_token = None
return token, refresh_token
# (2).修改用户认证中的token生成代码与返回值代码
token, refresh_token = self._generate_token(user_id)
return {'message': 'OK', 'data':{'token': token, 'refresh_token': refresh_toke}}
# (3).改写验签代码: 将更新token信号放在g对象中
def jwt_authentication():
g.user_id = None
g.is_refresh = False
# 获取请求头中的token
token = request.headers.get('Authorization')
print(token)
if token is not None and token.startswith('Bearer '):
token = token[7:]
# 验证token
payload = verify_jwt(token)
print('payload', payload)
if payload is not None:
# 保存到g对象中
g.user_id = payload.get('user_id')
g.is_refresh = payload.get('is_refresh', False)
(4).实现put刷新接口
def put(self):
"""
刷新token
:return:
"""
if g.user_id is not None and g.is_refresh is True:
token, refresh_token = self._generate_token(g.user_id, refresh=False)
return {'message': 'ok', 'data': {'token': token}}
else:
return {'message': 'Invalid refresh token'}, 403
JWT禁用问题
1.需求分析
用户登录后, Token两小时有效, 但在登录后的10分钟内, 用户修改了密码.这个Token依然可以实现请求. 怎么办呢?
2.应用场景
(1).用户修改密码, 需要颁发新的Token, 禁用原有Token
(2).后台封禁用户
3.问题解决方案
1.在redis中使用set类型保存新生成的token值, 格式为: "user:user_id:token".
2.客户端使用token进行请求时, 如果token验证通过, 则从Redis中查找该用户的token值是否存在
3.第一种情况: Redis中该用户的token值不存在, 则直接放行, 处理业务逻辑
4.第二种情况: Redis中该用户的token存在, 但与当前请求使用的token一致, 则说明用户使用的是新的token, 同样可以放行, 处理业务.
5.第三种情况: Redis中存在, 但当前用户请求携带的token与Redis中的token不一致, 则返回403状态码, 不再处理业务逻辑.
4.代码实现
# 4.代码实现
# 在Redis中使用set类型保存新token
key = 'user:{}:token'.format(user_id)
pl = redis_client.pipeline()
pl.sadd(key, new_token)
pl.expire(key, token有效期)
pl.execute()
# token检验操作
key = 'user:{}:token'.format(user_id)
valid_tokens = redis_client.smembers(key, token)
if valid_tokens and token not in valid_tokens:
return {'message': 'Invalid token'.}, 403