前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue + Flask 小知识(七)

Vue + Flask 小知识(七)

作者头像
周萝卜
发布2019-08-26 16:04:00
1.1K0
发布2019-08-26 16:04:00
举报
文章被收录于专栏:萝卜大杂烩

今天继续 Vue + Flask 小知识系列,登陆 session 的相关管理

登陆 session 超时

用户登陆系统之后,如果一段时间没有任何操作,session 需要有一个超时过期的动作,用户需要再次登陆才可以使用系统。

使用 before_app_request

before_app_request 是 flask 提供的请求钩子,可以装饰一个函数,使其在每次请求之前执行。

代码语言:javascript
复制
@auth.before_app_request
def before_request():
    if request.headers.get('Authorization', None) is not None:
        token = request.headers['Authorization'].split(' ')[1]
        try:
            data = s.loads(token)
            userId = data['userid']
            if rd.hget(data['userid'], "token") == token:
                operate_time = rd.hget(userId, "operate_time")
                if time.time() - float(operate_time) > session_expired_time:
                    rd.hdel(userId, "token", "operate_time")
                    return jsonify({"code": 401, "msg": "login expired"}), 401
                else:
                    rd.hset(userId, "operate_time", time.time())
            else:
                return jsonify({"code": 403, "msg": "token abnormal"}), 403
        except:
            pass
    else:
        pass

把用户 ID、token 和最近的操作时间保存到 redis 中,如果当前时间减去 redis 中保存的最近操作时间大于设置的超时时间,则返回 401 错误码。

改写登陆函数,设置 redis 哈希值

代码语言:javascript
复制
class LoginView(Resource):
    def post(self):
        try:
            username = request.get_json()['username']
            pwd = request.get_json()['password']
            user = User.query.filter_by(username=username).first()
            if user is not None and user.verify_password(pwd):
                data = token.genTokenSeq(username)
                h_dict = {"token": data['access_token'], "operate_time": time.time()}
                rd.hmset(user.id, h_dict)
                return {'code': 200, 'message': 'you are login now!', 'data': data}
            else:
                return {'code': 403, 'message': 'wrong account or password'}
        except:
            raise

用户登陆成功后,把用户 ID、token 和当前时间保存到 redis 中。

通过 axios interceptors 拦截

此时可以规定,401 错误码即为登陆超时错误码,使用 interceptors 拦截最为方便

代码语言:javascript
复制
Axios.interceptors.response.use(
    response => {
        return response;
    },
    error => {
        if (error.response){
            switch (error.response.status){
                case 401:
                    sessionStorage.removeItem(Config.tokenKey);
                    localStorage.removeItem('accessToken');
                    router.replace({
                        path: '/login',
                        query: {redirect: router.currentRoute.fullPath}
                    });
                    // vueObj 是在 main.js 中定义的把 vue实例赋予window的全局变量,在全局都可以使用,相当于 this。
                    vueObj.$message.error("登陆session过期,请重新登陆");
                    break;
                case 402:
                    console.log("do something");
                    break;

                case 403:
                    sessionStorage.removeItem(Config.tokenKey);
                    localStorage.removeItem('accessToken');
                    router.replace({
                        path: '/login',
                        query: {redirect: router.currentRoute.fullPath}
                    });
                    vueObj.$message.error("登陆token异常,请重新登陆");
                    break
            }
        }
        return Promise.reject(error.response.data);
    }
)

如果返回错误码为 401,则清空 sessionStorage 和 localStorage 相关信息,并重定向到 login 路由地址。

登出后 token 处理

在调用 logout 视图时,把对应的 token 保存到 redis 中,作为黑名单处理,黑名单内的 token 不再允许访问系统。

代码语言:javascript
复制
class LogoutView(Resource):
    @token.tokenRequired
    def get(self):
        try:
            token = request.headers['Authorization'].split(' ')[1]
            rd.set("token" + str(time.time()), token, ex=int(token_expired_time) + 200, nx=True)
        except:
            raise 

退出登陆的 token 保存以 token 为开头的 string 类型数据中

