JWT原理 FastAPI Vue3极简实现JWT登录鉴权
前言
前后端分离项目中,传统的Session登录需要服务端存储会话信息,存在服务器集群无法共享、扩展性差的问题。而JWT作为目前最主流的无状态登录鉴权方案,彻底解决了这个痛点。
本文分为三部分:零基础看懂JWT底层原理、FastAPI后端极简实现登录签发令牌、Vue3前端单文件对接登录鉴权,所有代码极致精简,无数据库、无冗余业务逻辑,纯核心功能方便新手快速理解原理。
一、JWT是什么?
JWT全称:JSON Web Token,是一种轻量级、无状态的令牌校验规范。
核心特点:服务端不需要存储任何用户登录信息,用户登录后拿到加密令牌,后续每次请求携带令牌,服务端仅通过校验令牌合法性,就能完成身份认证,完美适配分布式、微服务架构。
二、JWT令牌三段式结构
完整JWT格式:Header.Payload.Signature,三部分通过小数点分隔,前两段均为Base64编码字符串,并非加密。
1. Header 头部
-
作用:声明令牌类型、加密算法
-
默认配置:令牌类型为JWT,默认对称加密算法 HS256
-
注意:公开可解码,禁止存放任何敏感数据
2. Payload 载荷
-
作用:存放用户自定义信息、JWT标准内置字段(过期时间、签发人等)
-
常用内置字段:
exp令牌过期时间、sub用户唯一标识 -
核心避坑:载荷仅Base64编码,可直接解码查看,严禁存放密码、手机号等隐私数据
3. Signature 签名
-
作用:防止令牌被篡改,保障令牌安全
-
生成逻辑:头部 + 载荷拼接后,使用服务端专属密钥加密生成签名
-
校验逻辑:服务端接收Token后,本地重新计算签名,和原签名比对,不一致直接判定令牌非法
三、JWT完整登录鉴权流程
-
前端输入账号密码,提交登录请求
-
后端校验账号密码合法,生成JWT令牌返回前端
-
前端本地存储Token(localStorage)
-
后续所有需要权限的接口,请求头携带
Authorization: Bearer Token -
后端统一拦截校验Token:判断是否过期、签名是否合法
-
校验通过放行接口,失败直接返回401无权限
四、JWT优缺点
✅ 优点
-
无状态:服务端无需保存会话数据,天然支持集群部署
-
跨域友好:适配前后端分离、小程序、APP、移动端
-
减少查库:Token内自带用户基础信息,无需重复查询数据库
❌ 缺点
-
令牌签发后无法主动作废,只能等待过期,无法实现强制下线
-
无法中途修改用户信息,必须重新签发新Token
五、FastAPI 后端极简实现
5.1 安装依赖
bash
pip install fastapi uvicorn python-jose[cryptography]
5.2 完整后端代码
无数据库、无复杂模型,仅两个接口:登录发Token、鉴权获取用户信息,适配前端直接联调
python
from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from jose import jwt
from datetime import datetime, timedelta
# 初始化项目
app = FastAPI(title="JWT极简登录演示")
# 全局跨域配置(解决前端联调跨域报错)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# JWT配置
SECRET_KEY = "demo-secret-key-2026" # 线上需放入环境变量
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30 # token过期时间30分钟
# 模拟测试用户
fake_user = {"username": "admin", "password": "123456"}
# 统一Token校验依赖函数
def get_current_user(token: str):
try:
# 解码并自动校验过期时间、签名合法性
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
if username is None:
raise HTTPException(status_code=401, detail="令牌无效")
return username
except Exception:
raise HTTPException(status_code=401, detail="令牌过期或非法")
# 1. 登录接口:账号密码校验,返回JWT
@app.post("/login")
def login(username: str, password: str):
if username != fake_user["username"] or password != fake_user["password"]:
raise HTTPException(status_code=401, detail="账号或密码错误")
# 计算过期时间
expire_time = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
# 生成JWT令牌
token = jwt.encode({"sub": username, "exp": expire_time}, SECRET_KEY, algorithm=ALGORITHM)
return {"access_token": token, "token_type": "bearer"}
# 2. 受保护接口:必须携带合法Token才可访问
@app.get("/user/info")
def user_info(username: str = Depends(get_current_user)):
return {"code": 200, "username": username, "msg": "接口访问成功,Token校验通过"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
5.3 后端测试方式
启动项目后,直接访问官方自动接口文档:http://127.0.0.1:8000/docs
六、Vue3 前端极简实现(单HTML文件,零工程依赖)
无需Vite、无需npm、无需构建工具,CDN直接引入Vue3,双击html即可打开运行,全程标准请求头携带Token,符合真实业务规范,修复之前url拼接token的不安全写法。
6.1 前端完整代码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Vue3 + FastAPI JWT登录演示</title>
<!-- CDN引入Vue3,零依赖开箱即用 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<style>
div{margin: 20px 0;}
input{padding: 6px; margin: 8px 0; width: 200px;}
button{padding: 6px 16px; cursor: pointer;}
</style>
</head>
<body>
<div id="app" style="padding: 40px;">
<!-- 未登录:展示登录表单 -->
<div v-if="!token">
<h3>用户登录</h3>
<input v-model="username" placeholder="用户名:admin"><br>
<input v-model="password" placeholder="密码:123456" type="password"><br>
<button @click="login">立即登录</button>
</div>
<!-- 已登录:展示功能按钮 -->
<div v-else>
<p>✅ 登录成功,本地Token:</p>
<p style="word-break: break-all;">{{token}}</p>
<button @click="getUserInfo">访问需要鉴权的接口</button>
<p>接口返回数据:{{resData}}</p>
<button @click="logout" style="margin-left: 10px;">退出登录</button>
</div>
</div>
<script setup>
const { ref } = Vue
// 表单数据 & 本地token
const username = ref('admin')
const password = ref('123456')
const token = ref(localStorage.getItem('token') || '')
const resData = ref('')
const baseUrl = 'http://127.0.0.1:8000'
// 1. 登录:获取token并存入本地缓存
const login = async () => {
const res = await fetch(`${baseUrl}/login?username=${username.value}&password=${password.value}`,{
method: 'POST'
})
const data = await res.json()
if(res.ok){
token.value = data.access_token
localStorage.setItem('token', data.access_token)
alert('登录成功!')
}else{
alert(data.detail)
}
}
// 2. 请求受保护接口:请求头携带标准Bearer Token
const getUserInfo = async () => {
const res = await fetch(`${baseUrl}/user/info`,{
method: 'GET',
headers: {
// 行业标准鉴权头
'Authorization': `Bearer ${token.value}`
}
})
resData.value = await res.json()
}
// 3. 退出登录:清空本地token
const logout = () => {
token.value = ''
localStorage.removeItem('token')
}
</script>
</body>
</html>
七、前后端联调步骤
-
运行FastAPI后端代码,保持服务常驻,端口8000不变
-
直接双击前端html文件打开页面,无需启动任何前端服务
-
点击登录,后端生成Token自动存入浏览器本地存储
-
点击访问鉴权接口,前端自动在请求头带上Token,后端校验通过返回数据
八、核心代码关键点讲解
-
exp过期字段:JWT原生自带过期校验,解码时自动判断是否过期,无需手写时间判断逻辑
-
Bearer标准格式 :所有接口鉴权统一使用
Bearer 空格+token,贴合行业通用规范 -
FastAPI依赖注入:Token校验逻辑统一封装,所有鉴权接口直接复用,代码解耦
-
本地存储:前端使用localStorage持久化Token,刷新页面无需重复登录
九、生产环境必须补充的优化点
-
密钥禁止硬编码,通过环境变量配置读取
-
用户密码禁止明文存储,后端使用bcrypt加盐加密
-
增加刷新令牌refresh_token,避免用户频繁登录
-
敏感接口缩短Token有效期,提升接口安全性
-
增加全局请求拦截器,前端统一处理401无权限、自动跳转登录页
十、总结
-
JWT核心精髓:无状态、三段结构、签名防篡改,适配前后端分离架构
-
本次前后端代码均做到极致精简,剔除所有无关业务代码,专注讲解JWT鉴权核心流程
-
完整流程:登录发证 - 前端存证 - 请求带证 - 后端验证,覆盖企业项目最基础的登录鉴权全链路