Haytham personal blog development log - Flask + Vue logged token-based management and routing of

signpost

In line with what keywords, this blog possible will help you

  • Do not use the factory functions Flask application
  • Flask application does not use the blueprint
  • Flask cross-domain configuration
  • Token management of state-based login
  • Flask+Vue
  • Vue routing interception
  • Axios hook

Applicable scene

This is a personal blog to build a recording blog, is a simple and Flask Vue on the "book", the final code will include Web developers commonly used functions. (Incomplete, except for using a relatively high frequency)

surroundings

  • System: independent of
  • Flask(Python3)
  • Vue (Node.js)

reference

"Flask Web Development Python-based Web application development real"
Vue.js

background

Personal blog solutions so much, then why should I build myself a it?
In fact, the purpose is not to build a personal blog written directly using WordPress blog ... or a personal blog but I wanted to practice the technology they learn, taking into account the future may join load balancing, clustering techniques, resulting in major changes in architecture or try voice control and other new games are played, the code line by line out on the feasibility of the operation is necessarily better.

Code Function

Blog function is not perfect, only to achieve the following basic functions
Front: Register Login, blog creation (markdown editor) Home pull all articles, create a blog need to login status.
Rear: service requires more view function, cross-domain configuration, token management and authentication, database management.

For the purpose of sharing records will achieve logged in managed code are summarized as follows

Realization of ideas

To implement state management based on the login token, the idea is as follows

  1. The front-end account password to submit background
  2. Background verification, by this token is returned
  3. Before the front end of each request token which is provided to request header (used axios hooks)
  4. Background acquiring the protected function is called view token request header, and authentication token, then the call is allowed if no problem

This is a general idea, section view function subsequent calls to hand protection, both before and after any operation is to end to complete, can be performed as needed to achieve.
The following sections according to the order of the above ideas, show the main code, the code is completed finally posted.

Specific steps

Configuring Inter Flask

The preferred front and rear ends separated cross-domain needs to be configured, the rear end solution to the problem used here, use flask_cors library, as follows:

Will be due to the front end of each HTTP request after obtaining the token to token provided in the head, for the given named I 'token', if used other names, to be replaced in the 'Access-Control-Allow-Headers' in

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 initiate a login request to the flask through axios

The front end of the acquired account password to the background, the token is requested by the write Vuex. (Vuex the token will write 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 achieve view function

The view function via a user name and password, user authentication information, and generates a token, return 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

Axios hook configuration, at the head of each HTTP request token are added

axios.interceptors.request.use(
    config => {
        let token = localStorage.getItem('Authorization');
        if(token){
            config.headers.common['token'] = token
        }
        return config
    },
    err => {
        return Promise.reject(err);
    });

Achieve HTTPBasicAuth

flask_httpauth module implements the function of very few, the core part is that we need to implement @ auth.verify_password this callback function, when @ auth.login_required modified view function is accessed, will first execute the callback function, get in the callback function token http head, and verify that the token is legitimate, if it is legally allowed to access.

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"

Remark

Based on the above section of code that is the core of token management, and more aware of the idea to read the code, because it is also a reason to call functions, such as the ORM, so only over part of the code function is not perfect, please refer to simplify the following after the complete code.

The complete code

He emphasized : The following code for the sake of simplicity, the code required to implement the most basic functions, and does not follow the norms.

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()

View - 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,
})

View - 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
            }
        },
    ]
})

View - 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>

postscript

The complete code github address
haythamBlog
haythamBlog_flask

####
For more Haytham original articles, please pay attention to the public number "Xuju Long":
My micro-channel public number

Guess you like

Origin blog.51cto.com/13852791/2438066