【九】fastapi+vue实现token验证登录
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
整体思路
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>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献3条内容
所有评论(0)