指路牌

符合一下关键词,这篇博客有可能会对你有帮助

不使用工厂函数的Flask应用

不使用蓝本的Flask应用

Flask跨域配置

基于Token的登录状态管理

Flask+Vue

Vue路由拦截

Axios 钩子

适用场景

这是一篇个人博客搭建的记录博客,也是一篇关于Flask和Vue的简单"工具书",最后的代码会包含Web开发中常用的功能。(不全,只是使用频率相对高的)

环境

系统: 无关

Flask(Python3)

Vue(Node.js)

参考

《Flask Web开发 基于Python的Web应用开发实战》

Vue.js

背景

个人博客的解决方案那么多,为什么我要自己再搭建一个呢?

其实搭建个人博客的目的并不是为了写博客...否则直接使用WordPress了,个人博客只是我想要实践自己学的技术,同时考虑到以后可能会加入负载均衡、集群等技术,导致架构大改,或者尝试实现语音控制等新玩法,一行一行码出来的在操作的可行性上必然是更好的。

代码功能

博客功能尚不健全,只实现了以下的基本功能

前端:注册登陆,博客创建(markdown编辑器),首页拉取所有文章,创建博客需要登陆状态。

后端:以上服务需要的视图函数,配置跨域,令牌管理与验证,数据库管理。

出于记录的分享的目的,将实现登录状态管理的代码整理如下

实现思路

要实现基于令牌的登录状态管理,其思路大致如下

前端将帐号密码提交后台

后台验证,通过这返回token

前端在每次请求前将token设置到请求头当中(使用axios钩子)

后台在受保护的视图函数被调用时获取请求头的token,并验证token,若无问题则允许调用

这是一个大致的思路,后续调用手保护的视图函数部分,无论是让前后端完成什么操作,都可以执行根据需要实现。

以下部分将根据以上思路的顺序,展示主要代码,最后将贴出完成代码。

具体步骤

Flask配置跨域

前后端分离首选需要配置跨域,此处采用后端解决的方案,使用flask_cors库,代码如下:

由于会前端在获取token后会在每次HTTP请求时将token设置在头部,我给出的命名为'token',若使用了其他名称,需在'Access-Control-Allow-Headers'中替换

from flask_cors import CORS

CORS(app,supports_credentials=True)

@app.after_request

def after_request(resp):

resp = make_response(resp)

resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080'

resp.headers['Access-Control-Allow-Methods'] = 'GET,POST'

resp.headers['Access-Control-Allow-Headers'] = 'content-type,token'

return resp

Vue通过axios向flask发起登录请求

前端将获取的帐号密码传递给后台,将请求获取的token写入Vuex中。(Vuex中会将token写入localStorage)

let _this = this

axios.post('http://127.0.0.1:5000/login',{

username:this.username,

password:this.password,

})

.then(function(response){

let token = response.data

_this.changeLogin({Authorization: token})

})

.catch(function(error){

})

Flask实现视图函数

视图函数将通过用户名和密码,验证用户信息,并生成token,返回token。

# Routes

@app.route('/login',methods=['POST'])

def login():

json = request.get_json()

user = User.query.filter_by(username = json['username']).first()

if user.verify_password(json['password']):

g.currnet_user = user

token = user.generate_auth_token(expiration=3600)

return token

return "wrong password"

Vue配置Axios钩子

配置Axios钩子,在每次HTTP请求的头部都添加token

axios.interceptors.request.use(

config => {

let token = localStorage.getItem('Authorization');

if(token){

config.headers.common['token'] = token

}

return config

},

err => {

return Promise.reject(err);

});

实现HTTPBasicAuth

flask_httpauth模块实现的功能很少,其核心部分是我们需要自己实现@auth.verify_password这个回调函数,当被@auth.login_required修饰的视图函数被访问时,会先执行回调函数,在回调函数中将获取http头部的token,并验证该token是否合法,若合法则允许访问。

from flask_httpauth import HTTPBasicAuth

auth = HTTPBasicAuth()

@auth.verify_password

def verify_password(username_token):

username_token = request.headers.get('Token')

if username_token == '':

return False

else:

g.currnet_user = User.verify_auth_token(username_token)

g.token_used = True

