Flask+vue的Token认证

Posted 木小撇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask+vue的Token认证相关的知识,希望对你有一定的参考价值。

        知乎上面有一个话题“就目前就业形势和今后发展 php和Python作为后台开发语言哪一个更合适?”作为一个Phper也会偶尔写写Python的人来说,我觉得php的优势在于快速开发。Php7+swoole已经适用于大部分公司的应用场景。而php的缺点在于离开了web以外啥都不能干。而Python则在很多领域都有一席之地。所谓胶水语言,就是库多库多,也正是这一点Python写web一旦遇到什么坑爹的需求很容易扩展,上github先找到库再说。比如celery很容易就扩展到定时任务和异步任务。比如whoosh很方便就能扩展到搜索引擎。更何况Python的语法设计写起来真的比Php爽太多。人生苦短,我用Python。不是浪得虚名的。

    python也有很多的web框架可用,比如重量级的Django。Django+xadmin后台几乎只需要配置一下文件就好了,几乎不用写什么后台代码。作为一个后台程序猿写前端真的是无比痛。比如轻量级的Flask,需要什么功能直接找扩展就好了。今天我就讲讲Flask的API认证。其实网上有很多关于Falsk的api认证的资料,但大多不尽详实,正好我最近也在看关于Flask+vue的API认证,索性就写篇文章。

    首先我们新建一个Flask的项目。

    Flask项目目录结构如下     

│  config.py

│  manage.py

│  README.md

│  requirements.txt

└─app

    │  models.py

    │  models.pyc

    │  __init__.py

    │

    ├─api

    │      authentication.py

    │      errors.py

    │      __init__.py

    │

    ├─auth

    │      forms.py

    │      views.py

    │      __init__.py

    │

    └─main

            views.py

            __init__.py


       需要的扩展如下

  • flask

  • flask-cors:flask跨域

  • flask-sqlachemy: flask数据库orm

  • flask-httpauth:flask的auth认证

  • itsdangerous


      关于网上的Flask的项目结构有很多,有兴趣的可以自己去搜索一下。我这里提供一个参考资料,https://spacewander.github.io/explore-flask-zh/7-blueprints.html

        我只有用的是狗书的项目结构,基本参考的Flasky这个开源项目的结构。有兴趣的可以去github上面搜一下。

        Vue项目用的webpack模板。src的文件目录如下 


│  App.vue

│  main.js

├─assets

│      logo.png

├─components

│  │  HelloWorld.vue

│  │

│  ├─Home

│  │      Home.vue

│  │

│  ├─Login

│  │      Login.vue

│  │      LoginForm.vue

│  │

│  └─Register

│          Register.vue

│          RegisterForm.vue

├─helpers

│      jwt.js

├─router

│      index.js

└─store

    │  actions.js

    │  index.js

    │  mutation-type.js

    │  mutations.js

    │

    └─modules

            login.js

            user.js


用到技术栈

           Vue 2.0

            vue-router

            vuex

            axios

            vue-material

            iview

具体实现

api模块初始化

api/__init__.py

# -*- coding: utf-8 -*-
from flask import Blueprint
mod = Blueprint('api', __name__)
from . import authentication, errors


项目初始化文件

app/__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config

lm = LoginManager()
lm.session_protection = 'strong'
lm.login_view = 'auth.login'
db = SQLAlchemy()


def register_blueprints(app):
   # Prevents circular imports
   from auth import mod as auth
from main import mod as main
from api import mod as api
app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(main)
app.register_blueprint(api, url_prefix='/api')


def create_app(config_name):
   app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
register_blueprints(app)
db.init_app(app)
lm.init_app(app)
return app


配置文件

config.py

# -*- coding: utf-8 -*-
import os
basedir = os.path.abspath(os.path.dirname(__file__))


class Config():
   """
   基类Config中包含通用配置,子类分别定义专用的配置。
   如果需要,还可以添加其他配置类。
   为了让配置方式更灵活且安全,某些配置可以从环境变量中导入,
   例如SECRET_KEY,但也提供一个默认值,以防环境中没有定义。
   """
   SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string i dont want to change'
   SQLALCHEMY_COMMIT_ON_TEARDOWN = True  # True时,每次请求结束后会自动提交数据库的变动
   SQLALCHEMY_TRACK_MODIFICATIONS = True
   MAIL_SERVER = 'smtp.163.com'
   MAIL_PORT = 25
   MAIL_USE_TLS = True
   MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
MAIL_SUBJECT_PREFIX = 'hello flask'
   MAIL_SENDER = os.environ.get('MAIL_SENDER')

@staticmethod
   def init_app(app):
       pass
class DevelopmentConfig(Config):
   DEBUG = True
   SQLALCHEMY_DATABASE_URI = "mysql://root:root@localhost/flask"

class TestingConfig(Config):
   TESTING = True
   SQLALCHEMY_DATABASE_URI = "mysql://root:root@localhost/test"

class ProductionConfig(Config):
   SQLALCHEMY_DATABASE_URI = "mysql://root:root@localhost/test"

# 在这里config字典中注册了不同的配置环境,而且还注册了一个默认配置。
config = {
'development': DevelopmentConfig,
   'testing': TestingConfig,
   'production': ProductionConfig,
   'default': DevelopmentConfig
}

models.py

from app import db
from datetime import datetime
from flask_login import UserMixin
from app import lm
from flask import current_app
from werkzeug.security import generate_password_hash,check_password_hash
from itsdangerous import (TimedJSONWebSignatureSerializer
as Serializer, BadSignature, SignatureExpired)
# http://www.jb51.net/article/86606.htm


class User(db.Model,UserMixin):
   __tablename__ = 'user'
   id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True)
