Flask Vue.js全栈开发|第5章:个人主页与用户头像

  • 原创
  • Madman
  • /
  • /
  • 0
  • 3886 次阅读

flask vuejs 全栈开发-min.png

Synopsis: 用户认证修改为 JWT(JSON Web Token),后端使用 pyjwt 库生成 JWT 并验证合法性;前端使用 JSON.parse 解析 JWT 中的 payload 数据。提示消息改用 vue-toasted 插件,方便好用。前端每次访问 API 都需要附带 Token 到 Authorization 请求头中,使用请求拦截器自动添加进去,另外设置了响应拦截器,自动处理 401 和 404 错误。用户头像使用在线的 Gravatar 服务,前端使用 moment.js 格式化时间。个人主页的路由参数变化后默认是重用组件并不会重新加载数据,要使用 vue-router 的 beforeRouteUpdate() 导航守卫

代码已上传到 https://github.com/wangy8961/flask-vuejs-madblog/tree/v0.5 ,欢迎star

1. JWT

上一篇文章通过后端创建 Token 并保存到数据库中(同时记录 Token 的失效时间),结合 Flask-HTTPAuth 插件实现 Basic AuthToken Auth。但是,前端用户登录后,并不知道自己的用户 id,就没办法调用 GET /api/users/<id> 来获取自己的用户信息

1.1 pyjwt

现在改用 JWT 实现,它可以在 Token 中添加一些不是隐私的数据 payload,比如我们可以把用户 id 放进去。后端安装 pyjwt 包:

(venv) D:\python-code\flask-vuejs-madblog\back-end>pip install pyjwt
(venv) D:\python-code\flask-vuejs-madblog\back-end>pip freeze > requirements.txt

修改 User 数据模型 back-end/app/models.py,删除之前的 tokentoken_expiration 字段:

class User(PaginatedAPIMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))  # 不保存原始密码

    ...
    def get_jwt(self, expires_in=600):
        now = datetime.utcnow()
        payload = {
            'user_id': self.id,
            'name': self.name if self.name else self.username,
            'exp': now + timedelta(seconds=expires_in),
            'iat': now
        }
        return jwt.encode(
            payload,
            current_app.config['SECRET_KEY'],
            algorithm='HS256').decode('utf-8')

    @staticmethod
    def verify_jwt(token):
        try:
            payload = jwt.decode(
                token,
                current_app.config['SECRET_KEY'],
                algorithms=['HS256'])
        except (jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidSignatureError) as e:
            # Token过期,或被人修改,那么签名验证也会失败
            return None
        return User.query.get(payload.get('user_id'))

修改 back-end/app/api/tokens.py

from flask import jsonify, g
from app import db
from app.api import bp
from app.api.auth import basic_auth


@bp.route('/tokens', methods=['POST'])
@basic_auth.login_required
def get_token():
    token = g.current_user.get_jwt()
    db.session.commit()
    return jsonify({'token': token})

注意: JWT 没办法回收(不需要 DELETE /tokens),只能等它过期,所以有效时间别设置太长

1.2 Flask-Migrate 迁移

修改 User 数据模型后要进行数据库迁移:

(venv) D:\python-code\flask-vuejs-madblog\back-end>flask db migrate -m "users jwt"
(venv) D:\python-code\flask-vuejs-madblog\back-end>flask db upgrade
...
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "DROP": syntax error [SQL: 'ALTER TABLE user DROP COLUMN token_expiration'] (Background on this error at: http://sqlalche.me/e/e3q8)

注意: 报错的原因是 SQLite 默认不支持删除列

可以 batch_alter_table 上下文管理器来解决,参考: https://stackoverflow.com/questions/30394222/why-flask-migrate-cannot-upgrade-when-drop-column ,需要修改 back-end/migrations/env.py

def run_migrations_online():
    ...
    context.configure(connection=connection,
                      target_metadata=target_metadata,
                      process_revision_directives=process_revision_directives,
                      render_as_batch=True,  # 增加这个配置项
                      **current_app.extensions['migrate'].configure_args)

删除刚才新创建的迁移脚本,比如我的是 back-end/migrations/versions/8ba2e85acf5b_users_jwt.py。然后再次创建迁移脚本:

(venv) D:\python-code\flask-vuejs-madblog\back-end>flask db migrate -m "users jwt"

查看新迁移脚本,会发现使用了 batch_alter_table 上下文管理器:

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('user', schema=None) as batch_op:
        batch_op.drop_column('token')
        batch_op.drop_column('token_expiration')

    # ### end Alembic commands ###

最后,应用迁移脚本:

(venv) D:\python-code\flask-vuejs-madblog\back-end>flask db upgrade

1.3 JSON.parse

前端用户在登录页面输入用户名和密码后,axios 调用 POST /tokens。后端首先要进行 Basic Auth 认证, 如果用户名和密码正确,则进入视图函数 get_token 中执行并返回 JWT 给前端

用户 id 存放在 JWT 的第二部分中(三个部分以点号 . 分隔),所以前端可以使用如下方法解析 JSON 数据:

JSON.parse(atob(window.localStorage.getItem('madblog-token').split('.')[1])).user_id

修改 front-end/src/store.js

export default {
  debug: true,
  state: {
    is_authenticated: window.localStorage.getItem('madblog-token') ? true : false,
    // 用户登录后,就算刷新页面也能再次计算出 user_id
    user_id: window.localStorage.getItem('madblog-token') ? JSON.parse(atob(window.localStorage.getItem('madblog-token').split('.')[1])).user_id : 0
  },
  loginAction () {
    if (this.debug) { console.log('loginAction triggered') }
    this.state.is_authenticated = true
    this.state.user_id = JSON.parse(atob(window.localStorage.getItem('madblog-token').split('.')[1])).user_id
  },
  logoutAction () {
    if (this.debug) console.log('logoutAction triggered')
    window.localStorage.removeItem('madblog-token')
    this.state.is_authenticated = false
    this
                                
                            
未经允许不得转载: LIFE & SHARE - 王颜公子 » Flask Vue.js全栈开发|第5章:个人主页与用户头像

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

0 条评论

暂时还没有评论.

专题系列