return g.currnet_user is not None

@auth.login_required

@app.route('/creatpost',methods=['POST'])

def new_post():

json = request.get_json()

newpost = Post(title=json['title'],content=json['content'])

db.session.add(newpost)

db.session.commit()

return "200 OK"

备注

以上部分即是实现基于令牌管理的代码核心部分,阅读以上代码知晓思路即可,由于其还调用了诸如ORM中的函数的原因,所以只有以上部分代码功能并不健全,请参考下面简化后的完整代码。

完整代码

强调:以下代码出于简化的目的,皆为实现功能的最基本码,并没有遵循各种规范。

Flask

import os

from flask import Flask,make_response,render_template,redirect,url_for,jsonify,g,current_app,request,session

from flask_cors import CORS

from flask_sqlalchemy import SQLAlchemy

from flask_httpauth import HTTPBasicAuth

from flask_login import login_user,UserMixin,LoginManager,login_required

from werkzeug.security import generate_password_hash,check_password_hash

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

basedir = os.path.abspath(os.path.dirname(__file__))

# SQLite

app = Flask(__name__)

app.config['SECRET_KEY'] = 'secret-key'

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,'data.sqlite')

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# CORS

CORS(app,supports_credentials=True)

@app.after_request

def after_request(resp):

resp = make_response(resp)

resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080'

resp.headers['Access-Control-Allow-Methods'] = 'GET,POST'

resp.headers['Access-Control-Allow-Headers'] = 'content-type,token'

return resp

# Http auth

auth = HTTPBasicAuth()

@auth.verify_password

def verify_password(username_token):

username_token = request.headers.get('Token')

if username_token == '':

return False

else:

g.currnet_user = User.verify_auth_token(username_token)

g.token_used = True

return g.currnet_user is not None

@auth.error_handler

def auth_error():

return unauthorized('Invalid credentials')

# Routes

@app.route('/login',methods=['POST'])

def login():

json = request.get_json()

user = User.query.filter_by(username = json['username']).first()

if user.verify_password(json['password']):

g.currnet_user = user

token = user.generate_auth_token(expiration=3600)

return token

return "wrong password"

@app.route('/register',methods=['POST'])

def register():

json = request.get_json()

email = json['username'] + '@email.com'

user = User(email=email,username=json['username'],password=json['password'])

db.session.add(user)

db.session.commit()

return "200 OK register"

@app.route('/postlist')

def article():

ptemp = Post.query.all()

return jsonify({

'posts': [post.to_json() for post in ptemp],

})

@auth.login_required

@app.route('/creatpost',methods=['POST'])

def new_post():

json = request.get_json()

newpost = Post(title=json['title'],content=json['content'])

db.session.add(newpost)

db.session.commit()

return "200 OK"

def unauthorized(message):

response = jsonify({'error': 'unauthorized', 'message': message})

response.status_code = 401

return response

# ORM

class User(UserMixin,db.Model):

__tablename__ = 'users'

id = db.Column(db.Integer, primary_key=True)

email = db.Column(db.String(64),unique=True,index=True)

username = db.Column(db.String(64),unique=True,index=True)

password_hash = db.Column(db.String(128))

@property

def password(self):

raise AttributeError('password is not a readable attribute')

@password.setter

def password(self,password):

self.password_hash = generate_password_hash(password)

def verify_password(self,password):

return check_password_hash(self.password_hash,password)

def generate_auth_token(self,expiration):

s = Serializer(current_app.config['SECRET_KEY'],expires_in = expiration)

return s.dumps({'id':self.id}).decode('utf-8')

@staticmethod

def verify_auth_token(token):

s = Serializer(current_app.config['SECRET_KEY'])

try:

data = s.loads(token)

except:

return None

return User.query.get(data['id'])

class Post(db.Model):

__tablename__ = 'posts'

id = db.Column(db.Integer, primary_key=True)

title = db.Column(db.String(64),unique=True,index=True)

content = db.Column(db.String(64))

def to_json(self):

json_post = {

'title': self.title,

'content': self.content,

}

return json_post

if __name__ == '__main__':

db.drop_all()

db.create_all()

app.run()

Vue -- main.js

import Vue from 'vue';

import App from './App.vue';