password_hash = db.Column(db.String(64), index=True)
nickname = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
phone = db.Column(db.String(32), index=True)
add_time = db.Column(db.DateTime, default=datetime.now())
update_time = db.Column(db.DateTime, default=datetime.now())

@property
   def password(self):
       raise AttributeError('password is not readable attribute')

#相当于php里面__set()
@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)

# 生成token
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'])

# 后台认证
@lm.user_loader
   def load_user(user_id):
       return User.query.get(int(user_id))

def __repr__(self):
       return '<User %r>' % (self.username)

   

认证文件

# -*- coding: utf-8 -*-
from flask import g, jsonify, make_response
from flask_httpauth import HTTPBasicAuth
from ..models import User
from . import mod
from .errors import unauthorized, forbidden
from flask_cors import CORS

auth = HTTPBasicAuth()

CORS(mod)

# 验证
@auth.verify_password
def verify_password(username_or_token, password):
   # first try to authenticate by token
   user = User.verify_auth_token(username_or_token)
if not user:
       # try to authenticate with username/password
       user = User.query.filter_by(username=username_or_token).first()
if not user or not user.verify_password(password):
           return False
   g.user = user
g.current_user = User.verify_auth_token(username_or_token)
return True


@auth.error_handler
def auth_error():
   return unauthorized('Invalid credentials')

# 获取参数
@mod.route('/resource/')
@auth.login_required
def get_resource():
   return jsonify({'data': 'Hello, %s!' % g.user.username})

# 认证
@mod.route('/token/')
@auth.login_required
def get_auth_token():
   token = g.user.generate_auth_token(600)
return jsonify({'token': token.decode('ascii'), 'duration': 600})

 

vue部分

mian.js

初始化

axios.defaults.baseURL = 'http://127.0.0.1:5000'

axios.defaults.auth = {
username: '',
 password: '',
}


axios拦截器

axios.interceptors.request.use(function (config) {
if (JwtToken.getToken()){

}
}, function (error) {
// 可根据返回自己写验证逻辑
return Promise.reject(error);
});

       

//路由跳转  每次都必须带上用户名密码或者 token
router.beforeEach((to, from, next) => {
if (to.meta.require) {
if (store.state.token || JwtToken.getToken()) {
axios.defaults.auth = {
username: JwtToken.getToken(),
       password: JwtToken.getToken(),
     }
return next()
} else {
return next({'name': 'login'})
}
}
next()
})

        

路由配置

let routes = [
{
path: '/home',
   name: 'home',
   component: Home,
   // 作为需要认证的标识
   meta:{require:true},
 },
 {
path: '/register',
   name: 'register',
   component: Register,
   meta:{}
},
 {
path: '/login',
   name: 'login',
   component: Login,
   meta:{}
},
]

    

Vuex

import Vue from 'vue'

import Vuex from 'vuex'

import mutations from './mutations'

import actions from './actions'

import Login from './modules/login'

import User from './modules/user'

Vue.use(Vuex)

const store = new Vuex.Store({
state: {

},
 mutations,
 actions,
 modules: {
Login,
   User
}
});

export default store;


首先我们查看一下user模块


import * as types from './../mutation-type'
export default {
state : {
//这里可以定义一个auth 用于判断是否登录
username:null,
   password:null,
 },
 mutations:{
[types.SET_USER_TOKEN](state,payload){
state.username = payload.token
     state.password = ''
   }
}
,
}


登录模块

import JwtToken from './../../helpers/jwt'
import * as types from './../mutation-type'

export default {
actions:{
login({commit,dispatch},tokenData){
JwtToken.setToken(tokenData.token)
commit({
type:types.SET_USER_TOKEN,
       token:tokenData.token
     })
}
}
}


login组件

<template>
 <div>
   <Form ref="loginForm" :model="loginForm" :rules="rule" :label-width="60">
     <FormItem label="用户名" prop="name">
       <Input v-model="loginForm.name" placeholder="请输入用户名"></Input>
     </FormItem>
     <FormItem label="密码" prop="passwd">
       <Input type="password" v-model="loginForm.passwd"></Input>
     </FormItem>
     <FormItem>
       <Button type="primary" @click="handleSubmit('loginForm')">提交</Button>
       <Button type="ghost" @click="handleReset('loginForm')" style="margin-left: 8px">重置</Button>
     </FormItem>
   </Form>
 </div>
</template>
<script>
 export default {
data () {
return {
loginForm: {
name:'',
         passwd: '',
       },
       rule: {
name: [
{ required: true, message: '用户名不能为空', trigger: 'blur' }
],
         passwd: [
{ required: true, message: '密码不能为空', trigger: 'blur' }
],
       }
}
},
   methods: {
handleSubmit (name) {
this.$refs[name].validate((valid) => {
if (valid) {
this.$axios.defaults.auth = {
username: this.loginForm.name,
             password: this.loginForm.passwd
           }

this.$axios.get('/api/token/').then(response => {
//this.$Message.success("提交成功")
             let data = response.data
             console.log(data)
// 保存token
             this.$store.dispatch('login', data)
this.$router.push('/home')
}).catch(error => {
this.$Message.error(error.status)
})

this.$Message.success('Success!');
         } else {
this.$Message.error('表单验证失败');
         }
})
},
     handleReset (name) {
this.$refs[name].resetFields();
     }
}
}
</script>


以上是关于Flask+vue的Token认证的主要内容,如果未能解决你的问题,请参考以下文章

flask token认证

Haytham个人博客开发日志 -- Flask+Vue基于token的登录状态与路由管理

Express实战 - 应用案例- realworld-API - 路由设计 - mongoose - 数据验证 - 密码加密 - 登录接口 - 身份认证 - token - 增删改查API(代码片段

Flask+Vue Api认证

vue-router token状态认证

Flask 学习-26.JWT(JSON Web Token)生成Token