Haytham個人ブログの開発ログ - フラスコ+ Vueがトークンベースの管理とのルーティングをログに記録しました

道標

どのようなキーワードに沿って、可能なこのブログがお手伝いします

  • ファクトリ関数フラスコアプリケーションを使用しないでください。
  • フラスコのアプリケーションは、設計図を使用していません
  • クロスドメイン構成をフラスコ
  • 状態ベースのログインのトークン管理
  • フラスコ+ヴュー
  • Vueのルーティング傍受
  • Axiosフック

該当シーン

これは、記録のブログを構築するための個人的なブログです最終的なコードは、Web開発者、一般的に使用される機能が含まれます、「ブック」に、シンプルでフラスコVueのです。(比較的高い周波数を使用したこと以外は、不完全)

環境

  • システム:の独立しました
  • フラスコ(のpython3)
  • Vue(Node.js)

参照

「フラスコのWeb開発PythonベースのWebアプリケーション開発の本当の」
Vue.js

背景

個人的なブログのソリューションはそんなに、なぜ私は自分自身それ構築する必要がありますか?
実際には、目的は、ワードプレスのブログ...または個人のブログを使用して直接書かれた個人的なブログを構築することはありませんが、私は将来のアーキテクチャに大きな変化が生じ、負荷分散、クラスタリング技術を結合することを考慮に入れて、彼らが学ぶ技術を練習したかったですまたは音声コントロールや他の新しいゲームがプレイされ、操作の実現可能性にラインアウトすることにより、コードの行が必ずしも優れてみてください。

コード機能

ブログ機能は、以下の基本的な機能を実現するために、完璧ではない
ホームは、ステータスをログインする必要がブログを作成し、すべての記事を引っ張っログイン、ブログの作成(値下げエディタ)を登録:フロント。
リア:サービスは、より多くのビュー機能、クロスドメイン構成、トークン管理や認証、データベース管理が必要です。

共有レコードの目的のために、以下のように要約されているマネージコードでログインして達成します

アイデアの実現

次のようにログイントークンに基づいて、状態管理を実装するには、アイデアがあります

  1. 背景を提出するフロントエンドのアカウントのパスワード
  2. このトークンによって背景の検証は、返されます
  3. (フックaxios使用)リクエストヘッダに提供される各リクエストトークンのフロントエンド前
  4. 保護された関数を取得背景ビュートークン要求ヘッダと呼ばれ、認証トークン、問題はないならば、コールは許可されています

これは一般的な考え方、手の保護への断面図機能後続の呼び出し、前と後のいずれかの操作が完了するまでに終了することで、必要に応じて、達成するために行うことができます両方です。
上記のアイデアの順序に従って次のセクションでは、メインのコードを示し、コードは最後に掲載されて完成。

具体的な手順

インターフラスコの設定

好ましい前端と後端は、以下のように、flask_corsライブラリを使用して、クロスドメインを構成する必要がここで使用される問題の後端溶液を分離しました:

与えられた他の名前を使用した場合、私はの「アクセス制御 - 許可 - ヘッダ」に交換する、「トークン」という名前のために、ヘッドに設けられたトークンにトークンを取得した後に起因する各HTTP要求の前端になります

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を通じてフラスコにログイン要求を開始します

背景に取得したアカウントのパスワードのフロントエンド、トークンは、書き込みVuexによって要求されます。(Vuexトークンはの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){
})

フラスコビュー機能を実現

ユーザー名とパスワード、ユーザ認証情報、およびトークンを生成し、リターン・トークンを経由してビュー機能。

# 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要求トークンの先頭に追加されます

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.login_required修正ビュー機能がアクセスされると、最初のコールバック関数で取得し、コールバック関数を実行しますが、このコールバック関数の@ auth.verify_passwordを実装する必要があるということですトークンのhttpヘッドは、それが合法的にアクセスを許可された場合、トークンは、正当であることを確認してください。

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として、また、関数を呼び出すための理由であるので、これだけのコード機能の一部の上に完璧ではない、後に次のことを簡素化するために参照してください。完全なコード。

完全なコード

彼は強調し、簡単のため、次のコードを、最も基本的な機能を実装するために必要なコードを、そして規範に従っていません:。

フラスコ

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

ビュー - 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,
})

ビュー - 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の - ルータ

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

ビュー - コンポーネント - 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>

追伸

完全なコードgithubのアドレス
haythamBlog
haythamBlog_flask

####
よりHaytham元の記事については、公共の番号「Xujuロング」に注意を払ってください。
私のマイクロチャネル公共数

おすすめ

転載: blog.51cto.com/13852791/2438066