import VueRouter from 'vue-router';

import router from './router';

import iView from 'iview';

import 'iview/dist/styles/iview.css';

import axios from 'axios';

import vueAxios from 'vue-axios';

import store from './store';

import Vuex from 'vuex'

Vue.config.productionTip = false

Vue.use(VueRouter)

Vue.use(iView)

Vue.use(vueAxios,axios)

Vue.use(Vuex)

router.afterEach(route=>{

window.scroll(0,0);

})

router.beforeEach((to,from,next)=>{

let token = localStorage.getItem('Authorization');

if(!to.meta.isLogin){

next()

}else{

let token = localStorage.getItem('Authorization');

if(token == null || token == ''){

next('/')

}else{

next()

}

}

})

axios.interceptors.request.use(

config => {

let token = localStorage.getItem('Authorization');

if(token){

config.headers.common['token'] = token

}

return config

},

err => {

return Promise.reject(err);

});

new Vue({

el:'#app',

render: h => h(App),

router,

store,

})

Vue -- Vuex

import Vue from 'vue';

import Vuex from 'vuex';

import store from './index';

Vue.use(Vuex);

export default new Vuex.Store({

state:{

Authorization: localStorage.getItem('Authorization') ? localStorage.getItem('Authorization') : ''

},

mutations:{

changeLogin (state, user) {

state.Authorization = user.Authorization;

localStorage.setItem('Authorization', user.Authorization);

}

},

})

Vue -- router

import Vue from 'vue'

import Router from 'vue-router'

import home from '../components/home.vue'

import articleDetail from '../components/articleDetail'

import createPost from '../components/createPost'

Vue.use(Router)

export default new Router({

mode:'history',

routes:[

{

path:'/',

component:home,

name:'home',

meta:{

isLogin:false

}

},

{

path:'/article',

component:articleDetail,

name:'article',

meta:{

isLogin:false

}

},

{

path:'/createpost',

component:createPost,

name:'createpost',

meta:{

isLogin:true

}

},

]

})

Vue -- Components -- home.vue

Login

Register

v-for = "post in blogList"

>

v-bind:title=post.title

v-bind:content=post.content

>

Register

Login

import axios from 'axios'

import {mapMutations} from 'vuex'

import store from '../store'

import thumbnail from './articleThumbnail.vue'

export default{

name: 'home',

data:function(){

return {

loginModalStatus:false,

registerModalStatus:false,

username:'',

password:'',

blogList:'',

}

},

components:{

thumbnail:thumbnail,

},

created(){

localStorage.removeItem("Authorization","")

let _this = this

axios.get('http://127.0.0.1:5000/postlist')

.then(function(response){

_this.blogList = response.data.posts

})

.catch(function(error){

})

},

methods:{

...mapMutations([

'changeLogin'

]),

showRegisterModal:function(){

this.registerModalStatus = true;

},

showLoginModal:function(){

this.loginModalStatus = true;

},

registerEvent:function(){

let that = this

axios.post('http://127.0.0.1:5000/register',{

username:this.username,

password:this.password,

})

.then(function(res){

})

.catch(function(error){

})

},

loginEvent:function(){

let _this = this

axios.post('http://127.0.0.1:5000/login',{

username:this.username,

password:this.password,

})

.then(function(response){

let token = response.data

_this.changeLogin({Authorization: token})

})

.catch(function(error){

})

},

navigator:function(){

this.$router.push("/article")

},

},

}

后记

要获取更多Haytham原创文章,请关注公众号"许聚龙":

