The signpost
In line with the key words, this blog may be helpful to you.
- Flask applications without factory functions
- Flask Applications Without Blueprint
- Flask cross-domain configuration
- Login Status Management Based on Token
- Flask+Vue
- Vue Routing Interception
- Axios hook
Applicable scenario
This is a personal blog built record blog, but also a simple "tool book" about Flask and Vue, the final code will contain common functions in Web development. (incomplete, but relatively high frequency)
Environmental Science
- System: Irrelevant
- Flask(Python3)
- Vue(Node.js)
Reference resources
Flask Web Development Python-based Web Application Development Practice Vue.js
background
There are so many solutions for personal blog, why should I build another one by myself? In fact, the purpose of building a personal blog is not to write a blog... Otherwise, I will use WordPress directly. Personal blog is just a technology I want to learn by myself. Considering the possibility of adding load balancing, clustering and other technologies in the future, it will lead to major structural changes, or try to implement new methods such as voice control, one line by one code. The feasibility of the operation is bound to be better.
Code function
The function of blog is not perfect, it only realizes the following basic functions Front-end: Register for login, create a blog (markdown editor), pull all articles on the home page, create a blog needs to login status. Backend: View functions required by the above services, configuring cross-domain, token management and validation, database management.
For record sharing purposes, the code for login state management is organized as follows
Implementation ideas
To achieve token-based login status management, the ideas are as follows.
- Front End Submits Account Password to Background
- Background validation, through which token is returned
- The front end sets token to the request header before each request (using axios hooks)
- The background obtains the token of the request header when the protected view function is called, verifies the token, and allows the call if there is no problem.
This is a general idea, follow-up calls to hand-protected view function part, regardless of what the front and back end to complete, can be implemented as needed. The following sections will show the main code according to the order of the above ideas, and finally post the completion code.
Specific steps
Flask configuration across domains
The first choice for front-end and back-end separation is to configure cross-domain. Here, the solution of back-end solution is adopted. flask_cors library is used. The code is as follows:
Since the front end of the conference sets token at the head of each HTTP request after obtaining token, I give it the name'token'. If other names are used, they need to be replaced in'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 initiates a login request to flask via axios
The front end passes the obtained account password to the background and writes the requested token into Vuex. (Token will be written to local Storage in Vuex)
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 Implements View Functions
The view function verifies the user information through the username and password, generates token and returns 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 configuration Axios hook
Configure the Axios hook to add token at the head of each HTTP request
axios.interceptors.request.use( config => { let token = localStorage.getItem('Authorization'); if(token){ config.headers.common['token'] = token } return config }, err => { return Promise.reject(err); });
Implementing HTTP Basic Auth
The flask_httpauth module implements very few functions, and the core part is that we need to implement @auth.verify by ourselves._ The callback function password, when @auth.login _ When the required modified view function is accessed, the callback function is executed first. In the callback function, the token of the http header is obtained, and the validity of the token is verified. If it is valid, access is allowed.
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"
Remarks
The above part is the core part of the code based on token management. Read the above code to know the idea. Because it also calls functions such as ORM, only the above part of the code is not perfect. Please refer to the simplified complete code below.
Complete code
Emphasis: For the purpose of simplification, the following codes are the most basic codes to achieve functions, and do not follow various specifications.
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
<template> <div class="super"> <div class="header"> <div class="buttomDiv"> <Button type="success" class="loginButton" @click="showLoginModal">Login</Button> <Button type="primary" class="loginButton" @click="showRegisterModal">Register</Button> </div> </div> <div class = "content"> <div class="contentLeft"> <div v-for = "post in blogList" > <thumbnail v-bind:title=post.title v-bind:content=post.content ></thumbnail> </div> </div> <div class="contentRight"></div> </div> <Modal v-model="registerModalStatus" @on-ok="registerEvent"> <p>Register</p> <Input v-model="username" placeholder="Username" style="width: 300px" /> <Input v-model="password" placeholder="Password" style="width: 300px" /> </Modal> <Modal v-model="loginModalStatus" @on-ok="loginEvent"> <p>Login</p> <Input v-model="username" placeholder="Username" style="width: 300px" /> <Input v-model="password" placeholder="Password" style="width: 300px" /> </Modal> </div> </template> <script> 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") }, }, } </script> <style scoped> </style>
Epilogue
Full code github address haythamBlog haythamBlog_flask
For more original Haytham articles, please pay attention to the public number "Xu Julong":