flask中使用refresh token来刷新access token(前后端分离)

需要会的东西

  1. JWT
  2. token的认证方式
  3. hook
  4. g对象

认证机制

我试着描述一下

常规的token认证

前端每次向后端发送请求,在请求头里面写入access token,后端进行认证,非必须登录的操作都可以正常进行,而必须登录的资源都会返回login required
缺点:因为access token权限高,会频繁使用,相对不安全。设置过期时间太长了,被盗取后容易出问题,但是设置过期时间太短了,用户就需要频繁登录,来获取到access token

access token + refresh token

access token的操作跟上述一致,但是明确了,access token的过期时间:,但是没有很短,如果很短就需要一直刷新,refresh token就会频繁发送,这是不可以的。
时效短带来的不好的用户体验怎么来解决呢?这时候就需要refresh token来刷新access token了

前端 后端 发送请求 验证access token data login require refresh 发送refresh token 验证refresh token 分配新的access token refresh token is wrong alt [验证成功] [验证失败] alt [验证成功] [验证失败] [access token过期] loop [每次请求] 前端 后端

实际操作

生成+验证

具体操作跟上一篇博客基本一致
这里直接上代码了

def create_token(user, refresh_token=False):
    headers = {
        "alg": "HS256",
        "typ": "JWT",
    }
    exp = int(time.time() + 600) if refresh_token is False else int(time.time() + 3600 * 24 * 14)
    payload = {
        "name": user.username,
        "user_id": user.user_id,
        "exp": exp,
        "iss": 'byszqq'
    }
    key = current_app.config.get('JWT_SECRET_KEY')
    if refresh_token is True:
        u = UserModel.query.filter(UserModel.user_id == user.user_id).first()
        refresh_key = rand_str(6)
        u.refresh_key = refresh_key
        payload['refresh_key'] = refresh_key
        db.session.commit()
    token = jwt.encode(payload=payload, key=key, algorithm='HS256', headers=headers)
    return token


def validate_token(token, refresh_token=False):
    payload = None
    msg = None
    key = current_app.config.get('JWT_SECRET_KEY')
    try:
        payload = jwt.decode(jwt=token, key=key, algorithms=['HS256'], issuer='byszqq')
        if refresh_token is True:
            user = UserModel.query.filter(UserModel.user_id == payload.get('user_id')).first()
            if user.refresh_key != payload.get('refresh_key'):
                msg = 'refresh key 错误'
        else:
            if payload.get('refresh_key') is not None:
                msg = '禁止用refresh token'
    except exceptions.ExpiredSignatureError:
        msg = 'token已失效'
    except jwt.DecodeError:
        msg = 'token认证失败'
    except jwt.InvalidTokenError:
        msg = '非法的token'
    return payload, msg

在hook中的操作

在接受请求,进入视图函数前,对请求头中的Authorization中的access token进行认证,如果通过认证,给g对象绑定user对象,如果是时效,g.refresh = True。
在经过视图函数后,进行两个判断,如果是access token过期了,在Authorization中放入refresh字符串,告诉前端,需要访问刷新token的接口。如果g对象有user对象,在Authorization中放入新的access token,并且如果是刚登录的用户,再添加refresh token在请求头中。
注意一个东西,在before_request中的g.user = user

@app.before_request
def before_request():
    token, msg = validate_token(request.headers.get('Authorization'))
    if msg is None:
        try:
            user = UserModel.query.filter(UserModel.user_id == token.get('user_id')).first()
            g.user = user
        except Exception as e:
            print(e)
    elif msg == 'token已失效':
        g.refresh = True


@app.after_request
def after_request(resp):
    if hasattr(g, 'refresh') and g.refresh is True:
        resp.headers['Authorization'] = 'refresh'
    if hasattr(g, 'user'):
        resp.headers['Authorization'] = create_token(g.user)
        if hasattr(g, 'login') and g.login is True:
            resp.headers['refresh-token'] = create_token(g.user, refresh_token=True)
    return resp

login

其他都不太重要,重要的是我们看到g.user = cur_user

class LoginAPI(MethodView):
    def post(self):
        username = request.form.get('username')
        password = request.form.get('password')
        try:
            cur_user = UserModel.query.filter(UserModel.username == username).first()
        except SQLAlchemyError as e:
            Log.error(e)
            return jsonify({'code': 500, 'message': 'database error'})
        else:
            if cur_user is None:
                return jsonify({'code': 404, 'message': "there's no such user"})
            if check_password_hash(cur_user.password, password):
                g.user = cur_user
                g.login = True
                return jsonify({'code': 200, 'message': 'success', 'data': obj_to_dict(g.user)})
            else:
                return jsonify({'code': 403, 'message': 'your password is wrong'})

login require

简单来说,就是验证g对象中有没有user这个属性,而根据上面的login操作,还有hook里面的操作,会发现,只有当1. 登录过后,有g.user 2. access token通过验证后,有g.user

def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if hasattr(g, 'user'):
            return func(*args, **kwargs)
        return jsonify({'code': 403, 'message': "login required"})
    return wrapper

refresh

这是一个接口,当前端受到的Authorization中是"refresh"是调用
将refresh_token传到后端,进行验证,通过了又是g.user = user,返回去看一看hook的after_request的描述,如果g对象有user对象,在Authorization中放入新的access token,所以就会发现,在经过了refresh这个接口后,如果refresh token正确,就会有g.user = user,然后就会有resp.headers['Authorization'] = create_token(g.user),这样就做到了根据refresh token 刷新access token的效果了

class RefreshAPI(MethodView):
    def post(self):
        refresh_token = request.form.get('refresh_token')
        token, msg = validate_token(refresh_token, refresh_token=True)
        if msg is None:
            try:
                user = UserModel.query.filter(UserModel.user_id == token.get('user_id')).first()
            except SQLAlchemyError as e:
                Log.error(e)
            else:
                g.user = user
            return jsonify({'code': 200, 'message': 'success'})
        else:
            return jsonify({'code': 403, 'message': 'please login again'})

结语

算是敲了一年的代码了,有啥问题请多多指教

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
可以通过以下步骤来实现使用 Flask 进行前后分离上传文件: 1. 在前页面添加一个文件上传的表单,例如: ```html <form method="post" enctype="multipart/form-data" id="upload-form"> <input type="file" name="file" id="file-input"> <button type="submit">上传</button> </form> ``` 2. 在前页面编写 JavaScript 代码,使用 Fetch API 或 Axios 发送文件上传请求,例如: ```javascript const form = document.querySelector('#upload-form'); form.addEventListener('submit', (event) => { event.preventDefault(); const input = document.querySelector('#file-input'); const file = input.files[0]; const url = '/upload'; // 上传文件的后 API URL const formData = new FormData(); formData.append('file', file); fetch(url, { method: 'POST', body: formData, }).then(response => response.json()) .then(data => { console.log(data); }) .catch(error => { console.error(error); }); }); ``` 3. 在 Flask应用定义一个接收文件上传请求的路由,例如: ```python from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/upload', methods=['POST']) def upload_file(): file = request.files['file'] # 对文件进行处理,例如保存到本地磁盘或者处理文件内容 return jsonify({'status': 'ok'}) ``` 在这个路由函数,可以通过 `request.files` 获取上传的文件对象,然后对文件进行处理。最后返回一个 JSON 格式的响应,告诉前文件上传成功或者失败。 需要注意的是,在使用 Flask 进行文件上传时,需要设置表单的 `enctype` 属性为 `multipart/form-data`,这样才能上传二进制文件数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gsxdcyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值