python vue token_Haytham个人博客开发日志 -- Flask+Vue基于token的登录状态与路由管理...相关推荐

  1. Vue实战狗尾草博客管理系统第一章

    Vue实战狗尾草博客管理系统第一章 Vue实战狗尾草博客后台管理系统第一章 这里准备采用的技术栈为:vue全家桶+element-ui 这里因为是后台管理系统,没有做SSR的必要.所以这里就采用前后端 ...

  2. 基于SpringBoot和Vue的个人博客系统

    基于SpringBoot和Vue的个人博客系统 前言 ​ 本期项目分享一个漫威主题的炫酷博客系统,基于SpringBoot和Vue开发的前端分离项目.博客系统分为博客前台和博客后台两部分,游客可以访问 ...

  3. SpringBoot 博客开发 个人学习(项目开始和前端页面)

    博客开发 前言 1.需求与功能 1.1 需求 1.2 功能 2.页面开发(非重点,可直接跳过看后台部分) ===>>前端展示页面 2.1 首页 2.2 详情页 2.3 分类页 2.4 标签 ...

  4. Django个人博客搭建教程---用Vue写你的第一个前后端分离页面

    一.构建Vue.js前端项目 npm install vue-cli -g npm install webpack -g 在项目根目录下(和你的app目录平级) vue init webpack my ...

  5. 基于springboot + vue 的个人博客搭建过程(上线)

    承接上文: 基于springboot + vue 的个人博客搭建过程(续) 目录 1. 搭建环境 1. 安装docker 2. 拉取并运行 2.1 拉取服务 2.2 部署运行mysql 2.3 部署运 ...

  6. 一个 Vue + Node + MongoDB 博客系统

    源码 耗时半载(半个月)的大项目终于完成了.这是一个博客系统,使用 Vue 做前端框架,Node + express 做后端,数据库使用的是 MongoDB.实现了用户注册.用户登录.博客管理(文章的 ...

  7. Django 3.2.5博客开发教程:HelloWorld欢迎页面

    基础配置做好了之后,我们就可以先迁移数据到数据库,然后启动我们的项目,感受Django的魅力. 在Pycharm左下角底部的Terminal,会弹出Terminal终端窗口,Pycharm自动会帮我们 ...

  8. Django 3.2.5博客开发教程:URL与视图函数

    在讲URL与视图函数之前我们先给大家简单介绍一下用户访问网站的流程.我们访问一个网站的时候,一般先打开浏览器,然后在浏览器的地址栏里输入一个网址,也就是URL,然后回车,我们就可以在浏览器里看到这个网 ...

  9. Django 3.2.5博客开发教程:体验django模板

    上面我们有说过,用户发送请求的时候,视图会返回一个响应,响应可以是一个重定向,一个404错误,一个XML文档,一张图片或者是一个HTML内容的网页.前面几个返回的信息比较有限,我们重点更多是放在HTM ...

最新文章

  1. 基于爬山算法求解TSP问题(JAVA实现)
  2. MVC把表格导出到Excel
  3. Slony-I双机备份
  4. 计算机英文版个人简历发文,计算机个人简历英文_英文简历.doc
  5. kotlin 查找id_Kotlin程序查找等边三角形的区域
  6. 入门科普:什么时候要用Python?用哪个版本?什么时候不能用?
  7. Vue的过滤器,生命周期的钩子函数和使用Vue-router
  8. 流程图符号以及绘制流程图方法
  9. zynq创建ramip核
  10. paddle2.0实现DNN(minst数据集)
  11. 数理统计期末复习知识点总结(一)
  12. 吴江运东2万+,到底贵不贵?
  13. H5播放flv视频流
  14. 电子工业的发展也带动了电子设计自动化技术
  15. 什么是Linkerd
  16. 即使你毕业非名校,也能找到月入10k的工作
  17. 创建一个基于SpringBoot + MyBatis-Plus 的项目
  18. 安卓pdf阅读器_一文看懂|Kindle和其他安卓电子书阅读器,该选哪个?图+视频一目了然...
  19. SQL菜鸟笔记之第一篇 实验环境的搭建及准备工作
  20. Exception in thread C3P0PooledConnectionPoolManag

热门文章

  1. 计算机配件模拟,模拟计算机是指什么
  2. C++课后习题第五章17
  3. 贝叶斯方法学习笔记(二)
  4. dart语言中的常量与变量
  5. android 二级 滚动,android使用 ScrollerView 实现 可上下滚动的分类栏实例
  6. pandas apply lambda_一分钟一个Pandas小技巧(二)
  7. AcWing 1884. COW(前缀和)
  8. LRU算法的实现(STL+模拟)
  9. jquery修改服务器json,在没有JQuery的情况下将JSON发送到服务器并获取JSON作为回报...
  10. win10创建mysql数据库吗_win10 sqlite3创建的数据库文件在哪