python-flask 技能点使用-02 请求钩子实现登录验证
- 场景分析
使用python flask开发web系统,该系统是基于用户认证鉴权的web系统,因此需要考虑验证用户登录是否合法的 问题,我们一般借助于Redis来实现分布式缓存技术来实现单点登录的场景,前端请求一般将token信息携带在请求Headers中,后端系统需要根据要验证的业务Url来从请求头中获取Token然后来验证,此方案使用简单的用户id+随机数+加密生成Token的方式来实现
- 业务代码
- 用户登录成功--存储缓存
第〇步 定义用户Token类+辅助加密类Base64Handler+Redis缓存工具对象RedisUtil
class UserToken(object):
def __init__(self, user_id, token, create_time, expire_time):
self.user_id = user_id
self.token = token
self.create_time = create_time
self.expire_time = expire_time
@staticmethod
def get_user_id_from_token(token):
token_user_id = Base64Handler.base64_decode(token)
data = token_user_id.split(" ")
user_id = data[1]
return user_id
class Base64Handler(object):
# 根据user_id生成原始字符串的token
@staticmethod
def gen_user_token_orign(user_id):
user_id = str(user_id)
init_token = str(uuid.uuid4())
final_token = init_token + " " + user_id
return final_token
# 根据user_id生成base64加密后的字符串的token
@staticmethod
def gen_user_token_encode(user_id):
return Base64Handler.base64_encode(Base64Handler.gen_user_token_orign(user_id))
# 根据user_id生成带base64加密后的字符串的token,创建时间,过期时间等信息的对象UserToken的json字符串
@staticmethod
def gen_user_token_info(user_id, times):
token = Base64Handler.gen_user_token_encode(user_id)
# time.mktime(time.localtime()) 获取的是秒级别
create_time = int(time.mktime(time.localtime()))
expire_time = create_time + times
return UserToken(user_id, token, create_time, expire_time)
# base64 加密处理
@staticmethod
def base64_encode(content):
return base64.b64encode(content.encode(encoding='utf-8'))
# base64 解密处理
@staticmethod
def base64_decode(content):
# 解密 b'hello world'
decode = base64.b64decode(content)
# hello world
decode = decode.decode(encoding='utf-8')
return decode
# RedisClient客户端
redis_client = redis.Redis(host=app.config.get('REDIS_HOST'),
port=app.config.get('REDIS_PORT'),
password=app.config.get('REDIS_PASSWORD'),
db=app.config.get('REDIS_DB'))
# Redis缓存工具对象
class RedisUtil(object):
# 验证Token是否生效
@staticmethod
def validate_token(token):
# 前端当token为null的时候默认传值
if 'token' == token:
return False
user_id = UserToken.get_user_id_from_token(token)
# 1. 判断在redis中是否有缓存
redis_token_key = "token_" + user_id
if redis_client.exists(redis_token_key) == 0:
return False
cache_data = redis_client.get(redis_token_key)
if cache_data is None or CommonTool.is_none(cache_data):
return False
cache_data = cache_data.decode()
user_token = json.loads(cache_data)
cache_data_token = user_token['token']
if cache_data_token != token:
print("------- cache_data_token != req_token: ")
return False
else:
# 判断是否过期
cache_data_expire_time = int(user_token['expire_time'])
current_time = int(time.mktime(time.localtime()))
if current_time >= cache_data_expire_time:
redis_client.delete(redis_token_key)
print("------- current_time >= cache_data_expire_time ")
return False
return True
# 用户登录token保存到redis
@staticmethod
def cached_user_token(user_id, user_token):
redis_token_key = "token_" + str(user_id)
redis_client.set(redis_token_key, user_token)
第一步 生成带过期时间和用户id的Token
user_id = user.id
expire_time = int(app.config.get('TOKEN_EXPIRED_TIME_M'))
user_token = Base64Handler.gen_user_token_info(user_id, expire_time)
第二步 将token缓存到Redis
RedisUtil.cached_user_token(user_id, json.dumps(user_token, cls=CustomerEncoder, indent=4))
第三步 将token返回给前端
res_data = {}
user_token_dict = {}
if isinstance(user_token, UserToken):
user_token_dict['user_id'] = user_token.user_id
# todo 返回值中不要带bytes类型的否则报错,此处涉及多次使用decode将bytes转为string
user_token_dict['token'] = user_token.token.decode(encoding='utf-8')
user_token_dict['create_time'] = user_token.create_time
user_token_dict['expire_time'] = user_token.expire_time
res_data['user_token'] = user_token_dict
res_data['sys_user'] = JsonHandler.object_to_dict(user)
res_data['sys_user']['password'] = '********'
return JsonResponse.success(200, msg="login success", data=res_data)
辅助工具类
class JsonHandler(object):
# 将对象转换为dict 只能转换表数据
@staticmethod
def object_to_dict(row):
d = {}
for column in row.__table__.columns:
d[column.name] = str(getattr(row, column.name))
return d
# 将对象数组转换为List<dict>
@staticmethod
def objects_to_arrays(rows):
arr = []
for row in rows:
arr.append(JsonHandler.object_to_dict(row))
return arr
# 将数据库查询行数据转为字典
@staticmethod
def row_to_dict(row):
return dict(row)
@staticmethod
def rows_to_arrays(rows):
arr = []
for row in rows:
arr.append(JsonHandler.row_to_dict(row))
return arr
class JsonResponse(object):
def __init__(self, code, msg, data, **kwargs):
self.code = code
self.msg = msg
self.data = data
self.pagination = kwargs
# 指定一个类的方法为类方法,通常用self来传递当前类的实例--对象,cls传递当前类。
@classmethod
def success(cls, code=200, msg="success", data=None):
return cls(code, msg, data)
@classmethod
def success_page(cls, code=200, msg="success", data=None, **kwargs):
return cls(code, msg, data, **kwargs)
@classmethod
def fail(cls, code=400, msg="fail", data=None):
return cls(code, msg, data)
def to_dict(self):
return {
"code": self.code,
"msg": self.msg,
"data": self.data,
"pagination": self.pagination
}
-
- 业务请求--验证登录信息
我们借助于请求钩子中的before_request 在请求前进行拦截,考虑到有些业务并不需要登录验证,例如用户登录、用户登出、用户注册等类似场景,如下所示代码
#不需要验证的接口地址
no_use_auth_urls = ["/admin/user/logout", "/admin/user/login", "/admin/user/register", "/admin/user/code", "/admin/menu/import", "/admin/menu/tree", "/admin/menu/tree/i18n", "/favicon.ico"] # 在每一次请求之前调用,这时候已经有了请求,可以在这个方法里面做请求的校验 # 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数了 目前必须在这个文件中才会执行 @app.before_request def before_request(): url = request.url base_url = request.base_url root_url = request.root_url host_url = request.host_url full_path = request.full_path f_path = full_path.split("?") bus_url = str(f_path[0]) is_static = bus_url.startswith("/static") if bus_url not in no_use_auth_urls and is_static == False: print('before_request bus_url : ', bus_url) token = request.headers.get('Authorization') user_id = UserToken.get_user_id_from_token(token) if token is None: return JsonResponse.fail(9999, 'request headers missing Authorization') if not RedisUtil.validate_token(token): return JsonResponse.fail(9401, 'request headers missing Authorization or Authorization is expired,need to login') return None

浙公网安备 33010602011771号