flask JWT 认证
目录
- 项目概述
- 后端实现详解
- 1. 环境准备
- 2. 核心组件
- 用户模型和数据
- JWT 令牌生成
- 请求加载器(核心认证逻辑)
- 3. API 端点
- 登录接口
- 受保护的接口
- 前端实现详解
- 1. 核心 JavaScript 功能
- 通用请求函数
- 登录功能
- 2. 令牌管理
- 完整代码
- 运行步骤
- 1. 启动后端服务
- 2. 打开前端页面
- 3. 测试功能
- 关键特性
- 1. Authorization 头认证
- 2. 令牌生命周期管理
- 3. 安全特性
- 4. 用户体验
下面详细介绍如何使用 Flask 后端和 HTML 前端实现 JWT(JSON Web Token)认证系统。
项目概述
这是一个完整的 JWT 认证示例,包含:
- Flask 后端 API
- HTML 前端界面
项目目录
后端实现详解
1. 环境准备
首先安装虚拟环境和必要的 Python 库:
uv init
uv venv
source .venv/Script/activate
uv pip install flask flask-cors flask-login itsdangerous
2. 核心组件
用户模型和数据
class User(UserMixin):def __init__(self, id, username, email):self.id = idself.username = usernameself.email = email# 模拟用户数据库
users_db = {'admin': {'id': '1', 'username': 'admin', 'email': 'admin@example.com', 'password': 'password123'},'user': {'id': '2', 'username': 'user', 'email': 'user@example.com', 'password': 'password456'}
}
JWT 令牌生成
# JWT 序列化器
jwt_serializer = Serializer(app.config['SECRET_KEY'])# 生成 JWT 令牌
def generate_token(user_data):payload = {'user_id': user_data['id'],'username': user_data['username']}token = jwt_serializer.dumps(payload)return token
请求加载器(核心认证逻辑)
@login_manager.request_loader
def load_user_from_request(request):# 获取 Authorization 头authorization = request.headers.get('Authorization')if not authorization:return Nonetry:# 移除 'Bearer ' 前缀(如果有)if authorization.startswith('Bearer '):token = authorization[7:]else:token = authorization# 解析 JWT 令牌payload = jwt_serializer.loads(token, max_age=24*3600) # 24小时有效期# 根据 payload 创建用户对象user = User(id=payload['user_id'],username=payload['username'],email=f"{payload['username']}@example.com")return userexcept Exception as e:return None
3. API 端点
登录接口
@app.route('/api/login', methods=['POST'])
def login():data = request.get_json()username = data.get('username')password = data.get('password')# 验证用户凭证if username in users_db and users_db[username]['password'] == password:user_data = users_db[username]# 生成 JWT 令牌token = generate_token(user_data)return jsonify({'success': True,'message': '登录成功!','access_token': token,'user': {'id': user_data['id'],'username': user_data['username'],'email': user_data['email']}})return jsonify({'success': False,'message': '用户名或密码错误!'}), 401
受保护的接口
@app.route('/api/profile', methods=['GET'])
def get_profile():if current_user.is_authenticated:return jsonify({'success': True,'user': {'id': current_user.id,'username': current_user.username,'email': current_user.email},'message': f'欢迎,{current_user.username}!'})else:return jsonify({'success': False,'message': '未授权访问,请先登录!'}), 401
前端实现详解
1. 核心 JavaScript 功能
通用请求函数
async function makeRequest(url, options = {}) {try {// 如果有令牌,自动添加 Authorization 头if (currentToken) {options.headers = {'Authorization': `Bearer ${currentToken}`,'Content-Type': 'application/json',...options.headers};} else {options.headers = {'Content-Type': 'application/json',...options.headers};}const response = await fetch(API_BASE + url, options);const data = await response.json();return { success: response.ok, data, status: response.status };} catch (error) {return { success: false, error: error.message };}
}
登录功能
async function login() {const username = document.getElementById('username').value;const password = document.getElementById('password').value;const result = await makeRequest('/api/login', {method: 'POST',body: JSON.stringify({ username, password })});if (result.success && result.data.access_token) {// 保存令牌到本地存储currentToken = result.data.access_token;localStorage.setItem('access_token', currentToken);updateLoginStatus(true);}showResponse('loginResponse', result);
}
2. 令牌管理
- 存储:使用
localStorage
持久化存储 JWT 令牌 - 自动加载:页面加载时检查本地存储的令牌
- 自动添加:每个 API 请求自动添加
Authorization
头
完整代码
html 页面
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>JWT 认证示例</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;background-color: #f5f5f5;}.container {background: white;padding: 20px;border-radius: 8px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);margin-bottom: 20px;}.form-group {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;font-weight: bold;}input, button {padding: 8px 12px;border: 1px solid #ddd;border-radius: 4px;font-size: 14px;}input {width: 200px;}button {background-color: #007bff;color: white;border: none;cursor: pointer;margin-right: 10px;}button:hover {background-color: #0056b3;}button:disabled {background-color: #6c757d;cursor: not-allowed;}.response {background-color: #f8f9fa;border: 1px solid #dee2e6;border-radius: 4px;padding: 10px;margin-top: 10px;white-space: pre-wrap;font-family: monospace;font-size: 12px;max-height: 300px;overflow-y: auto;}.success {color: #28a745;}.error {color: #dc3545;}.token-display {background-color: #e9ecef;padding: 10px;border-radius: 4px;word-break: break-all;font-family: monospace;font-size: 11px;}.status {padding: 5px 10px;border-radius: 4px;font-weight: bold;display: inline-block;margin-bottom: 10px;}.status.logged-in {background-color: #d4edda;color: #155724;}.status.logged-out {background-color: #f8d7da;color: #721c24;}</style>
</head>
<body><h1>JWT 认证示例 - Authorization 头演示</h1><!-- 登录状态显示 --><div class="container"><h2>登录状态</h2><div id="loginStatus" class="status logged-out">未登录</div><div id="tokenDisplay" class="token-display" style="display: none;"><strong>当前 JWT 令牌:</strong><br><span id="currentToken"></span></div></div><!-- 登录表单 --><div class="container"><h2>用户登录</h2><div class="form-group"><label>用户名:</label><input type="text" id="username" value="admin" placeholder="admin 或 user"></div><div class="form-group"><label>密码:</label><input type="password" id="password" value="password123" placeholder="password123 或 password456"></div><button onclick="login()">登录</button><button onclick="logout()">登出</button><div id="loginResponse" class="response"></div></div><!-- API 测试 --><div class="container"><h2>API 测试(需要 Authorization 头)</h2><button onclick="getProfile()" id="profileBtn" disabled>获取用户资料</button><button onclick="testHeaders()" id="headersBtn" disabled>测试请求头</button><div id="apiResponse" class="response"></div></div><!-- 请求示例 --><div class="container"><h2>请求示例代码</h2><div id="requestExample" class="response">
// 前端发送带 Authorization 头的请求示例:fetch('http://localhost:5000/api/profile', {method: 'GET',headers: {'Authorization': 'Bearer ' + localStorage.getItem('access_token'),'Content-Type': 'application/json'}
})
.then(response => response.json())
.then(data => console.log(data));</div></div><script>const API_BASE = 'http://localhost:5000';let currentToken = null;// 页面加载时检查本地存储的令牌window.onload = function() {const token = localStorage.getItem('access_token');if (token) {currentToken = token;updateLoginStatus(true);}};// 更新登录状态显示function updateLoginStatus(isLoggedIn) {const statusElement = document.getElementById('loginStatus');const tokenDisplay = document.getElementById('tokenDisplay');const currentTokenElement = document.getElementById('currentToken');const profileBtn = document.getElementById('profileBtn');const headersBtn = document.getElementById('headersBtn');if (isLoggedIn && currentToken) {statusElement.textContent = '已登录';statusElement.className = 'status logged-in';tokenDisplay.style.display = 'block';currentTokenElement.textContent = currentToken;profileBtn.disabled = false;headersBtn.disabled = false;} else {statusElement.textContent = '未登录';statusElement.className = 'status logged-out';tokenDisplay.style.display = 'none';profileBtn.disabled = true;headersBtn.disabled = true;}}// 通用请求函数async function makeRequest(url, options = {}) {try {// 如果有令牌,自动添加 Authorization 头if (currentToken) {options.headers = {'Authorization': `Bearer ${currentToken}`,'Content-Type': 'application/json',...options.headers};} else {options.headers = {'Content-Type': 'application/json',...options.headers};}console.log('🚀 发送请求:', {url: API_BASE + url,method: options.method || 'GET',headers: options.headers});const response = await fetch(API_BASE + url, options);const data = await response.json();return { success: response.ok, data, status: response.status };} catch (error) {return { success: false, error: error.message };}}// 显示响应function showResponse(elementId, result) {const element = document.getElementById(elementId);if (result.success) {element.className = 'response success';element.textContent = JSON.stringify(result.data, null, 2);} else {element.className = 'response error';element.textContent = result.error || JSON.stringify(result.data, null, 2);}}// 登录async function login() {const username = document.getElementById('username').value;const password = document.getElementById('password').value;const result = await makeRequest('/api/login', {method: 'POST',body: JSON.stringify({ username, password })});if (result.success && result.data.access_token) {// 保存令牌到本地存储currentToken = result.data.access_token;localStorage.setItem('access_token', currentToken);updateLoginStatus(true);console.log('✅ 登录成功,令牌已保存:', currentToken.substring(0, 50) + '...');}showResponse('loginResponse', result);}// 登出function logout() {currentToken = null;localStorage.removeItem('access_token');updateLoginStatus(false);document.getElementById('loginResponse').textContent = '已登出';document.getElementById('apiResponse').textContent = '';console.log('👋 已登出');}// 获取用户资料(需要 Authorization 头)async function getProfile() {if (!currentToken) {alert('请先登录!');return;}const result = await makeRequest('/api/profile');showResponse('apiResponse', result);}// 测试请求头async function testHeaders() {const result = await makeRequest('/api/test-headers');showResponse('apiResponse', result);}</script>
</body>
</html>
backend_jwt_example.py
from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_login import LoginManager, UserMixin, login_user, current_user
from itsdangerous import URLSafeTimedSerializer as Serializer
import datetimeapp = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'# 启用 CORS
CORS(app, supports_credentials=True)# 初始化 LoginManager
login_manager = LoginManager()
login_manager.init_app(app)# 模拟用户数据
class User(UserMixin):def __init__(self, id, username, email):self.id = idself.username = usernameself.email = emailusers_db = {'admin': {'id': '1', 'username': 'admin', 'email': 'admin@example.com', 'password': 'password123'},'user': {'id': '2', 'username': 'user', 'email': 'user@example.com', 'password': 'password456'}
}# 存储有效的 access_token
valid_tokens = {}# JWT 序列化器
jwt_serializer = Serializer(app.config['SECRET_KEY'])# 生成 JWT 令牌
def generate_token(user_data):payload = {'user_id': user_data['id'],'username': user_data['username']}token = jwt_serializer.dumps(payload)return token# request_loader - 从 Authorization 头获取用户
@login_manager.request_loader
def load_user_from_request(request):# 获取 Authorization 头authorization = request.headers.get('Authorization')if not authorization:return Nonetry:# 移除 'Bearer ' 前缀(如果有)if authorization.startswith('Bearer '):token = authorization[7:]else:token = authorization# 解析 JWT 令牌payload = jwt_serializer.loads(token, max_age=24*3600) # 24小时有效期# 根据 payload 创建用户对象user = User(id=payload['user_id'],username=payload['username'],email=f"{payload['username']}@example.com")print(f"✅ 成功验证用户: {user.username}")return userexcept Exception as e:print(f"❌ JWT 验证失败: {e}")return None# 登录接口
@app.route('/api/login', methods=['POST'])
def login():data = request.get_json()username = data.get('username')password = data.get('password')# 验证用户凭证if username in users_db and users_db[username]['password'] == password:user_data = users_db[username]# 生成 JWT 令牌token = generate_token(user_data)return jsonify({'success': True,'message': '登录成功!','access_token': token,'user': {'id': user_data['id'],'username': user_data['username'],'email': user_data['email']}})return jsonify({'success': False,'message': '用户名或密码错误!'}), 401# 受保护的接口 - 需要 Authorization 头
@app.route('/api/profile', methods=['GET'])
def get_profile():if current_user.is_authenticated:return jsonify({'success': True,'user': {'id': current_user.id,'username': current_user.username,'email': current_user.email},'message': f'欢迎,{current_user.username}!'})else:return jsonify({'success': False,'message': '未授权访问,请先登录!'}), 401# 测试接口 - 显示请求头信息
@app.route('/api/test-headers', methods=['GET'])
def test_headers():headers = dict(request.headers)authorization = request.headers.get('Authorization')return jsonify({'authorization_header': authorization,'all_headers': headers,'is_authenticated': current_user.is_authenticated,'current_user': current_user.username if current_user.is_authenticated else None})if __name__ == '__main__':print("\n=== JWT 认证示例启动 ===")print("测试用户:")print(" - 用户名: admin, 密码: password123")print(" - 用户名: user, 密码: password456")print("\n接口说明:")print(" - POST /api/login - 登录获取令牌")print(" - GET /api/profile - 获取用户资料(需要 Authorization 头)")print(" - GET /api/test-headers - 查看请求头信息")print("========================\n")app.run(debug=True, host='0.0.0.0', port=5000)
运行步骤
1. 启动后端服务
uv run backend_jwt_example.py
服务将在 http://localhost:5000
启动。
2. 打开前端页面
在浏览器中打开 http://localhost:5000/static/frontend_jwt_example.html
文件。
3. 测试功能
-
登录测试:
- 用户名:
admin
,密码:password123
- 用户名:
user
,密码:password456
- 用户名:
-
API 测试:
- 登录成功后,点击"获取用户资料"按钮
- 点击"测试请求头"查看认证信息
关键特性
1. Authorization 头认证
- 前端自动在请求头中添加
Authorization: Bearer <token>
- 后端通过
request_loader
自动解析和验证令牌
2. 令牌生命周期管理
- 令牌有效期:24小时
- 自动过期处理
- 本地存储持久化
3. 安全特性
- CORS 支持跨域请求
- 令牌签名验证
- 自动过期检查
4. 用户体验
- 实时登录状态显示
- 令牌可视化
- 详细的错误信息
- 请求示例代码展示