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

 

posted @ 2023-05-15 10:40  521pingguo1314  阅读(135)  评论(0编辑  收藏  举报