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.執行venv\scripts\activate 這樣就啟動了虛擬機環境 

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'\x15f\x07\xd3\xd9\xbf*\x82\xd1\xe6\xb4\xf2\x95\xdd\x8f\x12'
#命令行中運行后拷貝出隨機值  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!\x8e\xc6\x9c\xb3\x89#\xa6\x0c\xcb\xf6\xcb\xd7\xbc'


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!!!' +'\n'+ 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!!!' +'\n'+ data.data)
        })
        .catch(error => {
          console.log(error)
        })
    },
    logoutForm() {
      service({url: '/logout',method: 'get'})
        .then(response => {
          const { data } = response
          alert('logout!!!' +'\n'+ data.data)
        })
        .catch(error => {
          console.log(error)
        })
    }
  }
}
</script>
Login.vue

3.npm run serve 后,測試。四個按鈕

 

 4.其中關注一個狀況,當登出后,再次點擊測試。測試依然返回成功。這就出現一個問題,登出功能無效,回看后端代碼logout是正常運作。
稍加分析,即可得出產生這種情況的原因是jwt Token本身的弊端。前文的連接中已經提醒。如何解決此狀況,日后再分析

5.收工了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM