flask和vue搭建前后分离项目
项目目录搭建示意图
web是js前端代码项目 api是后台接口项目
后端项目的搭建可以用任何web框架 flask或者django都可以 和平常用flask和django开发不同的是 每个url对应的view处理函数不要返回模板内容
即不再调用render等其它渲染函数 全部使用 return json_response({'data': users, 'total': total})返回数据到前端即可
前端调用后端接口的方式如下:
this.$http.get('/api/account/roles/').then(res => {
this.tableData = res.result
}, res => this.$layer_message(res.result)).finally(() => this.tableLoading = false)
通过这种方式实现前后端数据的交互和展示
前端实现权限管理控制
1.在登录系统的时候把用户的权限集合获取到并且存储到localstorage中
2.每个权限用一个唯一的字符串来标识即可
3.把用户权限判断函数挂载到全局Vue的原型上面

// 权限判断 Vue.prototype.has_permission = function (str_code) { if (localStorage.getItem('is_supper') === 'true') { return true } let permissions = localStorage.getItem('permissions'); if (!str_code || !permissions) return false; permissions = permissions.split(','); for (let or_item of str_code.split('|')) { if (isSubArray(permissions, or_item.split('&'))) { return true } } return false }; 在main.js中导入 import GlobalTools from './plugins/globalTools' Vue.use(GlobalTools, router);

<el-table-column label="操作" width="240px" v-if="has_permission('assets_host_edit|assets_host_del|assets_host_valid')"> <template slot-scope="scope"> <el-button v-if="has_permission('assets_host_edit')" size="small" @click="handleEdit(scope.row)">编辑</el-button> <el-button v-if="has_permission('assets_host_valid')" size="small" type="primary" @click="valid(scope.row)" :loading="btnValidLoading[scope.row.id]">验证 </el-button> <el-button v-if="has_permission('assets_host_del')" size="small" type="danger" @click="deleteCommit(scope.row)" :loading="btnDelLoading[scope.row.id]">删除 </el-button> </template> </el-table-column>

router是全局的router对象 // 路由导航钩子 router.beforeEach((to, from, next) => { if (['/', '/login', '/deny','/account/person','/account/personset','/home','/welcome'].includes(to.path)) { next() } else if (to.meta.hasOwnProperty('permission') && Vue.prototype.has_permission(to.meta.permission)) { next() } else { next({path: '/deny'}) } })
Vue.use(GlobalTools, router); //把router对象传递到GlobalTools模块中
后端实现权限管理

class User(db.Model, ModelMixin): __tablename__ = 'account_users' @property def permissions(self): if self.is_supper: return set() return Role.get_permissions(self.role_id) class Role(db.Model, ModelMixin): __tablename__ = 'account_roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False) desc = db.Column(db.String(255)) @staticmethod def get_permissions(role_id): sql = text('select p.name from account_role_permission_rel r, account_permissions p where r.role_id=%d and r.permission_id=p.id' % role_id) result = db.engine.execute(sql) return {x[0] for x in result}
用户登录成功时候返回数据处理

if user.is_active: if user.verify_password(form.password): login_limit.pop(form.username, None) token = uuid.uuid4().hex user.access_token = token user.token_expired = time.time() + 8 * 60 * 60 user.save() return json_response({ 'token': token, 'is_supper': user.is_supper, 'nickname': user.nickname, 'permissions': list(user.permissions) })
后端接口调用鉴权设计和实现
1.通过中间件把登录用户信息存储到全局对象中

# coding=utf-8 from flask import request, make_response, g from libs.tools import json_response from apps.account.models import User from public import app import time import flask_excel as excel def init_app(app): excel.init_excel(app) app.before_request(cross_domain_access_before) app.before_request(auth_middleware) app.after_request(cross_domain_access_after) app.register_error_handler(Exception, exception_handler) app.register_error_handler(404, page_not_found) def auth_middleware(): if request.path == '/account/users/login/' or request.path.startswith('/apis/configs/') \ or request.path.startswith('/apis/files/'): return None token = request.headers.get('X-TOKEN') if token and len(token) == 32: g.user = User.query.filter_by(access_token=token).first() if g.user and g.user.is_active and g.user.token_expired >= time.time(): g.user.token_expired = time.time() + 8 * 60 * 60 g.user.save() return None return json_response(message='Auth fail, please login'), 401
2.创建一个装饰器模块用来装饰所有需要鉴权的view处理函数

from flask import g from public import app from functools import wraps from libs.tools import json_response # Flask中的g对象是个很好的东西,主要用于在一个请求的过程中共享数据。 # 可以随意给g对象添加属性来保存数据,非常的方便 # # def require_permission(str_code): def decorate(func): @wraps(func) def wrapper(*args, **kwargs): if not g.user.is_supper: or_list = [x.strip() for x in str_code.split('|')] for or_item in or_list: and_set = {x.strip() for x in or_item.split('&')} if and_set.issubset(g.user.permissions): break else: return json_response(message='Permission denied'), 403 return func(*args, **kwargs) return wrapper return decorate
3.在view函数中调用装饰器