为 tokenRequired 装饰器增加判断

代码语言:javascript
复制
def tokenRequired(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'Authorization' in request.headers:
            split_token = request.headers['Authorization'].split(' ')
            if len(split_token) == 2 and split_token[0] == 'jwt':
                token = request.headers['Authorization'].split(' ')[1]
            else:
                return {'code': 401, 'message': 'authorize failed'}, 401
        else:
            return {'code': 401, 'message': 'authorize failed'}, 401
        token_list = []
        if rd.keys("token*"):
            for t in rd.keys("token*"):
                token_list.append(rd.get(t))
        if token in token_list:
            return {'code': 401, 'message': 'token is blocked'}, 401
        validator = validateToken(token)
        if validator['code'] != 200:
            if validator['message'] == 'toekn expired':
                return validator, 402
            else:
                return validator, 401
        elif validator['code'] == 200:
            return f(*args, **kwargs)
        return f(*args, **kwargs)
    return decorated_function

增加了读取 redis 和判断 token 的逻辑。

前端修改 logout 函数

代码语言:javascript
复制
logout() {
        logout.logout()
        .then(res => {
            console.log("成功退出登陆");
        })
        .catch(function(error){
            console.log("网络错误");
        })
        sessionStorage.removeItem(this.$Config.tokenKey);
        localStorage.removeItem('accessToken');
        this.$router.push({path: '/login'});
      }

至此,token 黑名单机制添加完毕。

刷新 token 功能

首先定义一个 renew token 的 API 函数

代码语言:javascript
复制
class RenewTokenView(Resource):
    @token.tokenRequired
    def post(self):
        old_token = request.headers['Authorization'].split(' ')[1]
        userid = load_token.load_token(old_token)['userid']
        refresh_token = request.get_json()['refresh_token']
        validator = token.validateToken(refresh_token)
        if validator['code'] != 200:
            if validator['message'] == 'toekn expired':
                return validator, 402
            else:
                return validator, 401
        elif validator['code'] == 200:
            new_token = token.genTokenSeq(userid=userid, onlyaccesstoken=True)
            h_dict = {"token": new_token['access_token'], "operate_time": time.time()}
            rd.hmset(userid, h_dict)
            return {'code': 200, 'message': 'renew token successful!', 'data': new_token}

接下来在前端判断 token 是否快要过期,如果快要过期,则调用刷新 token 的接口,刷新 token。

代码语言:javascript
复制
var nowTime = new Date();
            var tokenExpiredDate = new Date(localStorage.getItem("tokenExpiredDate"));
            var checkTime = (tokenExpiredDate.getTime() - nowTime.getTime())/1000;
            if(checkTime > 120) {
                next();
            }
            else{
                console.log("need refresh token");
                var r_token = {
                    "refresh_token": localStorage.getItem("refreshToken") 
                };
                refreshtoken.refreshtoken(r_token)
                .then(res => {
                    console.log("send request");
                    if(res.data.code === 200){
                        localStorage.setItem("accessToken", res.data.data['access_token']);
                        localStorage.setItem("accessTokenExpiryTime", res.data.data['access_token_expire_in']);
                        var signTokenTime = new Date();
                        var tokenExpiredDate = new Date(signTokenTime.setSeconds(signTokenTime.getSeconds() + res.data.data['access_token_expire_in']));
                        localStorage.setItem("tokenExpiredDate", tokenExpiredDate);
                        next();
                    }
                    else{
                        Message.error("刷新token失败,请重新登陆");
                        next({path: '/login'});
                    }
                })
                .catch(function(error){
                    console.log(error);
                    Message.error("刷新token失败,请重新登陆");
                    next({path: '/login'});
                })
            }

至此,access_token 快过期后,主动去刷新 token 的功能也做好了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 萝卜大杂烩 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 登陆 session 超时
  • 使用 before_app_request
  • 通过 axios interceptors 拦截
    • 登出后 token 处理
      • 刷新 token 功能
      相关产品与服务
      云数据库 Redis®
      腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档