VUE Flask登录的初探-JWT的探索

摘要:
上一次,我们简单地基于JWT实现了登录,并留下了一些问题,这是JWT固有的缺陷。添加按钮以修改密码。2.对于request.js,添加响应拦截器,并将令牌保存到cookie逻辑中,迁移到此处{titleMsg}}提交重置测试注销将密码更改为456importqsfrom'qs'importservicefrom'/Utils/request'exportdefault{name:'loginForm',data(){return{titleMsg:'欢迎来到标志世界',loginData:{username:'',password:''},方法:{loginForm{this.$refs[formName].validate(((有效)=˃{if(有效){service({url:'/login',方法:'post',数据:qs.stringfy(this.loginData)})。然后(response=˃{const{data}=response//Cookies.set('Authorization',data.data.token)alert('submit!!!

上回简单实现了基于JWT的登录,并且留下了一些问题,jwt天生的弊端。本次用某些逻辑解决jwt的弊端

先列举jwt可能遇到的问题:

1.注销问题,当客户端注销登录后,token在有效期内依然有效,实际上从服务端无法让token失效
2.修改密码,当用户修改了密码,按常规需要让前次token失效。
3.续签问题,jwt虽然有超时机制,但没有实现自动续签。

为了解决上述三个问题,我考虑用如下手段:

1.注销问题,我将对每个用户,维护一个sessionID,作为此用户本次会话的有效ID,当注销用户后,服务端将此sessionID 删除。于是服务端就能主动控制token的有效性

2.修改密码问题,我将对用户ID做一个替代ID,此ID生成逻辑是由用户真实ID和用户密码加密产生,由此当用户修改密码后,替代ID将自动被更新,由此,客户端的TokenID,无法在服务端查询到,于是失效。

3.续签问题,解决此事,只能不停刷新和下发token。

以上方案都扩大服务器资源开销。

下面是代码。

前端代码,

1.对login.vue代码做了微调。添加了修改密码的按钮

2.对request.js,添加了response拦截器,token的保存到cookie逻辑,迁移到此处
VUE Flask登录的初探-JWT的探索第1张

VUE Flask登录的初探-JWT的探索第2张VUE Flask登录的初探-JWT的探索第3张
<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" 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-item>
        <el-button @click="changePws()">修改密码为456</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import qs from 'qs'
import service from '../utils/request'
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.tips)
        })
        .catch(error => {
          console.log(error)
        })
    },
    logoutForm() {
      service({url: '/logout',method: 'get'})
        .then(response => {
          const { data } = response
          alert('logout!!!' +'
'+ data.data.tips)
        })
        .catch(error => {
          console.log(error)
        })
    },
    changePws() {
      service({url: '/changepws',method: 'get'})
        .then(response => {
          const { data } = response
          alert('changepws!!!' +'
'+ data.data)
        })
        .catch(error => {
          console.log(error)
        })
    },
  }
}
</script>
Login.vue
VUE Flask登录的初探-JWT的探索第4张VUE Flask登录的初探-JWT的探索第5张
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 => {
    const { data } = response    
    if (isJSON(data.data)) {
      let jsonObj = JSON.parse(JSON.stringify(data.data))
      if (Object.prototype.hasOwnProperty.call(jsonObj,"token")){
        console.log('update token')
        Cookies.set('Authorization',jsonObj.token)
      }
    } else {
      console.log('not')
    }
    return response
  },
  error => {
    console.log(error);
    return Promise.reject(error)
  }
)

function isJSON(str) {
  let jsonData = JSON.stringify(str)
  try {
    if (typeof JSON.parse(jsonData) == "object") {
      return true;
    } else {
      return false;
    }
  } 
  catch(e) {
    return false;
  }
}

export default service;
request.js

后端代码,改动还是蛮大,

1.login.py 将更多无关逻辑下发给其他py文件,保持login.py的纯粹性,只解决登录相关问题

2.user.py 用户操作的所有代码,均由此单元负责。

3.jwt_token.py 仅仅是将超时时间,改为入参。

VUE Flask登录的初探-JWT的探索第6张VUE Flask登录的初探-JWT的探索第7张
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 UserLogin,Operaters

login_page = Blueprint('login_page',__name__)

login_manager = LoginManager()
login_manager.login_view = None

@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 = UserLogin.verfiyToken(token)
  if payload != None:
    alternativeID = payload['data']['alternativeID']
    sessionID = payload['data']['sessionID']
    user = UserLogin.queryUser(alternativeID=alternativeID,sessionID=sessionID)
  else:
    user = None
  return user


@login_page.route('/first')
@login_required
def firstPage():
  returnData = {'code': 0, 'msg': 'success', 'data': {'token':current_user.token,'tips':'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 = UserLogin.queryUser(userName = username)
    if (user != None) and (user.verifyPassword(password)) :
      login_user(user)
      returnData = {'code': 0, 'msg': 'success', 'data': {'token':user.token}}
      return json.dumps(returnData),200
    else :
      returnData = {'code': 1, 'msg': 'failed', 'data': {'tips':'username or password is not correct'} }
      return json.dumps(returnData),200  

@login_page.route('/logout') 
@login_required
def logout():
  userName = current_user.userName
  alternativeID = current_user.alternativeID
  sessionID = current_user.sessionID
  UserLogin.dropSessionID(alternativeID,sessionID)
  logout_user()
  returnData = {'code': 0, 'msg': 'success', 'data': {'tips':'Bye ' + userName} }
  return json.dumps(returnData),200  


@login_page.route('/changepws') 
@login_required
def changePws():
  user = UserLogin.queryUser(userID = current_user.id)  
  user.changePws()
  returnData = {'code': 0, 'msg': 'success', 'data': {'tips':'password was changed'} }
  return json.dumps(returnData),200  
login.py
VUE Flask登录的初探-JWT的探索第8张VUE Flask登录的初探-JWT的探索第9张
import uuid
from flask_login import UserMixin
from werkzeug.security import check_password_hash,generate_password_hash
from jwt_token import genToken,verfiyToken
from datetime import datetime,timedelta

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

class UserLogin(UserMixin):
    def __init__(self,operater):
        self.id = operater.get("id")
        self.userName = operater.get("name")
        self.passwordHash = operater.get("password")
        self.alternativeID = operater.get("alternativeID")
        self.sessionID = None
        self.token = None
        self.oper = operater

        exp = datetime.utcnow() + timedelta(seconds=10)
        self.genSessionID(exp)
        self.genToken(exp)

        clearOvertimeSeesionID(operater)

    def genSessionID(self,exp,sessionID=None):
        if sessionID == None:
            self.sessionID = str(uuid.uuid4())            
            self.oper["sessionIDs"].append({'id':self.sessionID,'exp':exp})
        else:            
            self.sessionID = sessionID

    
    def verifyPassword(self,password):
        if self.passwordHash is None:
            return False
        return check_password_hash(self.passwordHash,password)

    def get_id(self):
        return self.id

    def get(user_id):
        if not user_id:
            return None
        for oper in Operaters:
            if str(oper.get('id')) == str(user_id) :
                return User(oper)
        return None

    def clearOvertimeSeesionID(operater):        
        for userSessionID in operater["sessionIDs"][::-1]:
            if userSessionID['exp'] < datetime.utcnow() :
                operater["sessionIDs"].remove(userSessionID)
    
    @staticmethod
    def queryUser(**kwargs):
        if 'userID' in kwargs:
            return UserLogin.queryUserByID( kwargs['userID'])
        elif 'userName' in kwargs:
            return UserLogin.queryUserByName(kwargs['userName'])
        elif ('alternativeID' in kwargs) and ('sessionID' in kwargs):
            return UserLogin.queryUserBySessionID(kwargs['alternativeID'],kwargs['sessionID'])
        else:
            return None
    

    @staticmethod
    def queryUserByID(userID):
        for oper in Operaters:
            if (oper.get('id') == userID) :
                user = UserLogin(oper)
                return user
        return None

    @staticmethod
    def queryUserByName(username):
        for oper in Operaters:
            if (oper.get('name') == username) :
                user = UserLogin(oper)
                return user
        return None

    @staticmethod
    def queryUserBySessionID(alternativeID,sessionID):
        exists = False
        for oper in Operaters:
            if (oper.get('alternativeID') == alternativeID) :
                sessionIDs = oper["sessionIDs"]
                exists = True
                break
        
        if exists:
            exists = False
            for userSessionID in sessionIDs:
                if (userSessionID['id'] == sessionID) :
                    exists = True
                    break

        if exists: 
            user = UserLogin(oper)
            return user
        else:
            return None

    @staticmethod
    def dropSessionID(alternativeID,sessionID):
        exists = False
        for oper in Operaters:
            if (oper.get('alternativeID') == alternativeID) :
                sessionIDs = oper["sessionIDs"]
                exists = True
                break
        
        if exists :
            exists = False
            for userSessionID in sessionIDs:
                if (userSessionID['id'] == sessionID) :
                    exists = True
                    break
        if exists :
            sessionIDs.remove(userSessionID)

    @staticmethod
    def verfiyToken(token):
        verfiyed = verfiyToken(token)
        if verfiyed :
            return True        

        for oper in Operaters:
            clearOvertimeSeesionID(oper)

        return verfiyed


    def changePws(self):
        for oper in Operaters:
            if (oper.get('id') == self.id) :              
                oper['password'] = generate_password_hash('456')
                oper['alternativeID'] = generate_password_hash('1'+generate_password_hash('456'))
                return true
        return False

    def genToken(self,exp):
        token = genToken(exp,{'alternativeID':self.alternativeID,'sessionID':self.sessionID})
        self.token = token
        return token

    
user.py
VUE Flask登录的初探-JWT的探索第10张VUE Flask登录的初探-JWT的探索第11张
import jwt
from jwt import PyJWTError
from datetime import datetime,timedelta

SECRECT_KEY = b'x92R!x8exc6x9cxb3x89#xa6x0cxcbxf6xcbxd7xbc'


def genToken(exp,data):
  payload = {
    'exp': exp,
    '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
jwt_token.py

题外话,学习python已经有几个月了,这个语言还是蛮有趣,相对来说,没有那么多约束,所以代码看起来比较随性。未来的日子,需要逐渐找出合适python的项目结构以及格式规范。

下一个话题,将步入数据库方面,我会把用户字典创建到数据库中,并用python+sql操作用户表。前端+后端+数据库,如此以来一套基本的项目就搭建完毕。

再有空就可以把缓存机制搭建起来。甚至是docker。现在程序员需要的知识量越来越繁杂,多而不精。这样真的好么?我没有答案。

免责声明:文章转载自《VUE Flask登录的初探-JWT的探索》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇bluetooth-蓝牙事件监听java8中新增编译参数parameters入门下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

egg.js的egg-jwt生成token做成中间件

做egg.js项目时想生成token,用egg-jwt插件最好了,但是不小心很容易踩到坑。主要主要几点: 1、把csrf关掉   config.security ={ csrf: { enable: false, ignoreJSON: true} }; 2、验证token写成中间件 3、前端交互时在headers传toke...

Delphi中Form的position属性与代码自定义窗体位置

通过Form的Position属性可设置窗体的初始位置,如选择DesktopCenter为桌面中心,ScreenCenter为屏幕中心,等等。 这个属性在很多时候简化了程序代码。 但是,如果设置了position为ScreenCenter和DesktopCenter等,在窗体oncreate和onshow事件中使用代码控制窗体的位置就会不成功了,无论设置S...

解决微信公众平台接口配置信息配置失败问题

填写好URL及TOKEN后,点“提交”时,总是提示“配置失败”或其他错误 确认URL指向的后台页面代码没有问题 确认TOKEN配置没有问题 这时请察看一下你的INDEX页面的编码格式,改成GB2312试试吧,也许会令你的问题迎刃而解。 谨以此文献给浮躁的自己。...

asp.net 页面跳转 隐藏参数显示

最开始跳转到html页面,想隐藏url显示,使用window.open 设置 location=no,地址栏没了, 但标题栏显示url和参数,无法解决, 修改为aspx页面跳转到aspx页面,使用form的post方式提交,设置form的action属性为目标页面,在新页面中接收传递参数,用frame嵌入待显示页面,这样就不会显示url 部署到服务器后,发...

php大文件上传(切片)方法

1、介绍enctype enctype属性规定发送到服务器之前应该如何对表单数据进行编码。 enctype作用是告知服务器请求正文的MIME类型(请求消息头content-type的作用一样) 1、1 enctype的取值有三种 值 描述 application/x-www-form-urlencoded 在发送前编码所有字符(默认) mult...

NodeJs接口token认证express框架passport实现方式Bearer认证

1.生成一个简单的express项目(命令:express passport-test),项目结构如下: 2.添加项目依赖: npm install passport --save npm install passport-http-bearer --save 3.在项目下新建 modules/auth.js 文件,内容如下 : var pp = req...