@blueprint.route('/', methods=['GET']) @require_permission('assets_host_view | publish_app_publish_host_select | ' 'job_task_add | job_task_edit | assets_host_exec') def get(): form, error = JsonParser(Argument('page', type=int, default=1, required=False), Argument('pagesize', type=int, default=10, required=False), Argument('host_query', type=dict, required=False), ).parse(request.args) if error is None: host_data = Host.query if form.page == -1: return json_response({'data': [x.to_json() for x in host_data.all()], 'total': -1}) if form.host_query.get('name_field'): host_data = host_data.filter(Host.name.like('%{}%'.format(form.host_query['name_field']))) if form.host_query.get('zone_field'): host_data = host_data.filter_by(zone=form.host_query['zone_field']) result = host_data.limit(form.pagesize).offset((form.page - 1) * form.pagesize).all() return json_response({'data': [x.to_json() for x in result], 'total': host_data.count()}) return json_response(message=error)
Token的实现和具体用途
token是一个由服务端生成并返回给客户端的随机编码字符串 并且客户端每次向服务端发起请求的时候在请求头中必须包含token 具体用途如下:
1.用来判断用户是否登录并且查看登录是否过期 类似于session
2.用来对客户端请求服务端的第一道检查 如果没有token 所有的接口统一返回未登录
3.token验证不能对登录后的用户实现具体的权限验证
token的存储设计 后台存储到用户信息表中

class User(db.Model, ModelMixin): __tablename__ = 'account_users' id = db.Column(db.Integer, primary_key=True) role_id = db.Column(db.Integer, db.ForeignKey('account_roles.id')) username = db.Column(db.String(50), unique=True, nullable=False) nickname = db.Column(db.String(50)) password_hash = db.Column(db.String(100), nullable=False) email = db.Column(db.String(120)) mobile = db.Column(db.String(30)) is_supper = db.Column(db.Boolean, default=False) is_active = db.Column(db.Boolean, default=True) access_token = db.Column(db.String(32)) token_expired = db.Column(db.Integer) role = db.relationship('Role')
token的后端生成 在用户登录成功的时候

@blueprint.route('/login/', methods=['POST']) def login(): form, error = JsonParser('username', 'password').parse() if error is None: user = User.query.filter_by(username=form.username).first() if user: if user.is_active: if user.verify_password(form.password): login_limit.pop(form.username, None) token = uuid.uuid4().hex user.access_token = token user.token_expired = time.time() + 8 * 60 * 60 user.save() return json_response({ 'token': token, 'is_supper': user.is_supper, 'nickname': user.nickname, 'permissions': list(user.permissions) }) else: login_limit[form.username] += 1 if login_limit[form.username] >= 3: user.update(is_active=False) return json_response(message='用户名或密码错误,连续3次错误将会被禁用') else: return json_response(message='用户已被禁用,请联系管理员') elif login_limit[form.username] >= 3: return json_response(message='用户已被禁用,请联系管理员') else: login_limit[form.username] += 1 return json_response(message='用户名或密码错误,连续3次错误将会被禁用') else: return json_response(message='请输入用户名和密码')
token截验证每个发送请求的客户端是否合法 通过中间件实现

def auth_middleware(): if request.path == '/account/users/login/' or request.path.startswith('/apis/configs/') \ or request.path.startswith('/apis/files/'): return None token = request.headers.get('X-TOKEN') if token and len(token) == 32: g.user = User.query.filter_by(access_token=token).first() if g.user and g.user.is_active and g.user.token_expired >= time.time(): g.user.token_expired = time.time() + 8 * 60 * 60 g.user.save() return None return json_response(message='Auth fail, please login'), 401
token的前端存储 存储到cookie或者localstorage中

handleSubmit() { this.error = ''; this.$refs['form'].validate(pass => { if (!pass) { return false } this.loading = true; this.$http.post('/api/account/users/login/', this.form).then(res => { localStorage.setItem('token', res.result['token']); localStorage.setItem('is_supper', res.result['is_supper']); localStorage.setItem('permissions', res.result['permissions']); localStorage.setItem('nickname', res.result['nickname']); this.$router.push('/welcome'); }, response => { this.error = response.result }).finally(() => this.loading = false) }) } }
token被集成到请求头中进行发送

// 请求拦截器 axios.interceptors.request.use(request => { if (request.url.startsWith('/api/')) { request.headers['X-TOKEN'] = localStorage.getItem('token'); // request.url = config.apiServer + request.url.replace('/api', '') // request.url = config.apiServer + request.url } request.timeout = envs.request_timeout; return request; }); // 返回拦截器 axios.interceptors.response.use(response => { return handleResponse(response, router) }, error => { if (error.response) { return handleResponse(error.response, router) } return Promise.reject({result: '请求异常: ' + error.message}) });
token无效示例
用户无权限示例
正常用户访问示例
本文来自博客园,作者:不懂123,转载请注明原文链接:https://www.cnblogs.com/yxh168/articles/10843494.html