Flask與Vue的token認證

後端使用flask設計基於token認證方式的restful接口,前端使用vue.js全家桶,利用axios通信。html

感謝兩篇文章的做者:前端

源碼連接:https://github.com/xingyys/fl...vue

後端Flask

Flask採用token認證方式,主要思路是經過/api/login登陸獲取token,而後使用token調用各個接口。
所用到框架的庫:python

  • flask
  • flask-cors:flask跨域
  • flask-sqlachemy: flask數據庫orm
  • flask-httpauth:flask的auth認證
  • passlib: python密碼解析庫
  • itsdangerous

後端結構圖

flask/
├── app    # 主目錄
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── models.py    # 數據庫
│   ├── models.pyc
│   ├── views.py    # 視圖
│   └── views.pyc
├── config.py    # 配置信息
├── config.pyc
├── db_create.py    # 建立數據庫
├── db_migrate.py   # 更新數據庫
├── db_repository
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── manage.py
│   ├── migrate.cfg
│   ├── README
│   └── versions
│       ├── 008_migration.py
│       ├── 008_migration.pyc
│       ├── 009_migration.py
│       ├── 009_migration.pyc
│       ├── __init__.py
│       └── __init__.pyc
├── index.html
└── run.py    # app的運行文件

具體實現

系統初始化app/__init__.py

# -*- coding:utf-8 -*-

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_httpauth import HTTPBasicAuth
from flask_cors import CORS

app = Flask(__name__)
# flask的跨域解決
CORS(app)
app.config.from_object('config')
db = SQLAlchemy(app)
auth = HTTPBasicAuth()

from . import models, views

配置文件config.py

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

SQLALCHEMY_DATABASE_URI = "mysql://root:123456@127.0.0.1/rest"
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
SQLALCHEMY_TRACK_MODIFICATIONS = True
BASEDIR = basedir
# 安全配置
CSRF_ENABLED = True
SECRET_KEY = 'jklklsadhfjkhwbii9/sdf\sdf'

環境中使用mysql數據庫,版本爲mariadb 10.1.22。建立restmysql

$ mysql -uroot -p xxxxxx
$ create database rest default charset utf8;

建立數據庫表app/models.py

# -*- coding:utf-8 -*-

from app import db, app
from passlib.apps import custom_app_context
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpired, BadSignature

class User(db.Model):
    __tablename__ =  'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(32), index=True)
    password = db.Column(db.String(128))

    # 密碼加密
    def hash_password(self, password):
        self.password = custom_app_context.encrypt(password)
    
    # 密碼解析
    def verify_password(self, password):
        return custom_app_context.verify(password, self.password)

    # 獲取token,有效時間10min
    def generate_auth_token(self, expiration = 600):
        s = Serializer(app.config['SECRET_KEY'], expires_in = expiration)
        return s.dumps({ 'id': self.id })

    # 解析token,確認登陸的用戶身份
    @staticmethod
    def verify_auth_token(token):
        s = Serializer(app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None # valid token, but expired
        except BadSignature:
            return None # invalid token
        user = User.query.get(data['id'])
        return user

建立數據庫users表:ios

$ python db_create.py
$ python db_migrate.py

視圖app/views.py

from app import app, db, auth
from flask import render_template, json, jsonify, request, abort, g
from app.models import *

@app.route("/")
@auth.login_required
def index():    
    return jsonify('Hello, %s' % g.user.username)


@app.route('/api/users', methods = ['POST'])
def new_user():
    username = request.json.get('username')
    password = request.json.get('password')
    if username is None or password is None:
        abort(400) # missing arguments
    if User.query.filter_by(username = username).first() is not None:
        abort(400) # existing user
    user = User(username = username)
    user.hash_password(password)
    db.session.add(user)
    db.session.commit()
    return jsonify({ 'username': user.username })

@auth.verify_password
def verify_password(username_or_token, password):
    if request.path == "/api/login":
        user = User.query.filter_by(username=username_or_token).first()
        if not user or not user.verify_password(password):
            return False
    else:
        user = User.verify_auth_token(username_or_token)
        if not user:
            return False    
    g.user = user   
    return True


@app.route('/api/login')
@auth.login_required
def get_auth_token():
    token = g.user.generate_auth_token()
    return jsonify(token)

用戶註冊後密碼加密存儲,確認用戶身份時密碼解密。須要認證的api上添加@auth.login_required,它會在調用接口以前調用@auth.verify_password下的方法(此方法惟一)如verify_password。根據請求的路徑選擇不一樣的認證方式。git

測試

使用curl命令測試接口github

註冊用戶:vue-router

$ curl -i -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}' http://127.0.0.1:5000/api/register
HTTP/1.0 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: *
Content-Length: 26
Server: Werkzeug/0.12.2 Python/2.7.13
Date: Wed, 20 Sep 2017 06:33:46 GMT

