Haytham 개인 블로그 개발 로그 - 플라스크 + 뷰는 토큰 기반 관리 및 라우팅을 기록

이정표

무슨 키워드에 맞춰, 가능이 블로그는 당신을 도울 것입니다

  • 공장 기능 플라스크 응용 프로그램을 사용하지 마십시오
  • 플라스크 응용 프로그램은 청사진을 사용하지 않습니다
  • 크로스 도메인 구성을 플라스크
  • 상태 기반 로그인의 토큰 관리
  • 플라스크 + 뷰
  • 뷰 라우팅 차단
  • Axios의 후크

적용 현장

이것은 최종 코드는 일반적으로 사용되는 기능 웹 개발자를 포함,의 "책"에 간단하고 플라스크 뷰 인 기록 블로그를 구축 할 수있는 개인 블로그입니다. (불완전 비교적 높은 주파수를 사용하는 것을 제외하고)

환경

  • 시스템 : 독립적
  • 플라스크 (Python3)
  • Vue (Node.js)

참고

"플라스크 웹 개발 파이썬 기반의 웹 애플리케이션 개발의 실제"
Vue.js

배경

개인 블로그 솔루션은 너무 많이, 왜 나는 나 자신 그것을 구축해야합니까?
사실, 목적은 워드 프레스 블로그 ... 또는 개인 블로그를 사용하여 직접 작성한 개인 블로그를 구축하는 것이 아니라 내가 아키텍처에 큰 변화의 결과로,로드 밸런싱, 클러스터링 기술을 결합 할 수 있습니다 계정으로 미래를 복용, 그들이 배운 기술을 연습하고 싶어 또는 음성 제어와 다른 새로운 게임이 재생됩니다 시도, 작업의 타당성에 라인 출력에 의해 코드 라인은 반드시 낫다.

코드 기능

블로그 기능은 다음과 같은 기본 기능을 달성하기 위해, 완벽하지
홈 상태를 로그인 할 필요가 블로그를 만들고, 모든 기사를 끌어 로그인, 블로그 만들기 (인하 편집기를) 등록 : 앞입니다.
후면 : 서비스 더보기 기능, 크로스 도메인 구성, 토큰 관리 및 인증, 데이터베이스 관리가 필요합니다.

공유 레코드의 목적은 관리 코드에 기록 달성을 위해 다음과 같이 요약된다

아이디어의 실현

다음과 같이 로그인 토큰을 기반으로 상태 관리를 구현하기 위해 아이디어는

  1. 프런트 엔드 계정 암호 배경 제출
  2. 이 토큰에 의해 배경 검증은 반환된다
  3. 헤더를 요청하기 위해 제공되는 각각의 요청 토큰의 선단부 전 (후크 Axios의 사용)
  4. 보호 된 함수를 얻는 배경보기 토큰 요청 헤더라고하며, 인증 토큰은 다음 호출은 아무런 문제가 있으면 허용

달성하기 위해 필요에 따라 이것은 일반적인 생각이다, 손 보호 단면도 기능 후속 호출은, 전에 어떤 작업을 완료하기 위해 종료 후 모두 수행 할 수 있습니다.
다음 절에서는 위의 아이디어의 순서에 따라, 주요 코드를 보여, 코드가 최종적으로 게시 완료됩니다.

특정 단계

인터 플라스크 구성

바람직한 전방 및 후방 단부는 다음과 같이 상호 도메인이 구성된 것으로 여기에서 사용 문제를 사용 flask_cors 라이브러리 후단 용액 필요 분리 :

가 I '토큰'는 대체 할 다른 이름을 사용할 경우 '액세스 제어를-허용 - 헤더'의 이름이 주어진 위해, 머리에서 제공하는 토큰 토큰을 획득 후 각 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

뷰는 Axios의를 통해 플라스크에 로그인 요청을 시작

배경에 획득 한 계정 암호의 프런트 엔드는, 토큰은 쓰기 Vuex에 의해 요구된다. (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){
})

플라스크보기 기능을 달성

뷰는 사용자 이름과 패스워드, 사용자 인증 정보를 통한 기능 및 토큰 생성은 토큰 리턴한다.

# 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"

뷰 구성 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 달성

auth.login_required 수정보기 기능에 액세스 @ 먼저 콜백 함수를 실행할 때 flask_httpauth 모듈은 거의의 기능을 구현하는 핵심 부분은 우리가 구현해야한다는 것입니다 @이 콜백 함수를 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);
        }
    },
})

뷰 - 라우터

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