【九】fastapi+vue实现token验证登录

整体思路

fastapi使用的jwt做的验证: https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/
在这里插入图片描述

进入首页时,调用了query查询用户接口,查询用户接口,在user.py中Depends(get_current_user)需要有token,否则就会返回401,而index.js就会拦截错误,报登录过期,并返回跳转到登录页面,然后账号密码登录后,调用登录接口会生成返回token,login.vue中localStorage.setItem()将token写在本地,然后跳首页,首页会请求查询用户,这时有token,token_verify.py中的jwt会去解码验证用户,得到用户,然后查询到用户并返回,接口会根据用户的权限返回对应的值。退出登录直接清楚token

fastapi

token_verify.py 三个方法,

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from core.dbs import get_dbs

# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "e41e978535549c7cd2281e794ff83829d75e36d45781bc837cbece8bafaab217"
ALGORITHM = "HS256"


pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# 验证明文和密文
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
    return pwd_context.hash(password)

# 验证账号、密码、等级是否一致,一致查询到用户返回
def authenticate_user(username, password, level):
    dbs = get_dbs()
    query_user_sql = f'''select id, username, password, phone, email, level from user_info where del_flag=0 and `username` = "{username}"
    and `level` = {level}'''
    dbs[0].execute(query_user_sql)
    users = dbs[0].fetchall()
    dbs[0].close()
    dbs[1].close()
    if len(users) == 0:
        return None
    user = users[0]
    hashed_password = user["password"]
    if verify_password(password, hashed_password):
        return users
    else:
        return None

# 根据请求来的token jwt解码后验证是否存在该用户,存在就返回,不存在返回401
async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("username")
        password: str = payload.get("password")
        level: str = payload.get("level")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = authenticate_user(username, password, level)
    if user is None:
        raise credentials_exception
    return user


user.py

from fastapi import APIRouter, Depends
from pymysql import cursors
from core.dbs import get_db
from core.token_verify import SECRET_KEY, ALGORITHM, get_password_hash, get_current_user, authenticate_user
from models.userinfo import Users, UpdateUsers, Token, LoginUser
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from datetime import datetime, timedelta, timezone
from typing import Annotated
from jose import JWTError, jwt
from typing import Union
import time

User = APIRouter(tags=["用户"], prefix="/user")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


@User.post("/add", summary="添加用户", status_code=200)
async def add_user(adduser: Users, db: cursors = Depends(get_db)):
    adduser_dict = adduser.dict()
    username = adduser_dict["username"]
    query_user_sql = f'''select id, username, password, phone, email, level from user_info where del_flag=0 and `username` = "{username}"'''
    db.execute(query_user_sql)
    users = db.fetchall()
    if len(users) > 0:
        return {"message": "用户名已存在"}
    password = get_password_hash(adduser_dict["password"])
    email = adduser_dict["email"]
    phone = adduser_dict["phone"]
    INSERT_USER_SQL = f"INSERT INTO `user_info`(`username`, `password`, `phone`, `email`) VALUES ('{username}', '{password}', '{phone}', '{email}');"
    db.execute(INSERT_USER_SQL)
    return {"message": "添加成功"}


@User.post("/login", summary="用户登录", status_code=200)
async def login_user(loguser: LoginUser):
    loguser_dict = loguser.dict()
    username = loguser_dict["username"]
    password = loguser_dict["password"]
    level = loguser_dict["level"]
    users = authenticate_user(username, password, level)
    # 登录成功返回token
    if users:
        expire = datetime.now(timezone.utc) + timedelta(minutes=60)
        loguser_dict.update({"exp": expire})
        access_token = jwt.encode(loguser_dict, SECRET_KEY, algorithm=ALGORITHM)
        return {"message": "登录成功", "Authorization": Token(access_token=access_token, token_type="bearer")}
    else:
        return {"message": "用户或密码错误"}


@User.get("/query", summary="查询用户", status_code=200)
async def query_user(uid: int | None = None, uname: str | None = None, db: cursors = Depends(get_db), current_user: User = Depends(get_current_user)):
    uuser = current_user[0]
    if uuser["level"] == -1:
        query_user_sql = '''select id, username, password, phone, email, level from user_info where del_flag=0 '''
        if uid is not None:
            query_user_sql += f'''and `id` = "{uid}"'''
        if uname is not None:
            query_user_sql += f'''and `username` = "{uname}"'''
        db.execute(query_user_sql)
    elif uuser["level"] == 9:
        return {"data": uuser}
    else:
        return {"message": "token过期,请重新登陆"}

vue

index.js

import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from "@/router/route.js";

const instance = axios.create({
    baseURL: 'http://localhost:5001/api/v1',
    timeout: 100000
})

function genOpts() {
    let headers = {}
    if (localStorage.Authorization) {
        headers = {
            'Authorization': localStorage.Authorization
        }
    }
    return {
        method: 'get',
        url: '',
        data: null,
        params: null,
        headers
    }
}

// 添加请求拦截器
instance.interceptors.request.use(function (config) {
    // let baseURL = isInWhiteList(config.url)
    // if (baseURL) {
    //     config.baseURL = baseURL
    // }
    return config;
}, function (error) {
    return Promise.reject(error);
});

const clearSystemInfo = (msg) => {
    ElMessage.error({
        message: msg,
        grouping: true
    });
    localStorage.clear()
    setTimeout(() => {
        window.location.reload()
    }, 1500);
}