{
  "username": "admin"
}

查看數據庫:sql

MariaDB [rest]> select * from users\G;
*************************** 1. row ***************************
      id: 1
username: admin
password: $6$rounds=656000$etV4F3xLL0dwflX8$mLFX9l5dumBnQFtajGmey346viGuQ4bxR7YhQdKtB/nQH9ij2e3HHMEBPj.ef/o//4o9P2Wd3Y7dxQfjwR2hY/
1 row in set (0.00 sec)

獲取token:

curl -i -u admin:123456  -X GET -H "Content-Type: application/json"  http://127.0.0.1:5000/api/login
HTTP/1.0 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: *
Content-Length: 125
Server: Werkzeug/0.12.2 Python/2.7.13
Date: Wed, 20 Sep 2017 06:37:01 GMT

"eyJhbGciOiJIUzI1NiIsImV4cCI6MTUwNTg5MDAyMSwiaWF0IjoxNTA1ODg5NDIxfQ.eyJpZCI6MX0.nUIKq-ZhFOiLPwZyUmfgWPfHYNy8o6eoR6lmzdsY0oQ"

使用token調用api:

$ curl -i -u eyJhbGciOiJIUzI1NiIsImV4cCI6MTUwNTg5MDAyMSwiaWF0IjoxNTA1ODg5NDIxfQ.eyJpZCI6MX0.nUIKq-ZhFOiLPwZyUmfgWPfHYNy8o6eoR6lmzdsY0oQ:unused   -X GET -H "Content-Type: application/json"  http://127.0.0.1:5000/
HTTP/1.0 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: *
Content-Length: 15
Server: Werkzeug/0.12.2 Python/2.7.13
Date: Wed, 20 Sep 2017 06:38:22 GMT

"Hello, admin"

基於tokenFlask api成功!!!!

前端Vue.js

前端使用vue的全家桶,axios先後端通信,axios攔截器,localStorage保存token
所使用的框架和庫:

  • vue2.0
  • iview2.X
  • axios
  • vuex
  • vue-router

具體實現

main.js

// 初始化axios
axios.defaults.baseURL = 'http://127.0.0.1:5000'
axios.defaults.auth = {
    username: '',
    password: '',
}

// axios.interceptors.request.use((config) => {
//     console.log(config)
//     return config;
// }, (error) => {
//     return Promise.reject(error)
// })

// axios攔截器,401狀態時跳轉登陸頁並清除token
axios.interceptors.response.use((response) => {
    return response;
}, (error) => {
    if (error.response) {
        switch (error.response.status) {
            case 401:
                store.commit('del_token')
                router.push('/login')
        }
    }
    return Promise.reject(error.response.data)
})

// 路由跳轉
router.beforeEach((to, from, next) => {
    if (to.meta.required) {
        // 檢查localStorage
        if (localStorage.token) {
            store.commit('set_token', localStorage.token)
            // 添加axios頭部Authorized
            axios.defaults.auth = {
                username: store.state.token,
                password: store.state.token,
            }
            // iview的頁面加載條
            iView.LoadingBar.start();
            next()
        } else {
            next({
                path: '/login',
            })
        }
    } else {
        iView.LoadingBar.start();
        next()
    }
})

router.afterEach((to, from, next) => {
    iView.LoadingBar.finish();
})

路由

export default new Router({
    routes: [{
        path: '/',
        name: 'index',
        component: Index,
        meta: {
            required: true,
        }
    }, {
        path: '/login',
        name: 'login',
        component: Login,
    }]
})

路由添加meta字段,做爲須要認證路由的標誌

vuex

export default new Vuex.Store({
    state: {
        token: ''
    },
    mutations: {
        set_token(state, token) {
            state.token = token
            localStorage.token = token
        },
        del_token(state) {
            state.token = ''
            localStorage.removeItem('token')
        }
    }
})

vuex中保存token,同時修改刪除tokenlocalStorage.token

登陸和登出

登陸:

handleSubmit(name, form) {
    this.$refs[name].validate((valid) => {
        if (valid) {
            // 用戶名密碼簡單驗證後添加到axios的auth中
            this.$axios.defaults.auth = {
                username: form.username,
                password: form.password,
            }
            this.$axios.get('/api/login').then(response => {
                this.$Message.success("提交成功")
                let data = response.data
                // 保存token
                this.$store.commit('set_token', data)
                this.$router.push('/')
            }).catch(error => {
                this.$Message.error(error.status)
            })
        } else {
            this.$Message.error('表單驗證失敗!');
        }
    })
}

登出:

logout() {
    this.$store.commit('del_token')
    this.$router.push('/login')
}

刪除token並跳轉到登陸頁

flaskvuetoken認證就完成了!!!!

相關文章
相關標籤/搜索