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认证的主要内容,如果未能解决你的问题,请参考以下文章
Haytham个人博客开发日志 -- Flask+Vue基于token的登录状态与路由管理
Express实战 - 应用案例- realworld-API - 路由设计 - mongoose - 数据验证 - 密码加密 - 登录接口 - 身份认证 - token - 增删改查API(代码片段