const err = error => {
    if (error.response) {
        const data = error.response.data;
        if (error.response.status === 401) {
            clearSystemInfo("登录过期")
            router.push({ name: 'login' })
            return Promise.reject(data)
        }
    }
    return Promise.reject(error);
};

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
    // 调用白名单
    // setTimeout(() => {
    //     getWhiteList()
    // }, 100);
    if (response.data.result == 506) {
        clearSystemInfo(response.data.msg)
        return Promise.reject(response.data)
    }
    if (response.data.result == 0) {
        ElMessage.error({
            message: response.data.msg,
            grouping: true
        });
        return Promise.reject(response.data)
    }
    return response;
}, err);

export async function request(options) {
    var opts = {
        ...genOpts(),
        ...options,
    };
    let res = await instance(opts)
    if (res && res.status == 200) {
        return res.data
    } else {
        return Promise.reject(res)
    }
}

login.vue

<template>
    <el-form ref="loginFormRef" :model="loginForm" :rules="rules" label-position="top" label-width="70px">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="loginForm.username" ></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input v-model="loginForm.password"></el-input>
      </el-form-item>
    </el-form>
    <el-button type="primary" @click="logins(loginFormRef)">登录</el-button>
    <el-button type="primary" @click="regist">注册</el-button>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { useRoute, useRouter } from "vue-router";
import {ElNotification, FormInstance, FormRules} from 'element-plus'
import { login } from "../apis/apis";
import { ElMessage } from "element-plus";

const route = useRoute()
const router = useRouter()

interface loginForm {
  username: string,
  password: string,
  level: number
}
// 表单校验规则
const loginFormRef = ref<FormInstance>()
// 赋值
const loginForm = reactive<loginForm>({
  username: '',
  password: '',
  level: 9
})
// 规则
const rules = reactive<FormRules>({
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 1, max: 20, message: '字符长度1-20之间', trigger: 'blur' },
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 8, max: 20, message: '字符长度8-20之间', trigger: 'blur' },
  ]
})

 // 提交登录数据
const logins = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      login(loginForm).then(res => {
        const mss = res.message
        if (mss === "登录成功" ) {
          localStorage.setItem('Authorization', "Bearer " + res.Authorization.access_token)
          ElMessage({ message: res.message,
            type: 'success', })
          // 清空表单
          loginForm.username = ''
          loginForm.password = ''
          router.push({ name: 'index'})
        }
        else if (mss === "用户或密码错误") {
          ElMessage({ message: res.message,
            type: 'error', })
        }
      })
    }
  })
}

const regist = () => {
  router.push({ name: 'regist' })
}

</script>

<style scoped>

</style>

index.vue

<template>
    <div id="bg">
      <div id="container">
        <h1>个人信息</h1>
      <p><span>姓名:</span>{{ tableData.username }}</p>
      <p><span>邮箱:</span>{{ tableData.email }}</p>
      <button @click.prevent="logout">退出</button>
      </div>
    </div>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { useRoute, useRouter } from "vue-router";
import {login, queryUser} from "../apis/apis.js";
import { ElMessage } from "element-plus";

const route = useRoute()
const router = useRouter()

const tableData = ref([])

queryUser({'uid': null}).then(res => {
  tableData.value = res.data
})

const logout = () => {
  localStorage.clear()
  router.push({ name: 'login' })
}

</script>



<style scoped>
</style>

register.vue

<template>
    <el-form ref="registFormRef" :model="registForm" :rules="rules" label-position="top" label-width="70px">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="registForm.username" ></el-input>
        {{ msss }}
      </el-form-item>
      <el-form-item label="密码" prop="password">
        {{ mssss }}
        <el-input v-model="registForm.password"></el-input>
      </el-form-item>
      <el-form-item label="手机号码" class="el-form-item">
        <el-input v-model="registForm.phone"></el-input>
      </el-form-item>
      <el-form-item label="邮箱" class="el-form-item">
        <el-input v-model="registForm.email"></el-input>
      </el-form-item>
    </el-form>
    <el-button type="primary" @click="submitRegist(registFormRef)">注册</el-button>
    <el-button type="primary" @click="login">登录</el-button>
</template>


<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { useRoute, useRouter } from "vue-router";
import { addUser } from "../apis/apis.js";
import { ElMessage } from "element-plus";
const route = useRoute()
const router = useRouter()

interface registForm {
  username: string,
  password: string,
  phone: string,
  email: string
}
// 表单校验规则
const registFormRef = ref<FormInstance>()
// 赋值
const registForm = reactive<registForm>({
  username: '',
  password: '',
  phone: '',
  email: ''
})
// 规则
const rules = reactive<FormRules>({
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 1, max: 20, message: '字符长度1-20之间', trigger: 'blur' },
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 8, max: 20, message: '字符长度8-20之间', trigger: 'blur' },
  ],
  phone: [
    { required: true, message: '请输入手机号码', trigger: 'blur' },
    { min: 11, max: 12, message: '字符长度11-12之间', trigger: 'blur' },
  ],
  email: [
    { required: true, message: '请输入邮箱', trigger: 'blur' },
    { min: 1, max: 100, message: '字符长度1-100之间', trigger: 'blur' },
  ]
})


 // 提交注册数据
const submitRegist = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      addUser(registForm).then(res => {
        const mss = res.message
        if (mss === "添加成功") {
          ElMessage({
            message: res.message,
            type: 'success',
          })
          // 清空表单
          registForm.username = ''
          registForm.password = ''
          registForm.phone = ''
          registForm.email = ''
          router.push({ name: 'login'})
        }
      })
    } else {
      console.log('错误的提交!', fields)
    }
  })
}


const login = () => {
  router.push({ name: 'login' })
}

</script>

<style scoped>
</style>

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值