VUE+Flask登录的初探--前端(Vue+element+axios)+后端(Flask+FlaskLogin+JWT)

0.前端部分依然基于VueCLI (https://cli.vuejs.org/zh/

1.创建hello-login文件夹,然后再此文件夹内执行 vue create front-end ,一顿狂回车后,如下图所示:

 2.安装elementUI,axios,js-cookie,qs
  2.1  npm i element-ui -S  (https://element.eleme.cn/#/zh-CN/component/installation
  2.2  npm install --save axios vue-axios  (http://www.axios-js.com/zh-cn/docs/vue-axios.html)
  2.3  npm install js-cookie --save (https://www.npmjs.com/package/js-cookie)
  2.4  npm install qs --save (https://www.npmjs.com/package/qs)

3.打开main.js,把elementUI和axios加载。搞定这块代码,npm run serve,试试能否正常把项目跑起来。(这是一种编码方式,安装组件算是破坏性的动作,需要勤于测试。以免后期跪了)

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import axios from 'axios'
import VueAxios from 'vue-axios'

//Vue.config.productionTip = false
axios.defaults.withCredentials = true

Vue.use(VueAxios,axios)
Vue.use(ElementUI)

new Vue({
  render: h => h(App),
}).$mount('#app')
View Code

4.在components目录下 创建Login.vue文件

<template>
  <div class='login'>
    <h1>{{ titleMsg }}</h1>
    <el-form ref="loginForm" :model="loginData" label-width="100px">
      <el-form-item label="用户名" prop="username" :rules="[{required: true, message: '用户名不能为空'}]">
        <el-input ref="username" type="password" v-model="loginData.username" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="password" :rules="[{required: true, message: '密码不能为空'}]">
        <el-input type="password" v-model="loginData.password" autocomplete="off"></el-input>
      </el-form-item>      
      <el-form-item>
        <el-button type="primary" @click="submitForm('loginForm')">提交</el-button>
        <el-button @click="resetForm('loginForm')">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  name: 'loginForm',
  data() {
    return {
      titleMsg: '欢迎来到旗帜世界',
      loginData: {
        username: '',
        password: ''
      }
    }
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
         if (valid) {
           console.log('submit')
         } else {
          console.log('illegad submit!!');
          return false;
        }
      })
    },
    resetForm(formName) {
      this.$refs[formName].resetFields()
      console.log('reset')
    }
  }
}
</script>
View Code

5.打开App.vue,将helloworld相关代码注释,改写成Login。这种操作可以加深理解vue的组件机制。为后期学习使用router打基础

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <!-- <HelloWorld msg="Welcome to Your Vue.js App"/> -->
    <Login/>
  </div>
</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'
import Login from './components/Login.vue'

export default {
  name: 'App',
  components: {
    //HelloWorld
    Login
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
View Code

6.npm run serve 跑起来后如下图。

7.接下来,使用axios把此表单提交到py搭建的后台并返回消息。先转到py端,把后端代码实现一部分。在完成这个功能吧。

--------------------------------------------------------------------------------------------------------------------------------------------------------------

0.后端实现,使用flask(https://dormousehole.readthedocs.io/en/latest/

1.在hello-login文件夹下创建 back-end文件夹,并运行命令行 py -m venv venv  这时就会在当前目录下创建venv虚拟机 (https://dormousehole.readthedocs.io/en/latest/installation.html#id4

2.执行venvscriptsactivate 这样就启动了虚拟机环境 

3.开始安装flask, flask-login, flask-cors, jwt 。注意都要在venv虚拟机环境下安装
  3.1 pip install Flask (https://dormousehole.readthedocs.io/en/latest/installation.html#flask
  3.2 pip install flask-login  (https://flask-login.readthedocs.io/en/latest/)
  3.3 pip install pyjwt  (https://pypi.org/project/PyJWT/)
  3.4 pip install -U flask-cors  (https://flask-cors.readthedocs.io/en/latest/)

4.在当前目录下创建app.py文件 敲入代码:

from flask import Flask
import json

app = Flask(__name__)
app.secret_key =b'x15fx07xd3xd9xbf*x82xd1xe6xb4xf2x95xddx8fx12'
#命令行中运行后拷贝出随机值  python -c "import os; print(os.urandom(16))"

@app.route('/hello')
def helloworld():
  returnData = {'code': 0, 'msg': 'success', 'data': 'hello world' }
  return json.dumps(returnData),200

if __name__ == '__main__':
  app.run(debug = True)
View Code

5.在venv虚拟机下 运行py app.py 然后再浏览器中查看 http://localhost:5000/hello  。这说明基本框架已经构建成功。
之所以返回如下格式,是参考了这篇博文(https://sobird.me/http-json-api-guide.htm),原始出处并未找到,

6.创建用户单元.user.py。实现了flask_login (https://flask-login.readthedocs.io/en/latest/index.html#your-user-class)所提及的功能。以及用USERS字典暂时代替未来的数据库表。

from flask_login import UserMixin
from werkzeug.security import check_password_hash,generate_password_hash


USERS = [
    {
        "id":1,
        "name":"admin",
        "password":generate_password_hash('123')
    },
    {
        "id":2,
        "name":"李四",
        "password":generate_password_hash('123')
    },    
]


 
class User(UserMixin):
    def __init__(self,user):
        self.username = user.get("name")
        self.password_hash = user.get("password")
        self.id = user.get("id")


    @staticmethod
    def queryUser(username):
        for user in USERS:
            if (user.get('name') == username) :
                return User(user)
        return None
    
    def verifyPassword(self,password):
        if self.password_hash is None:
            return False
        return check_password_hash(self.password_hash,password)

    def get_id(self):
        return self.id

    def get(user_id):
        if not user_id:
            return None
        for user in USERS:
            if str(user.get('id')) == str(user_id) :
                return User(user)
        print('None')
        return None
View Code

7.创建jwt操作单元jwt_token.py 。实现了对jwt的简单二次封装。其实不做封装也可以
  7.1 JWT中 “Registered claims” 包含 iss(发行者),exp(到期时间),sub(主题),aud(受众)是官方建议携带的。我偷懒只采用了到期时间这一个声明。
  7.2 jwt可以参考官网(https://jwt.io/)介于国内强大的长城。此官网偶发型能打开。   亦可参考此博文 https://www.cnblogs.com/mantoudev/p/8994341.html

from jwt import jwt,PyJWTError
from datetime import datetime,timedelta

SECRECT_KEY = b'x92R!x8exc6x9cxb3x89#xa6x0cxcbxf6xcbxd7xbc'


def genToken(data):
  expInt = datetime.utcnow() + timedelta(seconds=3)
  payload = {
    'exp': expInt,
    'data': data 
    }
  token = jwt.encode(payload,key= SECRECT_KEY,algorithm= 'HS256')
  return bytes.decode(token)

def verfiyToken(tokenStr):
  try:
    tokenBytes =  tokenStr.encode('utf-8')
    payload = jwt.decode(tokenBytes,key= SECRECT_KEY,algorithm= 'HS256')
    return payload
  except PyJWTError as e:
    print("jwt验证失败: %s" % e)
    return None
View Code

8.创建登录逻辑单元 login.py 。同前端的主要交互逻辑都在此处。flask_login的具体实现(https://flask-login.readthedocs.io/en/latest/index.html#installation)

import time
import json
from flask import Blueprint,request
from flask_login import LoginManager,login_user,logout_user,login_required,current_user
from user import User,USERS
from jwt_token import genToken,verfiyToken

login_page = Blueprint('login_page',__name__)

login_manager = LoginManager()
login_manager.login_view = 'helloworld'

@login_page.record_once
def on_load(state):
  login_manager.init_app(state.app)

# @login_manager.user_loader
# def load_user(user_id):
#   return User.get(user_id)

@login_manager.request_loader
def load_user_from_request(request):
  token = request.headers.get('Authorization')
  if token == None:
    return None

  payload = verfiyToken(token)
  if payload != None:
    user = User.queryUser(payload['data']['username'])
  else:
    user = None
  return user


@login_page.route('/first')
@login_required
def firstPage():
  returnData = {'code': 0, 'msg': 'success', 'data': 'First Blood(来自' + current_user.username +')' }
  return returnData,200

@login_page.route('/login', methods=['POST'])
def login():
  if request.method == 'POST':
    username = request.form['username']
    password = request.form['password']
    user = User.queryUser(username)
    if (user != None) and (user.verifyPassword(password)) :
      login_user(user)
      token = genToken({'username':username,'password':'******'})
      returnData = {'code': 0, 'msg': 'success', 'data': {'token':token} }
      return json.dumps(returnData),200
    else :
      returnData = {'code': 1, 'msg': 'success', 'data': 'username or password is not correct' }
      return json.dumps(returnData),200  

@login_page.route('/logout') 
@login_required
def logout():
  username = current_user.username
  logout_user()
  returnData = {'code': 0, 'msg': 'success', 'data': ' Bye ' + username }
  return json.dumps(returnData),200  
View Code

9.执行py app.py 后,在postman 分别测试如下链接(注意图中红框内容。):
  9.1 http://127.0.0.1:5000/hello
  9.2 http://127.0.0.1:5000/login
  9.3 http://127.0.0.1:5000/first
  9.4 http://127.0.0.1:5000/logout 

 

 

 10.后端代码初步结束。下一阶段,连接前后端

--------------------------------------------------------------------------------------------------------------------------------------------------

1.打开front-end项目,用axios把后端接口调用起来
2.在项目src目录下 创建文件夹 utils 然后在其内创建文件request.js。这里对axios做了简单封装。

import axios from 'axios'
import Cookies from 'js-cookie'


/****** 创建axios实例 ******/
const service = axios.create({
  baseURL: 'http://localhost:5000',  // api的base_url
  timeout: 5000  // 请求超时时间
})

service.interceptors.request.use(
  config => {
    config.headers['Authorization'] = Cookies.get('Authorization')
    return config
  },
  error => {
    console.log(error)
    return Promise.reject(error)
  }
)

/****** respone拦截器==>对响应做处理 ******/
// service.interceptors.response.use(
//   response => {
//     console.log(response)
//     //这里根据后端提供的数据进行对应的处理
//     if (response.data.result === 'TRUE') {
//         return response.data;
//     }
//   },
//   error => {
//     console.log(error);
//     return Promise.reject(error)
//   }
// )

export default service;
request.js
<template>
  <div class='login'>
    <h1>{{ titleMsg }}</h1>
    <el-form ref="loginForm" :model="loginData" label-width="100px">
      <el-form-item label="用户名" prop="username" :rules="[{required: true, message: '用户名不能为空'}]">
        <el-input ref="username" type="password" v-model="loginData.username" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="password" :rules="[{required: true, message: '密码不能为空'}]">
        <el-input type="password" v-model="loginData.password" autocomplete="off"></el-input>
      </el-form-item>      
      <el-form-item>
        <el-button type="primary" @click="loginForm('loginForm')">提交</el-button>
        <el-button @click="resetForm('loginForm')">重置</el-button>
      </el-form-item>
      <el-form-item>
        <el-button @click="testForm()">测试</el-button>
        <el-button @click="logoutForm()">登出</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import qs from 'qs'
import service from '../utils/request'
import Cookies from 'js-cookie'
export default {
  name: 'loginForm',
  data() {
    return {
      titleMsg: '欢迎来到旗帜世界',
      loginData: {
        username: '',
        password: ''
      }
    }
  },
  methods: {
    loginForm(formName) {
      this.$refs[formName].validate((valid) => {
         if (valid) {
           service({url: '/login',method: 'post',data: qs.stringify(this.loginData)})
             .then(response => {
               const { data } = response
               Cookies.set('Authorization',data.data.token)
               alert('submit!!!' +'
'+ data.msg)
             })
             .catch(error => {
               console.log(error)
             })
         } else {
           console.log('illegad submit!!');
           return false;
        }
      })
    },
    testForm() {
      service({url: '/first',method: 'get'})
        .then(response => {
          const { data } = response
          alert('firstPage!!!' +'
'+ data.data)
        })
        .catch(error => {
          console.log(error)
        })
    },
    logoutForm() {
      service({url: '/logout',method: 'get'})
        .then(response => {
          const { data } = response
          alert('logout!!!' +'
'+ data.data)
        })
        .catch(error => {
          console.log(error)
        })
    }
  }
}
</script>
Login.vue

3.npm run serve 后,测试。四个按钮

 4.其中关注一个状况,当登出后,再次点击测试。测试依然返回成功。这就出现一个问题,登出功能无效,回看后端代码logout是正常运作。
稍加分析,即可得出产生这种情况的原因是jwt Token本身的弊端。前文的连接中已经提醒。如何解决此状况,日后再分析

5.收工了。

原文地址:https://www.cnblogs.com/yaoshi641/p/13331029.html